Skip to content

Attempt to update to modern PyMunk #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# PyMuscle Examples

This directory contains examples showing how to use the PyMuscle library.

## Requirements

The examples require additional dependencies beyond the core PyMuscle package. Install them with:

```bash
pip install -r requirements.txt
```

## PyMunk Arm Example

The `pymunk-gym-example.py` file demonstrates a simple arm simulation with a bicep and tricep muscle modeled using PyMuscle and physics simulated with PyMunk.

This example has been updated to work with recent versions of PyMunk (6.x). If you encounter orientation issues, the code has been modified to display the arm with the correct orientation based on the current coordinate system.

### Notes on API changes:

- PyMunk changed its API between versions, moving from `pymunk.constraint.DampedSpring` to `pymunk.DampedSpring`.
- The gravity direction and arm orientation have been adjusted to work with PyMunk 6.x.

## Minimal Physio Example

The `minimal-physio-example.py` shows basic usage of PyMuscle without a physics simulation.

## PyMunk Gym Example

The `pymunk-gym-example.py` file demonstrates how to use PyMuscle with the OpenAI Gym framework by simulating an arm curl exercise.
23 changes: 12 additions & 11 deletions examples/envs/pymunk_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def _init_sim(self):
self.draw_options = pymunk.pygame_util.DrawOptions(self.screen)
self.draw_options.flags = 1 # Disable constraint drawing
self.space = pymunk.Space()
self.space.gravity = (0.0, -980.0)
self.space.gravity = (0.0, 980.0) # Normal gravity for the flipped arm

def _add_arm(self):
config = {
Expand All @@ -54,15 +54,15 @@ def _add_arm(self):
"brach_stiffness": 450,
"brach_damping": 200,
"tricep_rest_length": 30,
"tricep_stiffness": 50,
"tricep_stiffness": 200,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Increasing the tricep stiffness seems like a reasonable adjustment given the coordinate changes. Is there a particular reason why the value was increased to 200?

"tricep_damping": 400
}

# Upper Arm
upper_arm_length = 200
upper_arm_body = pymunk.Body(body_type=pymunk.Body.STATIC)
upper_arm_body.position = config["arm_center"]
upper_arm_body.angle = np.deg2rad(-45)
upper_arm_body.angle = np.deg2rad(45)
upper_arm_line = pymunk.Segment(upper_arm_body, (0, 0), (-upper_arm_length, 0), 5)
upper_arm_line.sensor = True # Disable collision

Expand All @@ -72,7 +72,7 @@ def _add_arm(self):
# Lower Arm
lower_arm_body = pymunk.Body(0, 0) # Pymunk will calculate moment based on mass of attached shape
lower_arm_body.position = config["arm_center"]
lower_arm_body.angle = np.deg2rad(config["lower_arm_starting_angle"])
lower_arm_body.angle = np.deg2rad(-config["lower_arm_starting_angle"]) # Negative angle to flip
elbow_extension_length = 20
lower_arm_start = (-elbow_extension_length, 0)
lower_arm_line = pymunk.Segment(
Expand All @@ -88,9 +88,8 @@ def _add_arm(self):
self.space.add(lower_arm_line)

# Hand
hand_width = hand_height = 15
start_x = config["lower_arm_length"]
start_y = 14
start_y = -14 # Flipped to match new orientation
self.hand_shape = pymunk.Circle(
lower_arm_body,
20,
Expand All @@ -105,7 +104,7 @@ def _add_arm(self):
self.space.add(elbow_joint)

# Spring (Brachialis Muscle)
brach_spring = pymunk.constraint.DampedSpring(
brach_spring = pymunk.DampedSpring(
upper_arm_body,
lower_arm_body,
(-(upper_arm_length * (1 / 2)), 0), # Connect half way up the upper arm
Expand All @@ -117,7 +116,7 @@ def _add_arm(self):
self.space.add(brach_spring)

# Spring (Tricep Muscle)
tricep_spring = pymunk.constraint.DampedSpring(
tricep_spring = pymunk.DampedSpring(
upper_arm_body,
lower_arm_body,
(-(upper_arm_length * (3 / 4)), 0),
Expand All @@ -132,7 +131,7 @@ def _add_arm(self):
elbow_stop_point = pymunk.Circle(
upper_arm_body,
radius=5,
offset=(-elbow_extension_length, -3)
offset=(-elbow_extension_length, 3) # Flipped to match new orientation
)
elbow_stop_point.friction = 1.0
self.space.add(elbow_stop_point)
Expand Down Expand Up @@ -165,8 +164,10 @@ def step(self, input_array, step_size, debug=True):
brach_output = self.brach_muscle.step(input_array[0], step_size)
tricep_output = self.tricep_muscle.step(input_array[1], step_size)

# Use a moderate gain for balanced muscle power
gain = 500
self.brach.stiffness = brach_output * gain
# Apply muscle outputs directly to their respective springs
self.brach.stiffness = brach_output * gain
self.tricep.stiffness = tricep_output * gain

if debug:
Expand All @@ -179,7 +180,7 @@ def step(self, input_array, step_size, debug=True):
return hand_location

def render(self, debug=True):
if debug and (self.draw_options.flags is not 3):
if debug and (self.draw_options.flags != 3):
self.draw_options.flags = 3 # Enable constraint drawing

self.screen.fill((255, 255, 255))
Expand Down
20 changes: 11 additions & 9 deletions examples/pymunk-gym-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def main():
# vary the excitation of the bicep as we try to hit a target location.
brachialis_input = 0.4 # Percent of max input
tricep_input = 0.2
hand_target_y = 360
target_delta = 10
hand_target_y = 200
target_delta = -10 # Negative delta because lower y values are higher on screen
# Hand tuned PID params.
Kp = 0.0001
Ki = 0.00004
Expand All @@ -44,15 +44,16 @@ def main():
if prev_y is None:
prev_y = hand_y

# Proportional component
error = hand_target_y - hand_y
# Proportional component - INVERTED for flipped coordinate system
# In the flipped system, we need to DECREASE y to move UP
error = hand_y - hand_target_y # Inverted error calculation
alpha = Kp * error
# Add integral component
# Add integral component - signs adjusted for flipped coordinates
i_c = Ki * (error * step_size)
alpha -= i_c
# Add in differential component
d_c = Kd * ((hand_y - prev_y) / step_size)
alpha -= d_c
alpha += i_c # Sign changed from - to +
# Add in differential component - signs adjusted for flipped coordinates
d_c = Kd * ((prev_y - hand_y) / step_size) # Inverted velocity calculation
alpha += d_c # Sign changed from - to +

prev_y = hand_y
brachialis_input += alpha
Expand All @@ -65,6 +66,7 @@ def main():
# Vary our set point and display the excitation required
if i % frames_per_second == 0:
print(brachialis_input)
print(prev_y)
hand_target_y += target_delta

# Switch directions every 5 seconds
Expand Down
5 changes: 5 additions & 0 deletions examples/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pymunk==6.11.1
pygame==2.6.1
numpy==2.2.3
gym==0.26.2
gym-notices==0.0.8