Posts Tagged ‘ Python

pyglet: First steps


I had to give my brain a break from all the Processing \ Android stuff and get back into Python.  I’ve done a few small apps with PyGame and it’s been an enjoyable process.  But PyGame is SDL based, and because of that never felt as ‘Pythonic’ to me as I’d like.  Plus, and this is a minor gripe, but it really rubs me the wrong way:  There seems to be no way to easily anti-alias PyGame programs.  Without that, to me, they end up looking just a bit too indy for my taste.

I thought I’d go about learning a new Python game development framework, and the one I settled on is pyglet.

Reasons I like pyglet?

  • Authored in Python with no external dependencies (other than OpenGL).  It ‘feels moar Python’.
  • It’s mainly a big fancy wrapper around OpenGL (which I want to learn).
  • I like it’s event system.
  • You can anti-alias it! :)
  • It has an even higher-level wrapper API, cocos2d (which I’ll get into later), which adds even more game-framework related abilities.

Things that bum me out about it:

  • Unless I’m missing it, has no built-in primitives library.  If you want to draw a line, or a filled circle, you gotta roll your own.  Yes, it has a ‘pyglet.graphics’ library (here, here), but it’s not exactly plug-n-play.
  • The API, while great in some areas, doesn’t have all the convenience functions I’m used to with something like Processing, or even PyGame.  But this means I get to learn how to do it, which isn’t a bad thing.
  • There is no sprite\rect-based collision systems (unlike PyGame).

So, my pyglet beginnings are below.  I tried to come up with a basic window displaying primitives, anti-aliased.  I found a great primitives library that someone already authored so that filled in my primitives hole.  And I wrote my own ‘utils’ module as I learned stuff, slowly filling in the missing pieces I think I’ll need in the future.

Links:

Here is my ‘utilities’ module:

# pyglet.utils.py
# www.akeric.com - 2011-03-17
# utils to make pyglet easier to work with, help my learning of it.

import pyglet
from pyglet.gl import *

def screenshot(name='screenshot'):
    """
    Take a screenshot

    Parameters:
    name : string : Default 'screenshot'.  Name of the saved image.  Will
        always save as .png
    """
    # Get the 'the back-left color buffer'
    pyglet.image.get_buffer_manager().get_color_buffer().save('%s.png'%name)

def getPixelValue(x, y):
    """
    Return the RGBA 0-255 color value of the pixel at the x,y position.
    """
    # BufferManager, ColorBufferImage
    color_buffer = pyglet.image.get_buffer_manager().get_color_buffer()
    # AbstractImage, ImageData, sequece of bytes
    pix = color_buffer.get_region(x,y,1,1).get_image_data().get_data("RGBA", 4)
    return pix[0], pix[1], pix[2], pix[3]

def drawPoint(x, y, color):
    """
    Based on the (r,g,b) color passed in, draw a point at the given x,y coord.
    """
    pyglet.graphics.draw(1, GL_POINTS,
                         ('v2i', (x, y)),
                         ('c3B', (color[0], color[1], color[2]) ) )

def getSmoothConfig():
    """
    Sets up a configuration that allows of smoothing\antialiasing.
    The return of this is passed to the config parameter of the created window.
    """
    try:
        # Try and create a window config with multisampling (antialiasing)
        config = Config(sample_buffers=1, samples=4,
                        depth_size=16, double_buffer=True)
    except pyglet.window.NoSuchConfigException:
        print "Smooth contex could not be aquiried."
        config = None
    return config

def printEvents(window):
    """
    Debug tool that will print the events to the console.

    window is an instance of a Window object receiving the events.
    """
    window.push_handlers(pyglet.window.event.WindowEventLogger())

def playMusic(music):
    """
    Simple wrapper to play a music (mp3) file.

    music : music file relative to application.
    """
    music = pyglet.resource.media(music)
    music.play()

def setBackgroundColor(color):
    """
    Color is a list of four values, [r,g,b,a], each from 0 -> 1
    """
    pyglet.gl.glClearColor(*color)

And here is my ‘basic window’ which draws primitives. It is a dupe of the primitives drawing example from the primitives.py module, but modified into a new window:

# primitivesTest01.py
# www.akeric.com - 2011-03-17

import sys
import random
import pyglet
from pyglet.gl import *
import primitives # module discussed above
import utils # module from above

FPS = 60
smoothConfig = utils.getSmoothConfig()

