urkit 0.3.7__tar.gz → 0.3.9__tar.gz

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.
Files changed (39) hide show
  1. {urkit-0.3.7 → urkit-0.3.9}/PKG-INFO +10 -7
  2. {urkit-0.3.7 → urkit-0.3.9}/README.md +9 -6
  3. {urkit-0.3.7 → urkit-0.3.9}/pyproject.toml +1 -1
  4. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/__init__.py +1 -1
  5. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/robot.py +156 -32
  6. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/PKG-INFO +10 -7
  7. {urkit-0.3.7 → urkit-0.3.9}/setup.cfg +0 -0
  8. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/__main__.py +0 -0
  9. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/cli/__init__.py +0 -0
  10. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/cli/colors.py +0 -0
  11. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/cli/connection_monitor.py +0 -0
  12. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/cli/points.py +0 -0
  13. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/cli/teach.py +0 -0
  14. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/config.py +0 -0
  15. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/connection.py +0 -0
  16. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/exceptions.py +0 -0
  17. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/geometry.py +0 -0
  18. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/__init__.py +0 -0
  19. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/base.py +0 -0
  20. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/digital.py +0 -0
  21. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/presets.py +0 -0
  22. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/robotiq.py +0 -0
  23. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/gripper/robotiq_preamble.py +0 -0
  24. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/io.py +0 -0
  25. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/motion.py +0 -0
  26. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/points.py +0 -0
  27. {urkit-0.3.7 → urkit-0.3.9}/src/urkit/telemetry.py +0 -0
  28. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/SOURCES.txt +0 -0
  29. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/dependency_links.txt +0 -0
  30. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/entry_points.txt +0 -0
  31. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/requires.txt +0 -0
  32. {urkit-0.3.7 → urkit-0.3.9}/src/urkit.egg-info/top_level.txt +0 -0
  33. {urkit-0.3.7 → urkit-0.3.9}/tests/test_exceptions.py +0 -0
  34. {urkit-0.3.7 → urkit-0.3.9}/tests/test_geometry.py +0 -0
  35. {urkit-0.3.7 → urkit-0.3.9}/tests/test_gripper.py +0 -0
  36. {urkit-0.3.7 → urkit-0.3.9}/tests/test_gripper_factory.py +0 -0
  37. {urkit-0.3.7 → urkit-0.3.9}/tests/test_gripper_presets.py +0 -0
  38. {urkit-0.3.7 → urkit-0.3.9}/tests/test_points.py +0 -0
  39. {urkit-0.3.7 → urkit-0.3.9}/tests/test_robot_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -88,7 +88,7 @@ robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E)
88
88
  robot.gripper.activate()
89
89
 
90
90
  robot.move_to("home")
91
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
91
+ robot.move_to("pick", offset_z=0.05) # 5cm above
92
92
  robot.gripper.close()
93
93
  robot.move_to("place")
94
94
  robot.gripper.open()
@@ -394,10 +394,12 @@ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rot
394
394
 
395
395
  #### Offsets
396
396
 
397
- Offsets are 6-element lists `[dx, dy, dz, drx, dry, drz]`:
397
+ Offsets can use individual parameters or a full 6-element list:
398
398
 
399
399
  ```python
400
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
400
+ robot.move_to("pick", offset_z=0.05) # 5cm above
401
+ robot.move_to("pick", offset_x=0.01, offset_z=-0.02) # combined
402
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full with rotation
401
403
  ```
402
404
 
403
405
  #### Resolve a Pose
@@ -416,7 +418,7 @@ robot.move_to(pose) # move to the resolved pose later
416
418
  from urkit import MoveFrame
417
419
 
418
420
  robot.move_frame = MoveFrame.TOOL # default is BASE
419
- robot.move_relative([0, 0, 0.05, 0, 0, 0]) # 5cm along tool Z
421
+ robot.move_relative(delta_z=0.05) # 5cm along tool Z
420
422
  ```
421
423
 
422
424
  - **BASE** (default): delta relative to robot base
@@ -440,8 +442,9 @@ robot.import_points("backup.json")
440
442
  #### Relative Moves
441
443
 
442
444
  ```python
