Saturday, February 5, 2011

More POV-ray and Pysanky

Continuing with the series of pysanky eggs I've been working on with Python and POV-ray - another week, another egg.  This one is a Lemko design.  The dots, I believe, represent stars.

Getting the crown and base was relatively easy, as it just required the correct positioning of a single stroke, then rotating it:

def makecrown(baseobj):
    crownstrokes = [baseobj]
    theta = STEP
    while theta < 360.0:
        crownstrokes.append(pov.Object(baseobj,
                            rotate = (0.0, theta, 0.0)))
        theta += STEP
    return pov.Union(*crownstrokes)

In this case, step = 360.0/24.0.

Likewise, I have a function for the dots that made them easier to place:

def movedot(basedot, xtheta, ytheta):
    dot = pov.Object(basedot, rotate = (xtheta, ytheta, 0.0))
    return pov.Object(dot, translate = (0.0, 1.0, 0.0))

Ultimately, I'd like to get enough egg designs coded to put together some sort of scene in POV-ray dedicated to the eggs.  The following two scenes show what I have so far:

If I can identify a few more attractive, computationally friendly designs, I should be able to come up with enough eggs for a decent POV-ray egg scene.  In the meantime, thanks for having a look.

Saturday, January 29, 2011

POV-ray -another Pysanky Egg

This is a continuation on a series of POV-ray posts all focused on the Pysanky decoration of egg shapes.  An attempt to represent the drop pull method of wax application to the egg is the subject of this post.  To represent this I've borrowed some work on flower shapes from a previous post and added a simple methodology for extending the flower in a simple V shape lengthwise:

def cycleequation(step, start, stop, trigfunc, factorr, factortheta, startend):
    """
    Returns list of x, y coordinates that correspond
    to the equation of the form

    factorr * r * r = (trigfunc(factortheta * theta)) ** 2
 
    theta will be incremented by step.
    start is the first value of theta.
    stop is the last value of theta.

    Shape starts and ends at startend, preferably a
    coordinate along the POV-ray z axis in the
    direction negative of the other points.
    """
    coords = [startend]
    theta = start
    while theta < stop:
        coords.append(getcoords(theta, factorr, factortheta, trigfunc))
        theta += step
    coords.append(getcoords(stop, factorr, factortheta, trigfunc))
    coords.append(startend)
    return coords

The head of the shape is formed by the equation 

r ** 2 = (math.cos(2.0 * theta)) ** 2


from math.pi/4.0 + 0.2 to 3 * math.pi/4.0 - 0.2

For a long drop pull shape, a start and end point of (0.0, -15.0) yields a shape like the one pictured below:



The head and the lengthwise extension are slightly slope discordant.  I did this intentionally to try to get the effect of the real life dropping of hot wax on the egg and pulling away from the drop to make the V shape.


With a parameter of (0.0, -5.0) for the start-stop point, a shorter, stubby drop is formed:






Through a series of scaling and rotation operations, I was able to get the drop shapes positioned to decorate my egg:







It takes 41 different strokes to make this egg (the way I approached it).  I fell into the temptation briefly of naming the strokes 'stroke1', 'stroke2', etc.  This is bad.  Descriptive names are good.  Even with a deceptively simple design like this, it's impossible to figure out what's going on and mirror the strokes that can be mirrored without descriptive names like 'northeastcenter', 'northwestcenter', etc.

Once I get a chance to rework the code, I can shorten the names and eliminate repetitive code.  This pattern recognition/code refactoring exercise should keep me busy for a bit.  In the meantime, thanks for having a look.

Saturday, January 15, 2011

jython and java.util.Properties