class PrimWin(pyglet.window.Window):

    def __init__(self):
        super(PrimWin, self).__init__(fullscreen=False, caption='Primitives Test!', config=smoothConfig)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.p = primitives.Pixel(10,10)
        self.c = primitives.Circle(200,100,width=100,color=(0.,.9,0.,1.))
        self.a = primitives.Arc(150,150,radius=100,color=(1.,0.,0.,1.),sweep=90,style=GLU_FILL)
        self.P = primitives.Polygon([(0, 0), (50, 200), (80, 200),(60,100),(100,5)],color=(.3,0.2,0.5,.7))
        self.l = primitives.Line((10,299),(100,25),stroke=8,color=(0,0.,1.,1.))
        # Setup debug framerate display:
        self.fps_display = pyglet.clock.ClockDisplay()
        # Schedule the update of this window, so it will advance in time at the
        # defined framerate.  If we don't, the window will only update on events
        # like mouse motion.
        pyglet.clock.schedule_interval(self.update, 1.0/FPS)

    def on_draw(self):
        # Window event
        self.clear()
        self.c.render()
        self.p.render()
        self.a.render()
        self.P.render()
        self.l.render()
        self.fps_display.draw()

    def on_mouse_motion(self, x, y, dx, dy):
        # Window event
        self.c.x = x
        self.c.y = y

    def update(self, dt):
        # Scheduled event
        self.a.rotation+=1
        self.c.color = [random.random() for i in xrange(3)]+[1]

if __name__ == '__main__':
    PrimWin()
    sys.exit(pyglet.app.run())

And the result of this awesome code is the screenshot at the top of the screen.

Next steps will be to dig more into cocos2d and see if I can come up with something more interesting than the above screenshot to develop.

Arduino to Maya Communication via Python

Arduino + light sensor talking to a sphere in Maya

I’ve wanted for some time to get my Arduino to talk with my 3d application of choice, Maya.  There were a few hurdles to overcome first:

  1. The Arduino communicates to the computer over the serial port (easily, by default).
  2. Maya (to my knowledge), has no built-in serial communication.  If you can find a built in mel command or API call, let me know 😉
  3. Maya comes with its own version of the Python scripting language (2.6ish)
  4. Python (external to Maya) has it’s own (separately installed)  pySerial module.  BUT:
    1. pySerial is a 32-bit app  (no 64-bit build I can find, and I’m not smart enough to recompile it to 64-bit).
    2. I’m running a 64-bit version of Maya, with a 64-bit version of Python = I can’t use pySerial in Maya :-(

Maya can however receive incoming communication over a commandPort, which you can ‘pretend’ is a serial port.  Below I’ll describe how to do that.  A bunch of these steps I’d already discussed in previous blog posts here or on my mel wiki, which I’ll list individually first:

Overview of the process:

  1. Author Arduino code to read sensor data and send it over the serial port.
  2. Author Python code in Maya (Maya’s version of 64-bit Python 2.6) to setup a commandPort, and to do ‘something’ with the incoming data.
  3. Author Python code external to Maya (in 32-bit Python 2.6) to receive the Arduino serial data (using pySerial), and broadcast it to the open Maya commandPort.

Notes about the commandPort code below:

  • I’ve hard-coded the commandPort data below to the address of  “127.0.0.1:7777”.
  • 127.0.0.1 is the localHost address of your machine, you shouldn’t change that.  But the “7777” is an arbitrary port number I made up, feel free to change it.
  • The important thing is that it must be consistent in all the places referenced in the code below.

Step 1:  Author Arduino Code:

This is a very simple sketch showing you the bare-bones requirements. In my case, I’ve hooked up an analog sensor to pin 5 (in my case, it’s a light sensor).
Upload this to the Arduino and start broadcasting!  You can open the Arduino Serial Monitor to see what it’s up to, but be sure to close the monitor before you do any other work.

/**
SerialSend01
Eric Pavey 2011-01-26

Sketch will print the value from the sensor to the serial port.
Sensor is plugged into analog pin 5
*/

#define SENSOR 5
int val = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  val = analogRead(SENSOR);
  // Print to the serial port:
  Serial.println(val);
  delay(100);
}

Step 2: Author Python code in Maya:

The below code could eventually be made into a well-packaged Maya-Python module, but for the time being you can just paste it into the Script Editor for execution.  Thing to note:

  • The Maya commandPort command has a ‘prefix’ parameter that takes the name of a mel procedure (not Python function) that can intercept the incoming data.  Because of this, we use Python to create a simple wrapper mel procedure that in turn calls to the Python function that does the work.
  • In the below example, I have the Python function query for the existence of a ‘pSphere1’ object in Maya, and if it finds it, will scale it based on the passed in Arduino sensor values.  This should obviously be changed to something way more cool for what you’re doing :-)
# Eric Pavey - 2011-01-26
# In Maya, via Python:
import maya.cmds as mc
import maya.mel as mm

# Our mel global proc.
melproc = """
global proc portData(string $arg){
    python(("portData(\\"" + $arg + "\\")"));
}
"""
mm.eval(melproc)