443
- robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
444
- robot.move_relative([0, 0, 0.05, 0, 0, 0], frame=MoveFrame.TOOL) # 5cm along tool Z
445
+ robot.move_relative(delta_y=0.01) # 1cm along Y
446
+ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
447
+ robot.move_relative([0, 0.01, 0, 0, 0, 0])
445
448
  ```
446
449
 
447
450
  #### Sequences with Blending
@@ -62,7 +62,7 @@ robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E)
62
62
  robot.gripper.activate()
63
63
 
64
64
  robot.move_to("home")
65
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
65
+ robot.move_to("pick", offset_z=0.05) # 5cm above
66
66
  robot.gripper.close()
67
67
  robot.move_to("place")
68
68
  robot.gripper.open()
@@ -368,10 +368,12 @@ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rot
368
368
 
369
369
  #### Offsets
370
370
 
371
- Offsets are 6-element lists `[dx, dy, dz, drx, dry, drz]`:
371
+ Offsets can use individual parameters or a full 6-element list:
372
372
 
373
373
  ```python
374
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
374
+ robot.move_to("pick", offset_z=0.05) # 5cm above
375
+ robot.move_to("pick", offset_x=0.01, offset_z=-0.02) # combined
376
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full with rotation
375
377
  ```
376
378
 
377
379
  #### Resolve a Pose
@@ -390,7 +392,7 @@ robot.move_to(pose) # move to the resolved pose later
390
392
  from urkit import MoveFrame
391
393
 
392
394
  robot.move_frame = MoveFrame.TOOL # default is BASE
393
- robot.move_relative([0, 0, 0.05, 0, 0, 0]) # 5cm along tool Z
395
+ robot.move_relative(delta_z=0.05) # 5cm along tool Z
394
396
  ```
395
397
 
396
398
  - **BASE** (default): delta relative to robot base
@@ -414,8 +416,9 @@ robot.import_points("backup.json")
414
416
  #### Relative Moves
415
417
 
416
418
  ```python
417
- robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
418
- robot.move_relative([0, 0, 0.05, 0, 0, 0], frame=MoveFrame.TOOL) # 5cm along tool Z
419
+ robot.move_relative(delta_y=0.01) # 1cm along Y
420
+ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
421
+ robot.move_relative([0, 0.01, 0, 0, 0, 0])
419
422
  ```
420
423
 
421
424
  #### Sequences with Blending
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "urkit"
7
- version = "0.3.7"
7
+ version = "0.3.9"
8
8
  description = "Universal Robots e-Series control toolkit built on ur_rtde"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -24,7 +24,7 @@ Quick start::
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
- __version__ = "0.3.7"
27
+ __version__ = "0.3.9"
28
28
 
29
29
  from urkit.config import load_config, resolve_config
