Skip to content

Commit

Permalink
support python 3.11, 3.12; bug fixes; test initial publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
justagist committed Aug 7, 2024
1 parent 892a2f0 commit b03d95c
Show file tree
Hide file tree
Showing 9 changed files with 1,398 additions and 2,078 deletions.
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
# Only publish on `publish` branch
if: github.ref == 'refs/heads/publish'
runs-on: ubuntu-latest
environment: release
permissions: # Don't forget permissions
contents: write
steps:
Expand Down
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Changelog

## pyrcf

## [0.0.0]
## [0.0.1] - 2024-08-08

### Adds

- Initial working version of framework
- Minimal dummy example of control loop
3,323 changes: 1,326 additions & 1,997 deletions pixi.lock

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
[project]
name = "pyrcf"
version = "0.2.0"
version = "0.0.1"
authors = [{ name = "Saif Sidhik", email = "[email protected]" }]
description = "A Python Robot Control Framework for quickly prototyping control algorithms for different robot embodiments."
readme = "README.md"

dependencies = ["pybullet>=3.2.6,<4", "numpy", "pin>=2.7.0,<3", "pybullet-robot>=0.1.4"]
dependencies = [
"pybullet>=3.2.6,<4",
"pin>=2.7.0,<3",
"pybullet-robot>=0.1.4",
"yourdfpy>=0.0.56,<0.0.57",
"scipy>=1.14.0,<2",
"pygame>=2.6.0,<3",
"numpy>=1.26",
]

[tool.pixi.project]
channels = ["conda-forge"]
platforms = ["linux-64"]

[tool.pixi.dependencies]
python = ">=3.10,<3.11"
nptyping = ">=2.5.0,<3"
dataclasses = ">=0.8,<0.9"
numpy = ">=1.6,<2.0"
yourdfpy = ">=0.0.56,<0.0.57"
scipy = ">=1.14.0,<2"
pygame = ">=2.6.0,<3"
python = ">=3.10"

[tool.pixi.feature.py310.dependencies]
python = "3.10.*"
# [tool.pixi.feature.py311.dependencies]
# python = "3.11.*"
# [tool.pixi.feature.py312.dependencies]
# python = "3.12.*"
[tool.pixi.feature.py311.dependencies]
python = "3.11.*"
[tool.pixi.feature.py312.dependencies]
python = "3.12.*"

