Search code examples
pythonchipmunkpymunk

How to rotate pymunk joints at will?


I'm trying to create a walking spider like this:
enter image description here

I considered using a SimpleMotor at the pink and red joints and control them using the rate function. But when I tried, I get an error that the function is not callable.

self.motorJoint1.rate(0.0) TypeError: 'float' object is not callable

I don't see any other functions in the pymunk API that allow controlling the joints at will. Is there really no function or am I missing something?

Basically in the run loop I want to specify rotations to the joints at certain points of time, to not just make the spider walk, but to eventually be able to use Neural Networks to allow it to experiment with various configurations of leg positions and figure out which ones can make it walk:

angle1 = 30
angle2 = 10
redJoint1.rotate(angle1)
pinkJoint2.rotate(angle2)
if angle1 < 50:
    angle1 = angle1 + 1

Is it possible at all to achieve such a level of control over joints using Pymunk? To be able to stop moving the legs (without needing to put the body to sleep), or to rotate the leg joints to whatever angle the spider 'wishes to' at any point in time? Sample code would be a great help.


Solution

  • From the servo example I took a hint and implemented this basic leg:

    enter image description here

    import sys
    
    import pygame
    from pygame.locals import USEREVENT, QUIT, KEYDOWN, KEYUP, K_s, K_r, K_q, K_ESCAPE, K_UP, K_DOWN, K_RIGHT, K_LEFT
    from pygame.color import THECOLORS
    
    import pymunk
    from pymunk import Vec2d
    import pymunk.pygame_util
    
    class Simulator(object):
    
        def __init__(self):
            self.display_flags = 0
            self.display_size = (600, 600)
    
            self.space = pymunk.Space()
            self.space.gravity = (0.0, -1900.0)
            #self.space.damping = 0.999 # to prevent it from blowing up.
    
            # Pymunk physics coordinates start from the lower right-hand corner of the screen.
            self.ground_y = 100
            ground = pymunk.Segment(self.space.static_body, (5, self.ground_y), (595, self.ground_y), 1.0)
            ground.friction = 1.0
            self.space.add(ground)
    
            self.screen = None
    
            self.draw_options = None
    
        def reset_bodies(self):
            for body in self.space.bodies:
                if not hasattr(body, 'start_position'):
                    continue
                body.position = Vec2d(body.start_position)
                body.force = 0, 0
                body.torque = 0
                body.velocity = 0, 0
                body.angular_velocity = 0
                body.angle = body.start_angle
    
        def draw(self):        
            self.screen.fill(THECOLORS["white"])### Clear the screen        
            self.space.debug_draw(self.draw_options)### Draw space        
            pygame.display.flip()### All done, lets flip the display
    
        def main(self):
            pygame.init()
            self.screen = pygame.display.set_mode(self.display_size, self.display_flags)
            width, height = self.screen.get_size()
            self.draw_options = pymunk.pygame_util.DrawOptions(self.screen)
    
            def to_pygame(p):            
                return int(p.x), int(-p.y+height) #Small hack to convert pymunk to pygame coordinates
            def from_pygame(p):
                return to_pygame(p)
    
            clock = pygame.time.Clock()
            running = True
            font = pygame.font.Font(None, 16)
    
            # Create the spider
            chassisXY = Vec2d(self.display_size[0]/2, self.ground_y+100)
            chWd = 70; chHt = 50
            chassisMass = 10
            
            legWd_a = 50; legHt_a = 5
            legWd_b = 100; legHt_b = 5
            legMass = 1
            relativeAnguVel = 0
    
            #---chassis
            chassis_b = pymunk.Body(chassisMass, pymunk.moment_for_box(chassisMass, (chWd, chHt)))
            chassis_b.position = chassisXY
            chassis_shape = pymunk.Poly.create_box(chassis_b, (chWd, chHt))
            chassis_shape.color = 200, 200, 200, 100
            print("chassis position");print(chassis_b.position)
    
            #---first left leg a
            leftLeg_1a_body = pymunk.Body(legMass, pymunk.moment_for_box(legMass, (legWd_a, legHt_a)))
            leftLeg_1a_body.position = chassisXY - ((chWd/2)+(legWd_a/2), 0)
            leftLeg_1a_shape = pymunk.Poly.create_box(leftLeg_1a_body, (legWd_a, legHt_a))        
            leftLeg_1a_shape.color = 255, 0, 0, 100
            
            #---first left leg b
            leftLeg_1b_body = pymunk.Body(legMass, pymunk.moment_for_box(legMass, (legWd_b, legHt_b)))
            leftLeg_1b_body.position = leftLeg_1a_body.position - ((legWd_a/2)+(legWd_b/2), 0)
            leftLeg_1b_shape = pymunk.Poly.create_box(leftLeg_1b_body, (legWd_b, legHt_b))        
            leftLeg_1b_shape.color = 0, 255, 0, 100        
            
            #---first right leg a
            rightLeg_1a_body = pymunk.Body(legMass, pymunk.moment_for_box(legMass, (legWd_a, legHt_a)))
            rightLeg_1a_body.position = chassisXY + ((chWd/2)+(legWd_a/2), 0)
            rightLeg_1a_shape = pymunk.Poly.create_box(rightLeg_1a_body, (legWd_a, legHt_a))        
            rightLeg_1a_shape.color = 255, 0, 0, 100        
            
            #---first right leg b
            rightLeg_1b_body = pymunk.Body(legMass, pymunk.moment_for_box(legMass, (legWd_b, legHt_b)))
            rightLeg_1b_body.position = rightLeg_1a_body.position + ((legWd_a/2)+(legWd_b/2), 0)
            rightLeg_1b_shape = pymunk.Poly.create_box(rightLeg_1b_body, (legWd_b, legHt_b))        
            rightLeg_1b_shape.color = 0, 255, 0, 100     
            
            #---link left leg b with left leg a       
            pj_ba1left = pymunk.PinJoint(leftLeg_1b_body, leftLeg_1a_body, (legWd_b/2,0), (-legWd_a/2,0))#anchor point coordinates are wrt the body; not the space
            motor_ba1Left = pymunk.SimpleMotor(leftLeg_1b_body, leftLeg_1a_body, relativeAnguVel)
            #---link left leg a with chassis
            pj_ac1left = pymunk.PinJoint(leftLeg_1a_body, chassis_b, (legWd_a/2,0), (-chWd/2, 0))
            motor_ac1Left = pymunk.SimpleMotor(leftLeg_1a_body, chassis_b, relativeAnguVel)
            #---link right leg b with right leg a       
            pj_ba1Right = pymunk.PinJoint(rightLeg_1b_body, rightLeg_1a_body, (-legWd_b/2,0), (legWd_a/2,0))#anchor point coordinates are wrt the body; not the space
            motor_ba1Right = pymunk.SimpleMotor(rightLeg_1b_body, rightLeg_1a_body, relativeAnguVel)
            #---link right leg a with chassis
            pj_ac1Right = pymunk.PinJoint(rightLeg_1a_body, chassis_b, (-legWd_a/2,0), (chWd/2, 0))
            motor_ac1Right = pymunk.SimpleMotor(rightLeg_1a_body, chassis_b, relativeAnguVel)              
            
            self.space.add(chassis_b, chassis_shape) 
            self.space.add(leftLeg_1a_body, leftLeg_1a_shape, rightLeg_1a_body, rightLeg_1a_shape) 
            self.space.add(leftLeg_1b_body, leftLeg_1b_shape, rightLeg_1b_body, rightLeg_1b_shape) 
            self.space.add(pj_ba1left, motor_ba1Left, pj_ac1left, motor_ac1Left)  
            self.space.add(pj_ba1Right, motor_ba1Right, pj_ac1Right, motor_ac1Right)      
            
            #---prevent collisions with ShapeFilter
            shape_filter = pymunk.ShapeFilter(group=1)
            chassis_shape.filter = shape_filter
            leftLeg_1a_shape.filter = shape_filter
            rightLeg_1a_shape.filter = shape_filter
            leftLeg_1b_shape.filter = shape_filter
            rightLeg_1b_shape.filter = shape_filter        
                        
    
            simulate = False
            rotationRate = 2
            while running:
                for event in pygame.event.get():
                    if event.type == QUIT or (event.type == KEYDOWN and event.key in (K_q, K_ESCAPE)):
                        #running = False
                        sys.exit(0)
                    elif event.type == KEYDOWN and event.key == K_s:
                        # Start/stop simulation.
                        simulate = not simulate
                    elif event.type == KEYDOWN and event.key == K_r:
                        # Reset.
                        # simulate = False
                        self.reset_bodies()
                    elif event.type == KEYDOWN and event.key == K_UP:
                        motor_ba1Left.rate = rotationRate
                    elif event.type == KEYDOWN and event.key == K_DOWN:
                        motor_ba1Left.rate = -rotationRate
                    elif event.type == KEYDOWN and event.key == K_LEFT:
                        motor_ac1Left.rate = rotationRate
                    elif event.type == KEYDOWN and event.key == K_RIGHT:
                        motor_ac1Left.rate = -rotationRate                    
                    elif event.type == KEYUP:
                        motor_ba1Left.rate = 0
                        motor_ac1Left.rate = 0
    
                self.draw()
    
                ### Update physics
                fps = 50
                iterations = 25
                dt = 1.0/float(fps)/float(iterations)
                if simulate:
                    for x in range(iterations): # 10 iterations to get a more stable simulation
                        self.space.step(dt)
    
                pygame.display.flip()
                clock.tick(fps)
    
    if __name__ == '__main__':
        sim = Simulator()
        sim.main()
    

    It can be controlled with the up, left, right and down arrow keys after first pressing the s key to start the simulation. I've also made sure the variables are created properly linked with each other and named well.

    The part about making the joints move to a desired angle is yet to be implemented, but perhaps that could be calculated by taking the x,y positions of the ends of the joints and using a formula to calculate the angle and then move the motor until it reaches a desired angle.

    If there's a better way, do let me know by posting an answer or editing this one.