30
30
  from urkit.exceptions import (
@@ -163,7 +163,10 @@ class URRobot:
163
163
  # handles that case too.
164
164
  self._connect_dashboard()
165
165
  did_power_on = self.power_on()
166
- did_release = self.release_brakes()
166
+ # After power_on the robot is IDLE but brakes are still engaged —
167
+ # robotmode can't distinguish "IDLE braked" from "IDLE released",
168
+ # so force the brake release when we just powered on.
169
+ did_release = self.release_brakes(force=did_power_on)
167
170
  boot_needed = did_power_on or did_release
168
171
 
169
172
  # Settle after boot — the robot reports IDLE but the Secondary
@@ -438,7 +441,7 @@ class URRobot:
438
441
  Example:
439
442
  >>> robot.move_frame = MoveFrame.TOOL
440
443
  >>> robot.move_relative([0.01, 0, 0, 0, 0, 0]) # +X in tool frame
441
- >>> robot.move_to("pick", offset=[0, 0, 0.05]) # offset in tool frame
444
+ >>> robot.move_to("pick", offset_z=0.05) # offset in tool frame
442
445
  """
443
446
  return self._move_frame
444
447
 
@@ -584,9 +587,13 @@ class URRobot:
584
587
  if "Powering on" in response:
585
588
  _status("Powering on robot...", done=False)
586
589
  logger.info("Robot powering on, waiting for ready state...")
590
+ # Wait until the robot reaches IDLE (brakes engaged, ready for
591
+ # brake release). Stopping at "not POWER_OFF" is insufficient —
592
+ # the robot may still be in POWER_ON or BOOTING where the
593
+ # dashboard rejects "brake release" with a mounting error.
587
594
  self._poll_robotmode(
588
- lambda m: "POWER_OFF" not in m.upper(),
589
- timeout=30.0,
595
+ lambda m: "IDLE" in m.upper(),
596
+ timeout=15.0,
590
597
  )
591
598
  _status("Robot powered on", done=True)
592
599
  return True
@@ -613,11 +620,17 @@ class URRobot:
613
620
  except Exception as e:
614
621
  raise ConnectionError(f"Power off failed: {e}")
615
622
 
616
- def release_brakes(self) -> bool:
623
+ def release_brakes(self, *, force: bool = False) -> bool:
617
624
  """Release the robot brakes (enable control).
618
625
 
619
- Checks the current robot mode first. If the robot is already
620
- in IDLE or RUNNING, the command is skipped silently.
626
+ Checks the current mode first. If the robot is already
627
+ in RUNNING, the command is skipped silently. (IDLE cannot
628
+ distinguish braked from released — use *force* after power_on.)
629
+
630
+ Args:
631
+ force: Skip the mode check and send brake release regardless.
632
+ Should be True when called immediately after power_on(),
633
+ because robotmode reports IDLE for both braked and released.
621
634
 
622
635
  Polls until the robot reports IDLE or RUNNING mode.
623
636
 
@@ -629,20 +642,34 @@ class URRobot:
629
642
  ConnectionError: If brake release fails or times out.
630
643
  """
631
644
  try:
632
- # Check current mode first — skip if already released.
645
+ # Check current mode first — skip if already running.
646
+ # Note: IDLE is ambiguous (brakes may be engaged or released),
647
+ # so we only skip on RUNNING. When force=True (called after
648
+ # power_on), we skip the check entirely.
633
649
  mode = self._send_dashboard("robotmode").upper()
634
- if "IDLE" in mode or "RUNNING" in mode:
650
+ if not force and "RUNNING" in mode:
635
651
  logger.info("Brakes already released (mode: %s), skipping.", mode)
636
652
  return False
637
653
 
638
654
  response = self._send_dashboard("brake release")
639
655
  logger.info("Brake release: %s", response)
656
+
657
+ # Retry once if the mounting calibration wasn't ready.
658
+ if "mounting is not correct" in response:
659
+ logger.info(
660
+ "Brake release rejected (mounting not ready), "
661
+ "retrying after 3s..."
662
+ )
663
+ time.sleep(3)
664
+ response = self._send_dashboard("brake release")
665
+ logger.info("Brake release (retry): %s", response)
666
+
640
667
  if "Brake releasing" in response:
641
668
  _status("Releasing brakes...", done=False)
642
669
  logger.info("Brakes releasing, waiting for ready state...")
643
670
  self._poll_robotmode(
644
671
  lambda m: "IDLE" in m.upper() or "RUNNING" in m.upper(),
645
- timeout=30.0,
672
+ timeout=15.0,
646
673
  )
647
674
  _status("Brakes released", done=True)
648
675
  return True
@@ -821,6 +848,12 @@ class URRobot:
821
848
  *,
822
849
  offset: list[float] | None = None,
823
850
  frame: MoveFrame | None = None,
851
+ offset_x: float = 0.0,
852
+ offset_y: float = 0.0,
853
+ offset_z: float = 0.0,
854
+ offset_rx: float = 0.0,
855
+ offset_ry: float = 0.0,
856
+ offset_rz: float = 0.0,
824
857
  ) -> list[float]:
825
858
  """Resolve a saved point or raw pose to a TCP pose.
826
859
 
@@ -831,9 +864,16 @@ class URRobot:
831
864
  target: A saved point name (str) or a raw TCP pose
832
865
  [x, y, z, rx, ry, rz].
833
866
  offset: Optional offset [dx, dy, dz, drx, dry, drz]
834
- applied to the target pose.
867
+ applied to the target pose. Mutually exclusive with
868
+ individual offset_* parameters.
835
869
  frame: Coordinate frame for the offset. Falls back to the
836
870
  current ``move_frame`` property (BASE or TOOL).
871
+ offset_x: X offset in meters (default 0.0).
872
+ offset_y: Y offset in meters (default 0.0).
873
+ offset_z: Z offset in meters (default 0.0).
874
+ offset_rx: Roll offset in radians (default 0.0).
875
+ offset_ry: Pitch offset in radians (default 0.0).
876
+ offset_rz: Yaw offset in radians (default 0.0).
837
877
 
838
878
  Returns:
839
879
  TCP pose as [x, y, z, rx, ry, rz].
@@ -843,18 +883,32 @@ class URRobot:
843
883
 
844
884
  Example:
845
885
  >>> pose = robot.get_pose("pick")
846
- >>> pose = robot.get_pose("pick", offset=[0, 0, 0.05, 0, 0, 0])
886
+ >>> pose = robot.get_pose("pick", offset_z=0.05)
847
887
  >>> robot.move_to(pose)
848
888
  """
849
889
  point = self._lookup_point(target)
850
890
 
851
- if offset is not None:
852
- if len(offset) != 6:
891
+ # Build offset from individual params or use provided list
892
+ individual_offset = [offset_x, offset_y, offset_z, offset_rx, offset_ry, offset_rz]
893
+ has_individual = any(v != 0.0 for v in individual_offset)
894
+
895
+ if offset is not None and has_individual:
896
+ raise PointError(
897
+ "Cannot use both 'offset' list and individual offset_* parameters. "
898
+ "Use one or the other."
899
+ )
900
+
901
+ final_offset = offset if offset is not None and not has_individual else (
902
+ individual_offset if has_individual else None
903
+ )
904
+
905
+ if final_offset is not None:
906
+ if len(final_offset) != 6:
853
907
  raise PointError(
854
908
  f"Offset must have 6 values [dx, dy, dz, drx, dry, drz], "
855
- f"got {len(offset)}."
909
+ f"got {len(final_offset)}."
856
910
  )
857
- point = point.with_offset(offset, frame=frame or self._move_frame)
911
+ point = point.with_offset(final_offset, frame=frame or self._move_frame)
858
912
 
859
913
  return list(point.pose)
860
914
 
@@ -868,6 +922,12 @@ class URRobot:
868
922
  vel: float | None = None,
869
923
  acc: float | None = None,
870
924
  asynchronous: bool = False,
925
+ offset_x: float = 0.0,
926
+ offset_y: float = 0.0,
927
+ offset_z: float = 0.0,
928
+ offset_rx: float = 0.0,
929
+ offset_ry: float = 0.0,
930
+ offset_rz: float = 0.0,
871
931
  ) -> None:
872
932
  """Move to a saved point or raw pose.
873
933
 
@@ -877,11 +937,20 @@ class URRobot:
877
937
  linear: If True (default), use Cartesian linear move (moveL).
878
938
  If False, use joint-space move (moveJ).
879
939
  offset: Optional offset [dx, dy, dz, drx, dry, drz]
880
- applied to the target pose before moving.
940
+ applied to the target pose before moving. Mutually
941
+ exclusive with individual offset_* parameters.
881
942
  frame: Coordinate frame for the offset. Falls back to the
882
943
  current ``move_frame`` property (BASE or TOOL).
883
944
  vel: Velocity override. Falls back to default_vel.
884
945
  acc: Acceleration override. Falls back to default_acc.
946
+ asynchronous: If True, move runs in background and method returns
947
+ immediately (default False).
948
+ offset_x: X offset in meters (default 0.0).
949
+ offset_y: Y offset in meters (default 0.0).
950
+ offset_z: Z offset in meters (default 0.0).
951
+ offset_rx: Roll offset in radians (default 0.0).
952
+ offset_ry: Pitch offset in radians (default 0.0).
953
+ offset_rz: Yaw offset in radians (default 0.0).
885
954
 
886
955
  Raises:
887
956
  MotionError: If the move fails or IK has no solution.
@@ -890,8 +959,9 @@ class URRobot:
890
959
  Example:
891
960
  >>> robot.move_to("home")
892
961
  >>> robot.move_to("pick", linear=False)
893
- >>> robot.move_to("place", offset=[0, 0, 0.05, 0, 0, 0])
894
- >>> robot.move_to("place", offset=[0, 0, 0.05], frame=MoveFrame.TOOL)
962
+ >>> robot.move_to("pick", offset_z=0.05) # 5cm above
963
+ >>> robot.move_to("pick", offset_x=0.01, offset_z=-0.02)
964
+ >>> robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full
895
965
  >>> robot.move_to([0.5, 0, 0.3, 0, 0, 0]) # raw pose
896
966
  """
897
967
  self._check_connection()
@@ -899,14 +969,28 @@ class URRobot:
899
969
 
900
970
  point = self._lookup_point(target)
901
971
 
972
+ # Build offset from individual params or use provided list
973
+ individual_offset = [offset_x, offset_y, offset_z, offset_rx, offset_ry, offset_rz]
974
+ has_individual = any(v != 0.0 for v in individual_offset)
975
+
976
+ if offset is not None and has_individual:
977
+ raise PointError(
978
+ "Cannot use both 'offset' list and individual offset_* parameters. "
979
+ "Use one or the other."
980
+ )
981
+
982
+ final_offset = offset if offset is not None and not has_individual else (
983
+ individual_offset if has_individual else None
984
+ )
985
+
902
986
  # Apply offset if provided
903
- if offset is not None:
904
- if len(offset) != 6:
987
+ if final_offset is not None:
988
+ if len(final_offset) != 6:
905
989
  raise PointError(
906
990
  f"Offset must have 6 values [dx, dy, dz, drx, dry, drz], "
907
- f"got {len(offset)}."
991
+ f"got {len(final_offset)}."
908
992
  )
909
- point = point.with_offset(offset, frame=frame or self._move_frame)
993
+ point = point.with_offset(final_offset, frame=frame or self._move_frame)
910
994
 
911
995
  pose = list(point.pose)
912
996
 
@@ -978,12 +1062,19 @@ class URRobot:
978
1062
 
979
1063
  def move_relative(
980
1064
  self,
981
- delta: list[float],
1065
+ delta: list[float] | None = None,
982
1066
  *,
983
1067
  linear: bool = True,
984
1068
  frame: MoveFrame | None = None,
985
1069
  vel: float | None = None,
986
1070
  acc: float | None = None,
1071
+ asynchronous: bool = False,
1072
+ delta_x: float = 0.0,
1073
+ delta_y: float = 0.0,
1074
+ delta_z: float = 0.0,
1075
+ delta_rx: float = 0.0,
1076
+ delta_ry: float = 0.0,
1077
+ delta_rz: float = 0.0,
987
1078
  ) -> None:
988
1079
  """Relative Cartesian move from the current position.
989
1080
 
@@ -992,27 +1083,57 @@ class URRobot:
992
1083
 
993
1084
  Args:
994
1085
  delta: [dx, dy, dz, drx, dry, drz] in meters/radians.
1086
+ Mutually exclusive with individual delta_* parameters.
995
1087
  linear: If True (default), use Cartesian linear move.
996
1088
  If False, solve IK and use joint-space move.
997
1089
  frame: Coordinate frame for the delta. Falls back to the
998
1090
  current ``move_frame`` property (BASE or TOOL).
999
1091
  vel: Velocity override. Falls back to default_vel.
1000
1092
  acc: Acceleration override. Falls back to default_acc.
1093
+ asynchronous: If True, move runs in background and method returns
1094
+ immediately (default False).
1095
+ delta_x: X delta in meters (default 0.0).
1096
+ delta_y: Y delta in meters (default 0.0).
1097
+ delta_z: Z delta in meters (default 0.0).
1098
+ delta_rx: Roll delta in radians (default 0.0).
1099
+ delta_ry: Pitch delta in radians (default 0.0).
1100
+ delta_rz: Yaw delta in radians (default 0.0).
1001
1101
 
1002
1102
  Raises:
1003
1103
  MotionError: If the move fails.
1004
1104
 
1005
1105
  Example:
1006
- >>> robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
1007
- >>> robot.move_relative([0, 0, 0.05], frame=MoveFrame.TOOL)
1106
+ >>> robot.move_relative(delta_y=0.01) # 1cm along Y
1107
+ >>> robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL)
1108
+ >>> robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full list
1008
1109
  """
1009
1110
  self._check_connection()
1010
1111
  self._disable_freedrive_guard()
1011
1112
 
1012
- if len(delta) != 6:
1113
+ # Build delta from individual params or use provided list
1114
+ individual_delta = [delta_x, delta_y, delta_z, delta_rx, delta_ry, delta_rz]
1115
+ has_individual = any(v != 0.0 for v in individual_delta)
1116
+
1117
+ if delta is not None and has_individual:
1118
+ raise MotionError(
1119
+ "Cannot use both 'delta' list and individual delta_* parameters. "
1120
+ "Use one or the other."
1121
+ )
1122
+
1123
+ final_delta = delta if delta is not None and not has_individual else (
1124
+ individual_delta if has_individual else None
1125
+ )
1126
+
1127
+ if final_delta is None:
1128
+ raise MotionError(
1129
+ "Relative move requires either 'delta' list or at least one "
1130
+ "delta_* parameter."
1131
+ )
1132
+
1133
+ if len(final_delta) != 6:
1013
1134
  raise MotionError(
1014
1135
  f"Relative move requires 6 values [dx,dy,dz,drx,dry,drz], "
1015
- f"got {len(delta)}."
1136
+ f"got {len(final_delta)}."
1016
1137
  )
1017
1138
 
1018
1139
  vel = vel if vel is not None else self._default_vel
@@ -1021,13 +1142,13 @@ class URRobot:
1021
1142
 
1022
1143
  try:
1023
1144
  current = list(self._rtde_r.getActualTCPPose())
1024
- target = transform_pose_delta(current, delta, effective_frame)
1145
+ target = transform_pose_delta(current, final_delta, effective_frame)
1025
1146
 
1026
1147
  if linear:
1027
- self._motion.movel(target, vel=vel, acc=acc)
1148
+ self._motion.movel(target, vel=vel, acc=acc, asynchronous=asynchronous)
1028
1149
  else:
1029
1150
  joints = self.inverse_kinematics(target)
1030
- self._motion.movej(joints, vel=vel, acc=acc)
1151
+ self._motion.movej(joints, vel=vel, acc=acc, asynchronous=asynchronous)
1031
1152
  except MotionError:
1032
1153
  raise
1033
1154
  except Exception as e:
@@ -1041,6 +1162,7 @@ class URRobot:
1041
1162
  blend_radius: float = 0.0,
1042
1163
  vel: float | None = None,
1043
1164
  acc: float | None = None,
1165
+ asynchronous: bool = False,
1044
1166
  ) -> None:
1045
1167
  """Move through a sequence of points with optional blending.