[tool.pixi.feature.devenv]
dependencies = { python = "3.10" }
Expand Down Expand Up @@ -62,8 +64,8 @@ include = ["pyrcf"]
[tool.pixi.environments]
default = { features = ["test"], solve-group = "default" }
py310 = ["py310", "test"]
# py311 = ["py311", "test"]
# py312 = ["py312", "test"]
py311 = ["py311", "test"]
py312 = ["py312", "test"]
devenv = { features = [
"devenv",
], solve-group = "devenv", no-default-feature = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@


class UIBase(GlobalMotionPlannerBase):
class NotConnectedError(RuntimeError):
"""Exception to be thrown when a UI interface could not be detected or was disconnected."""

def __init__(self, *args):
super().__init__(*args)

@abstractmethod
def process_user_input(
Expand Down
10 changes: 10 additions & 0 deletions pyrcf/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
class PyRCFExceptionBase(Exception): ...


# CtrlLoop Signal
class CtrlLoopExitSignal(KeyboardInterrupt):
"""Request to exit the control loop."""


# UI Errors
class UIException(IOError):
"""Exceptions relating to user interfaces (UIBase objects)."""


class NotConnectedError(UIException):
"""Exception to be thrown when a UI interface could not be detected or was disconnected."""
19 changes: 9 additions & 10 deletions pyrcf/core/types/planner_outputs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum

from .tf_types import Twist, RelativePose
Expand Down Expand Up @@ -78,17 +78,17 @@ class GlobalMotionPlan:
[r,p,y] = [0.0, 0.0, yaw_BF] in world
"""

twist: Twist = Twist()
twist: Twist = field(default_factory=Twist)
"""Twist in base/teleop frame of the robot"""

# joint reference is not ideal in a global plan message; only left for directly sending
# reference using joystick/gui (for testing new robots, or for controlling manipulator)
joint_references: JointStates = JointStates()
joint_references: JointStates = field(default_factory=JointStates)
"""Desired joint references. Typically a local planner would only fill joint position or
velocity values, and not effort values."""
# ee references is not ideal in a global plan message; only left for directly sending reference
# using joystick/gui (for testing new robots, or for controlling manipulator)
end_effector_references: EndEffectorStates = EndEffectorStates()
end_effector_references: EndEffectorStates = field(default_factory=EndEffectorStates)
"""Desired end-effector pose (in global frame), contact states, contact forces etc. can be
defined here."""

Expand Down Expand Up @@ -120,9 +120,8 @@ class LocalMotionPlan:
This may have to be changed later if there is a need.
"""

relative_pose: RelativePose = (
None # setting default value of identity is wrong (due to definition of teleop frame)
)
# setting default value of identity is wrong (due to definition of teleop frame)
relative_pose: RelativePose = None
"""Pose in teleop frame of the robot.
Teleop frame is equivalent to robot (base) frame, but has the roll, pitch and height values
Expand All @@ -136,12 +135,12 @@ class LocalMotionPlan:
-------
NOTE: The frame used may have to be changed later. We may need to use this in the global frame.
"""
twist: Twist = Twist()
twist: Twist = field(default_factory=Twist)
"""Base velocity commands in the base frame."""
joint_references: JointStates = JointStates()
joint_references: JointStates = field(default_factory=JointStates)
"""Desired joint references. Typically a local planner would only fill joint position or
velocity values, and not effort values."""
end_effector_references: EndEffectorStates = EndEffectorStates()
end_effector_references: EndEffectorStates = field(default_factory=EndEffectorStates)
"""Desired end-effector pose (in global frame), contact states, contact forces etc. can be
defined here.
Expand Down
12 changes: 6 additions & 6 deletions pyrcf/core/types/robot_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,11 @@ class StateEstimates:
NOTE: It might be worth making base state estimates into type FrameMotion.
"""

pose: Pose3D = Pose3D()
pose: Pose3D = field(default_factory=Pose3D)
"""Pose of the robot's base link in the world frame."""
twist: Twist = Twist()
twist: Twist = field(default_factory=Twist)
"""Velocity of the robot in the base frame; linear and angular."""
end_effector_states: EndEffectorStates = EndEffectorStates()
end_effector_states: EndEffectorStates = field(default_factory=EndEffectorStates)
"""State estimate data relating to end-effectors of this robot."""

def extend(
Expand Down Expand Up @@ -419,9 +419,9 @@ class RobotState:
All joint values should be in the order mentioned in joint_names.
"""

state_estimates: StateEstimates = StateEstimates()
state_estimates: StateEstimates = field(default_factory=StateEstimates)
"""State estimator output."""
joint_states: JointStates = JointStates()
joint_states: JointStates = field(default_factory=JointStates)
"""Proprioceptive data from the robot sensors/interfaces."""

def extend(
Expand Down Expand Up @@ -491,7 +491,7 @@ class RobotCmd:
Kd (np.ndarray)
"""

joint_commands: JointStates = JointStates()
joint_commands: JointStates = field(default_factory=JointStates)
Kp: np.ndarray = None
"""Array of joint position gains for each joint (stiffness) in `joint_commands.joint_names`
order."""
Expand Down
67 changes: 25 additions & 42 deletions pyrcf/core/types/tf_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,35 @@ class Pose3D:
Defaults to False.
"""

position: Vector3D = np.zeros(3)
orientation: QuatType = np.array([0, 0, 0, 1])
position: Vector3D = field(default_factory=lambda: np.zeros(3))
orientation: QuatType = field(default_factory=lambda: np.array([0, 0, 0, 1]))
"""quaternion [x,y,z,w]"""

validate_quaternion: bool = field(default=False, init=True, repr=False)
"""If this is enabled, quaternion validity will be checked after construction. Default False."""

def __post_init__(self):
self.__do_checks()

def __do_checks(self):
assert len(self.position) == 3, "Position dimension should be 3."
assert len(self.orientation) == 4, "Quaternion dimension should be 4."
if not self.validate_quaternion:
return
assert np.isclose(np.linalg.norm(self.orientation), 1.0), "Quaternion is not normalised."

def __eq__(self, other: "Pose3D") -> bool:
return np.allclose(self.position, other.position) and np.allclose(
self.orientation, other.orientation
)

def __setattr__(self, prop, val):
if prop not in ["position", "orientation", "validate_quaternion"]:
raise NameError(
f"Tried to assign value to illegal parameter {prop} in object of type "
f"{self.__class__.__name__}."
)
if prop != "validate_quaternion":
val = np.array(val)
super().__setattr__(prop, val)
self.__do_checks()
match prop:
case "position":
assert len(val) == 3, "Position dimension should be 3."
case "orientation":
assert len(val) == 4, "Quaternion dimension should be 4."
if self.validate_quaternion:
assert np.isclose(np.linalg.norm(val), 1.0), "Quaternion is not normalised."
case "validate_quaternion":
super().__setattr__(prop, val)
return
case _:
raise NameError(
f"Tried to assign value to illegal parameter {prop} in object of type "
f"{self.__class__.__name__}."
)
super().__setattr__(prop, np.array(val))


@dataclass
Expand All @@ -60,15 +57,8 @@ class Twist:
This object also allows equality comparison (e.g. check for (`if twist1 == twist2:`)).
"""

linear: Vector3D = np.zeros(3)
angular: Vector3D = np.zeros(3)

def __post_init__(self):
self.__do_checks()

def __do_checks(self):
assert len(self.linear) == 3, "Linear velocity should be of dimension 3"
assert len(self.angular) == 3, "Angular velocity should be of dimension 3"
linear: Vector3D = field(default_factory=lambda: np.zeros(3))
angular: Vector3D = field(default_factory=lambda: np.zeros(3))

def __eq__(self, other: "Twist") -> bool:
return np.allclose(self.linear, other.linear) and np.allclose(self.angular, other.angular)
Expand All @@ -77,10 +67,10 @@ def __setattr__(self, __name: str, __value: np.ndarray | Tuple[float]) -> None:
if __name not in ["linear", "angular"]:
raise NameError(
f"Tried to assign value to illegal parameter {__name} in object of type "
f" {self.__class__.__name__}."
f"{self.__class__.__name__}."
)
assert len(__value) == 3, f"{__name} velocity should be of dimension 3"
super().__setattr__(__name, np.array(__value))
self.__do_checks()


@dataclass
Expand All @@ -91,17 +81,10 @@ class RelativePose:
This object also allows equality comparison (e.g. check for (`if r_pose1 == r_pose2:`)).
"""

position: Vector3D = np.zeros(3)
rpy: Vector3D = np.zeros(3)
position: Vector3D = field(default_factory=lambda: np.zeros(3))
rpy: Vector3D = field(default_factory=lambda: np.zeros(3))
"""[Roll, Pitch, Yaw] in radians"""

def __post_init__(self):
self.__do_checks()

def __do_checks(self):
assert len(self.position) == 3, "Position should be of dimension 3"
assert len(self.rpy) == 3, "RPY should be of dimension 3"

def __eq__(self, other: "RelativePose") -> bool:
return np.allclose(self.position, other.position) and np.allclose(self.rpy, other.rpy)

Expand All @@ -111,5 +94,5 @@ def __setattr__(self, __name: str, __value: np.ndarray | Tuple[float]) -> None:
f"Tried to assign value to illegal parameter {__name} in object of type "
f"{self.__class__.__name__}."
)
assert len(__value) == 3, f"{__name} should be of dimension 3"
super().__setattr__(__name, np.array(__value))
self.__do_checks()

0 comments on commit b03d95c

Please sign in to comment.