The other day my boss gave me an assignment to create a static properties file with stringified numeric mappings of file lookups.  Turns out this is really easy in jython (if you're used to Python instead of Java).  Hardly any Java is required:

$ /usr/local/jdk-1.7.0/bin/java -jar jython.jar
Jython 2.5.1 (Release_2_5_1:6813, Sep 26 2009, 13:47:54)
[OpenJDK Client VM (Sun Microsystems Inc.)] on java1.7.0-internal
Type "help", "copyright", "credits" or "license" for more information.
>>> from java.util import Properties
>>> filemappings = {'1':'numberone.py',
...                 '2':'numbertwo.py',
...                 '3':'numberthree.py'}
>>> props = Properties()
>>> for key in filemappings:
...     props.setProperty(key, filemappings[key])
...
>>> fle = open('blah', 'w')
>>> props.store(fle, 'put comment here')
>>> fle.close()
>>> fle = open('blah', 'r')
>>> props = Properties()
>>> props.load(fle)
>>> props.getProperty('1')
u'numberone.py'
>>> for prop in props:
...     print prop, props.getProperty(prop)
...
3 numberthree.py
2 numbertwo.py
1 numberone.py
>>> fle.close()
>>>





The file itself looks like this:



#put comment here
#Sat Jan 15 16:24:57 MST 2011
3=numberthree.py
2=numbertwo.py
1=numberone.py


The value strings are stored in a manner that has them returned as Jython Unicode strings.
My case is the simplest one, in that the properties file is static.  To update it dynamically and store it for the next use (for example, to maintain state), you would need a writable file object.

Sunday, January 9, 2011

Prisms and Trigonometric Shapes - POV-ray

I was looking for a way to put a four petal flower shape on my Python-POV-ray generated egg.  As it turns out, this shape is a common geometric one called Pascal's Rose.

For expediency rather than efficiency's sake, I opted to generate the shape using individual x, y points (actually x, z in POV-ray):


def getcoords(theta, factorr, factortheta, trigfunc):
    """
    Returns two tuple of x and y values for
    the equation in the form

    factorr * r * r = trigfunc(factortheta * theta)
    """
    r = math.sqrt((trigfunc(factortheta * theta)) ** 2 / factorr)
    x = r * math.cos(theta)
    y = r * math.sin(theta)
    if abs(x) < ZEROLIMIT:
        x = 0.0
    if abs(y) < ZEROLIMIT:
        y = 0.0
    print x, y, theta
    return x, y

def cycleequation(step, start, stop, trigfunc, factorr, factortheta):
    """
    Returns list of x, y coordinates that correspond
    to the equation of the form

    factorr * r * r = trigfunc(factortheta * theta)

    theta will be incremented by step.
    start is the first value of theta.
    stop is the last value of theta.
    """
    coords = []
    theta = start
    while theta < stop:
        coords.append(getcoords(theta, factorr, factortheta, trigfunc))
        theta += step
    coords.append(getcoords(stop, factorr, factortheta, trigfunc))
    return coords

math.cos creates Pascal's Rose for this equation.  I had to add a Prism object to the POV-ray recipe I am using.

class Prism(Item):
  def __init__(self, base, top, numpts, *opts, **kwargs):
    Item.__init__(self,"prism",(base, top, numpts),opts,**kwargs)

For me it was easiest to just translate the points of the prism into POV-ray SDL language prior to feeding them to Python:

def translatetopovray(pts):
    """
    Returns string in povray format of list
    of points pts.
    """
    stringpoints = []
    for pt in pts:
        stringpoints.append('<%.8f, %.8f>' % pt)
    return string.join(stringpoints, ',')

This allowed for generation of a prism:

This is just a rough representation of what I wanted.  The scene I was creating required a two colored flower, the outside as gold and the inside as orange.  Further, the outside edge needed to appear to be of constant width, as one would achieve by applying wax to an egg with the pysanky drawing tool, the kistka.

To get this effect I used a rough offset algorithm - get the centerpoint of the line segment on the outside edge of the "flower", offset a set distance along the normal to the outside line segment, then connect all the inner points.

def gettan(pt1, pt2):
    """
    Returns the tangent of the directional
    angle formed by line segment pt1-pt2.
    """
    return (pt2[1] - pt1[1])/(pt2[0] - pt1[0])

def getmidpoint(pt1, pt2):
    """
    Get the midpoint of the line segment
    pt1-pt2.
    """
    x = pt1[0] + (pt2[0] - pt1[0])/2.0
    y = pt1[1] + (pt2[1] - pt1[1])/2.0
    return x, y

def calcoffsetpoint(pt1, pt2, offset):
    """
    Get point offset distance offset
    from the midpoint of line segment
    pt1-pt2 to the left.
    """
    midpoint = getmidpoint(pt1, pt2)
    # get normal
    # quadrant I
    if pt2[0] >= pt1[0] and pt2[1] >= pt1[1]:
        theta = math.atan(gettan(pt1, pt2)) + math.pi/2.0
        x = midpoint[0] + math.cos(theta) * offset
        y = midpoint[1] + math.sin(theta) * offset
    # quadrant II
    elif pt2[0] <= pt1[0] and pt2[1] >= pt1[1]:
        theta = math.atan(gettan(pt1, pt2)) + math.pi/2.0
        x = midpoint[0] - math.cos(theta) * offset
        y = midpoint[1] - math.sin(theta) * offset
    # quadrant III
    elif pt2[0] <= pt1[0] and pt2[1] <= pt1[1]:
        theta = 3.0 * math.pi/2.0 + math.atan(gettan(pt1, pt2))
        x = midpoint[0] + math.cos(theta) * offset
        y = midpoint[1] + math.sin(theta) * offset
    # quadrant IV
    elif pt2[0] >= pt1[0] and pt2[1] <= pt1[1]:
        theta = 3.0 * math.pi/2.0 + math.atan(gettan(pt1, pt2))
        x = midpoint[0] - math.cos(theta) * offset
        y = midpoint[1] - math.sin(theta) * offset
    else:
        print pt1, pt2
        print 'error - theta undefined'
        theta = 0.0
    return x, y

def getmiddle(step, start, stop, trigfunc, factorr, factortheta, offset):
    """
    Return list of points for middle of 'petal'.
    """
    startend = (0.0, 0.0)
    pts = [startend]
    # need to offset by fixed distance along
    # normal to each line segment
    # need to get pts along original flower
    # but not near 0.0, 0.0 where they will
    # overlap
    outsidepts = cycleequation(step, start, stop,
        trigfunc, factorr, factortheta)
    counter = 0
    maxcounter = len(outsidepts)
    while (counter + 1) < (maxcounter - 1):
        pts.append(calcoffsetpoint(outsidepts[counter],
            outsidepts[counter + 1], offset))
        counter += 1
    pts.append(startend)
    return pts

My analytical geometry is a bit rough, but this works well enough for what I was trying to accomplish.

Through some intersecting and positioning, I was able to get the effect I wanted.  This egg design, according to the Sixty Score of Easter Eggs book by Elyjiw, is from the Odessa region of the Ukraine.


Thanks for having a look.



Sunday, December 26, 2010

POV-ray + Python - Scene Creation

Brief recap:  this recipe from the Python cookbook webpage is the basis for an api for POV-ray from Python.  I added an Object class to the api for more flexibility in manipulating pre-made POV-ray objects.

This final post in the POV-ray series deals with an attempt to create a scene.  I want to draw some pysanky eggs on a board with pegs in it for the eggs to stand on.

For the wood texture of the board I've used T_Wood10 from the POV-ray woods.inc library.  This requires the inclusion of the woods.inc library in the Python code:

file = pov.File('test5.pov', 'colors.inc', 'woods.inc')

test5.pov is my output file; colors.inc and woods.inc are my include files.

Because the api deals exclusively with text, the wood texture will have to be put in as a string literal complete with POV-ray's curly brackets:

testblock = pov.Box((-15, -6.50, -18), (15, -2.00, -3), 
    texture = '{T_Wood10}')

As for the scene itself:
This is good for illustrating the scene's design, but it's not very realistic.  The eggs look like they're wrapped in cellophane.  I tried introducing some focal blur:

cam = pov.Camera(location = (0, 5, -35), look_at = (0, 0, 2),
    focal_point = (0, 2, -10), aperture = 0.4, blur_samples = 20)

This looks more realistic, but blurry.  It would probably serve a remote corner of a scene better rather than the main object.

At this point it's really the nuances of POV-ray scene creation that will make a difference.  The purpose of this post and the last three was to show that you can do some fun things with POV-ray from Python.

Lastly, and just for fun, some wooden eggs (textures textures.inc/Cork, woods.inc T_Wood10 and T_Wood14):

Tuesday, December 21, 2010

Coding POV-ray in Python

My past two posts were basically on the same topic as this one.  The difference now is that I was able to write Python code as I would normally write it with functions, dictionaries, and loops and still come out with valid POV-ray code on the other end.  It's about a third of the length of the equivalent POV-ray code and, for me at least, easier to understand and read.

Here is the last part of the code that resulted in the scene of the three eggs below:

file = pov.File('test4.pov', 'colors.inc')
cam = pov.Camera(location = (-2, 3, -12), look_at = (0, 0, 2))
sorokolines = makesorokolines()testblock = makeredwedges()
testblockred = pov.Object(testblock, RED)
testblock = makeblackwedges()
testblockblack = pov.Object(testblock, BLACK)
test = pov.Union(testblockred, testblockblack, sorokolines)
test1 = pov.Object(test, translate = (2, 2, 0))
test2 = pov.Object(test, translate = (-2, -2, 0))
test3 = pov.Object(test, translate = (-6, 2, 0))
file.write(cam, test1, test2, test3, light1, light2, light3, light4, light5)

Now that I've proven to myself that I can code this in Python, I'd like to try something more involved like putting the eggs on a surface with a texture.

Thanks for having a look.

Monday, December 13, 2010

More POV-ray

Since last time I've had some success porting POV-ray code to Python code based on this recipe by Simon Burton.

To briefly recap, I'm trying to reproduce this egg with Python code:

Since my first attempt, this is how far I've gotten:
Basically, the lines; I've made them a bit thicker to emphasize them.  I'm still working on getting the camera and look at values set to coincide with the main axes.

The pictures are all well and good, but what's pretty exciting is that I made the second one using the API from the recipe.  Further, I was able to expand on the recipe by adding an Object class:

class Object(Item):
  def __init__(self, *opts, **kwargs):
    Item.__init__(self, "object", (), opts, **kwargs)

This was really simple; I just copied what Simon Burton had done with the other classes.  Still, it opened up a lot of possibilities for twirling, flipping, and coloring elements once they're constructed.  For example, the code for the dividing lines above is (I've skipped the egg shape code for brevity):