1046
1168
 
@@ -1062,6 +1184,8 @@ class URRobot:
1062
1184
  Applied to intermediate points only.
1063
1185
  vel: Velocity override. Falls back to default_vel.
1064
1186
  acc: Acceleration override. Falls back to default_acc.
1187
+ asynchronous: If True, sequence runs in background and method
1188
+ returns immediately (default False).
1065
1189
 
1066
1190
  Raises:
1067
1191
  MotionError: If the sequence fails or fewer than 2 targets.
@@ -1109,7 +1233,7 @@ class URRobot:
1109
1233
  )
1110
1234
 
1111
1235
  try:
1112
- self._rtde_c.movePath(path, False) # False = synchronous
1236
+ self._rtde_c.movePath(path, not asynchronous)
1113
1237
  except Exception as e:
1114
1238
  raise MotionError(f"move_sequence failed: {e}")
1115
1239
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -88,7 +88,7 @@ robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E)
88
88
  robot.gripper.activate()
89
89
 
90
90
  robot.move_to("home")
91
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
91
+ robot.move_to("pick", offset_z=0.05) # 5cm above
92
92
  robot.gripper.close()
93
93
  robot.move_to("place")
94
94
  robot.gripper.open()
@@ -394,10 +394,12 @@ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rot
394
394
 