# Our Python function that can be changed to do whatever we want:
def portData(arg):
    """
    Read the 'serial' data passed in from the commandPort
    """
    print "Recieved!: ", arg

    # Some silly example code to scale a sphere:
    mappedVal = (float(arg)/1023.0) * 10
    if mc.objExists('pSphere1'):
        mc.setAttr('pSphere1.scale', mappedVal, mappedVal, mappedVal)

# Open the commandPort.  The 'prefix' argument string is calling to the defined
# mel script above (which then calls to our Python function of the same name):
mc.commandPort(name="127.0.0.1:7777", echoOutput=False, noreturn=False,
               prefix="portData", returnNumCommands=True)
mc.commandPort(name=":7777", echoOutput=False, noreturn=False,
               prefix="portData", returnNumCommands=True)

Step 3: Author Python module to receive Arduino serial data and broadcast to Maya:

You must make sure you’ve previously downloaded and installed pySerial to a place where your system-installed Python can see it.  And it goes without saying you also need to have Python 2.6 installed as well.

This code will intercept the Arduino serial data, and send it to Maya over the commandPort.  It will only send new data, meaning, it will only send data that has changed from the previous data received.

# arduinoRead.py
# Python module external to Maya
# Eric Pavey - 2011-01-26

import socket
import serial

ARDUINO =  "COM3"

def main():
    maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    maya.connect(("127.0.0.1", 7777))
    ser =  serial.Serial(ARDUINO, timeout=1)
    prevVal = None
    while 1:
        # Read the serial value
        ser.flushInput()
        serialValue = ser.readline().strip()
        # Catch any bad serial data:
        try:
            if serialValue != prevVal:
                # Print the value if it differs from the prevVal:
                maya.send(str(serialValue))
                prevVal = serialValue
        except ValueError:
            pass

if __name__ == '__main__':
    main()

Save the above code as a Python module, and then execute it from a shell:

c:\pythonStuff\python arduinoRead.py

This should form the link between the broadcasting Arduino, and the listening Maya session, converting from serial data to data sent over Maya’s commandPort.

Result:

In Maya, make a sphere, and rename it to ‘pSphere1’ if it’s not already.  If all the code is working, you should see the script editor print the received Arduino data whenever it changes, and you should see the scale of your sphere be effected by that data.  Magic!

Speech 2 Text 2 Speech

Hot off the heels of my previous post on getting Python scripting on my Android phone via the “Scripting Layer For Android“, I wrote my first (silly) Python module directly on the phone:  I’ve coined it ‘Speech 2 Text 2 Speech‘:  You speak into the phone, it converts it to text, then says it back.  And, it’s sooo easy to do:

# speech2text2speech.py
import android
droid = android.Android()
speech = droid.recognizeSpeech("Talk Now", None, None)
print speech[1]
droid.ttsSpeak(speech[1])

Sometimes the “Talk Now” window pops up too fast and you have to reset it, but when it works it’s pretty funny to hear what you said repeated back in ‘Androidish’ 😉

Android Adventures

Go to part 2…

I finally got a smartphone:  Samsung Captivate.  Why?  Mainly since the bar for making apps on them seems pretty low, which is something I’ve wanted to try for some time.  Plus after seeing these two posts…

And their reference to Processing For Android

…and considering my love for Processing
…it just all seemed to make sense.

The first thing I wanted to do though had nothing to do with Processing:  I wanted to get the ‘Scripting Layer For Android‘ (SL4A) installed on it so I could run Python (which I love more than Processing), in a shell, on my phone.  I documented how to do this on the Android Emulator in a previous post (and waxed lyrical on how I’d like to program on an Android phone in a post before that). However, to get all this working on a real phone, I had to jump through some hoops.

Disclaimer:    I’m a total smartphone\Android noob, and this is the first time I’ve ever written words like  ‘sideload’ or ‘root’.  I did this a day after getting the phone, never having owned a smartphone before.  So for most of  it I was just fumbling in the dark.  There may be easier ways, but I had a terrible time finding any kind of documentation on anything.  Any documentation I did use is linked to below.

A.  Time To Root:

AT&T in its infinite wisdom has disallowed ‘sideloading’ of apps onto the phone:  You can’t copy something onto the internal SD card and ‘install’.  If you try, you get a nice “You can only install apps from the Marketplace” window pop up.  Meaning, I can’t install SL4A  (or if you can, I just went through a lot more steps than needed…).   Talking with my buddies who’s smartphone kung-fu is much more powerful than my own, they told me to look into ‘rooting’ the phone:  Give me ‘root access’ on the phone I just bought (shouldn’t everyone have that?  I must have missed the politics class on that one).  This will allow me to be able to ‘sideload’ apps.