white = pov.Texture(pov.Pigment(color = (1.0, 1.0, 1.0)),
    pov.Finish(phong = redphng, reflection = redrflct))
horizontaldividingline = pov.Box((-3, 2.15, -3), (3, 2.25, 3), white)
verticaldividingline = pov.Box((-0.05, -1, -3), (0.05, 7, 3), white)

# vectors for dividing lines
# down on y axis
movedown = (0, -2.2, 0)
# up on y axis
mvup = (0, 2.2, 0)
# flip right around z axis
flprt = (0, 0, -52.5)

# scale - same in all directions (2)
scal = (2, 2, 2)

# turn for display
trn15back = (0, -15, 0)

planes = [horizontaldividingline, verticaldividingline]

# vertical planes
EIGHTHTURN = 45
turn = 45
for counter in range(3):
    plane = pov.Object(verticaldividingline, rotate = (0, turn, 0))
    planes.append(plane)
    turn += EIGHTHTURN

# for dividing lines at angle to horizontal
QUARTERTURN = 90
flprtdivlinepre = pov.Object(horizontaldividingline,
    translate = movedown, rotate = flprt)
flprtdivline = pov.Object(flprtdivlinepre, translate = mvup)

planes.append(flprtdivline)
turn = 90
for counter in range(3):
    plane = pov.Object(flprtdivline, rotate = (0, turn, 0))
    planes.append(plane)
    turn += QUARTERTURN

sorokoplanes = pov.Union(*planes)

eggwhite = pov.Object(unionegg, white, scale = scal)

sorokolines = pov.Intersection(eggwhite, sorokoplanes)

sorokotest = pov.Object(sorokolines, translate = (0, -2.75, 0), rotate = trn15back)

The egg design is very symmetrical and lends itself to repetition.  I tried to use this to my advantage with the two loops.  The list unpacking also compresses the code a bit.

Next on the agenda is placing code in functions and classes.  This would allow for making multiple eggs of different colors with a single code call.