395
395
  #### Offsets
396
396
 
397
- Offsets are 6-element lists `[dx, dy, dz, drx, dry, drz]`:
397
+ Offsets can use individual parameters or a full 6-element list:
398
398
 
399
399
  ```python
400
- robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0]) # 5cm above pick
400
+ robot.move_to("pick", offset_z=0.05) # 5cm above
401
+ robot.move_to("pick", offset_x=0.01, offset_z=-0.02) # combined
402
+ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full with rotation
401
403
  ```
402
404
 
403
405
  #### Resolve a Pose
@@ -416,7 +418,7 @@ robot.move_to(pose) # move to the resolved pose later
416
418
  from urkit import MoveFrame
417
419
 
418
420
  robot.move_frame = MoveFrame.TOOL # default is BASE
419
- robot.move_relative([0, 0, 0.05, 0, 0, 0]) # 5cm along tool Z
421
+ robot.move_relative(delta_z=0.05) # 5cm along tool Z
420
422
  ```
421
423
 
422
424
  - **BASE** (default): delta relative to robot base
@@ -440,8 +442,9 @@ robot.import_points("backup.json")
440
442
  #### Relative Moves
441
443
 
442
444
  ```python
443
- robot.move_relative([0, 0.01, 0, 0, 0, 0]) # 1cm along Y
444
- robot.move_relative([0, 0, 0.05, 0, 0, 0], frame=MoveFrame.TOOL) # 5cm along tool Z
445
+ robot.move_relative(delta_y=0.01) # 1cm along Y
446
+ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
447
+ robot.move_relative([0, 0.01, 0, 0, 0, 0])
445
448
  ```
446
449
 
447
450
  #### Sequences with Blending
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes