diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..85d04f5 --- /dev/null +++ b/examples/README.md @@ -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. \ No newline at end of file diff --git a/examples/envs/pymunk_arm.py b/examples/envs/pymunk_arm.py index 09c7004..fc4646e 100644 --- a/examples/envs/pymunk_arm.py +++ b/examples/envs/pymunk_arm.py @@ -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 = { @@ -54,7 +54,7 @@ def _add_arm(self): "brach_stiffness": 450, "brach_damping": 200, "tricep_rest_length": 30, - "tricep_stiffness": 50, + "tricep_stiffness": 200, "tricep_damping": 400 } @@ -62,7 +62,7 @@ def _add_arm(self): 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 @@ -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( @@ -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, @@ -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 @@ -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), @@ -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) @@ -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: @@ -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)) diff --git a/examples/pymunk-gym-example.py b/examples/pymunk-gym-example.py index b618507..1fc3dd0 100644 --- a/examples/pymunk-gym-example.py +++ b/examples/pymunk-gym-example.py @@ -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 @@ -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 @@ -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 diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 0000000..cb393f8 --- /dev/null +++ b/examples/requirements.txt @@ -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 \ No newline at end of file