After a lot of websearching, I came across this form post, which describes how to install the “Unleash the Beast” root tool.  It seemed to do everything I needed it to in one step:  Clean off all the bloatware that came with the phone, disable the annoying on\off sound fx, plus a bunch of other stuff.  Most importantly though it gives you root access and enable sideloading.  I followed the directions on the site, and it worked flawlessly.  Not to say it didn’t stress me out during the process :)

Excellent, root complete!

B.  What happened to my USB?

Loooong story short:  When I first got the phone and before the root, my XP laptop wouldn’t detect it.  Went online to the Samsung web site to get drivers:  Site claimed it had drivers, but when I went to the download page:  “This product has no downloads”.  I contacted customer support explaining the issue, and their reply was to do the exact thing I had just done.  Robot.  More websearching:  Based on this post, I got a link to the drivers, and installed:  the  machine could now see my phone.  Win.

After the root, when I connected phone to computer, the computer again failed to detect it.  Tried plugging it into a different machine:  Same result.   The phone came with no directions, and the web page for downloads claiming it had the manual didn’t work (like mentioned above).  More websearching:

  • Settings -> Applications -> USB Settings -> Set to “Ask on connection” (was ‘Kies’).

Now when I plug the phone into the computer, the phone asks me ‘how it should be connected’:  Set to “Mass storage” (not Kies).  Then access the notifications menu (that one that scrolls down from the top), click on the “USB connected” item, and then “Mount”.  Suddenly the computer detects it.  What a pain…

C.  Get Astro File Manager

Part of  executing “Unleash The Beast” was the removal of the built-in “My Files” app.  To install the new tools I’d be getting I needed a new file manager:  Recommended in several places was “Astro File Manager“.  Successfully installed from the marketplace.

D.  Install SL4A with Python:

From the Android Scripting download page, I downloaded both sl4a_r2.apk and python_for_android_r1.apk.  After connecting the phone to computer and jumping through all the USB hoops, I copied them both to the \Android dir (I have no idea if that’s the right place).  Unmounting the the usb drive from the phone’s menu, I then accessed Astro:  Browsed to the /sdcard/Android dir, selected ‘sl4a_r2’ -> Open App Manager -> Install.  I then repeated this process on ‘python_for_android_r1’.  While python was installing, it also downloaded and installed all the other Python .zip files found in the SL4A download page as well.

When it was complete, I had the nice ‘SL4A’ app  ready for usage

Note: In hind-site I found the official doc for “Installing Interpreters” which saves you from having to install the Python shell by hand.  Main problem is the menu for his app on the phone isn’t that intuitive, and I totally missed how to do it the first time around.

Update: A buddy of mine did this, his install was even easier:  On his phone he simply browsed to the SL4A page, and clicked on the bar-code:  It installed automatically.  Magic.

E.  Do something with it:

Launch it ->  Settings Menu -> View -> Interpreters -> Python 2.6.2 ->  the shell opens:

>>> import this

Continue to Part 2

Basic Pygame Program

Whenever I begin a new Pygame application, I always start with a ‘basic program’ to begin things. Saves me from having to re-type all the simple stuff. Obviously a Pygame program can get far more complex than this, but everything has to start somewhere. Thought I’d post it up for reference.

"""
default.py
www.akeric.com - 2010-09-07
Default, base, initial setup for a pygame program.
In this case, black background, white circle follows mouse position, and
framerate is shown in the title-bar.
"""

#-------------------------------------------------------------------------------
# Imports & Inits
import sys
import pygame
from pygame.locals import *
pygame.init()

#-------------------------------------------------------------------------------
# Constants
VERSION = '1.0'
WIDTH = 512
HEIGHT = 512
FRAMERATE = 60

#-------------------------------------------------------------------------------
# Screen Setup
screen = pygame.display.set_mode((WIDTH, HEIGHT))
bgCol = Color('black')
clock = pygame.time.Clock()
moduleName = __file__.split('\\')[-1]

#-------------------------------------------------------------------------------
# Define helper functions, classes, etc...
def spam():
    pos = pygame.mouse.get_pos()
    pygame.draw.circle(screen, Color('white'), pos, 32)

#-------------------------------------------------------------------------------
# Main Program
def main():
    print "Running Python version: %s"%sys.version
    print "Running PyGame version: %s"%pygame.ver
    print "Running %s version: %s"%(moduleName, VERSION)
    looping = True

    # Main Loop-----------------------
    while looping:
        # Maintain our framerate, set caption, clear background:
        clock.tick(FRAMERATE)
        pygame.display.set_caption("%s - FPS: %.2f" %(moduleName,clock.get_fps()) )
        screen.fill(bgCol)

        # Detect for events-----------
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                looping = False
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    looping = False

        # Do stuff!-------------------
        spam()

        # Update our display:---------
        pygame.display.flip()

#-------------------------------------------------------------------------------
# Execution from shell\icon:
if __name__ == "__main__":
    # Make running from IDE work better:
    sys.exit(main())