After finishing part 1, it’s time to extend our interpreter (just a little). Today we’ll add support to two more basic commands – PU and PD.
PU, or Pen Up is a command to, um.., put the pen up? After you issue a PU command, whatever you do will not produce a drawing. PD, or Pen Down command cancels PU and puts the pen down. Using these two, we can draw discontinuous drawings.
Implementing them is easy. We’ll have a new variable called
pen_upwhich will hold the current state of the pen. When we encounter a PU, we make it True, and when we encounter a PD, we make it false. And finally, we only draw a line if
pen_up is False.
So, make this global variable –
pen_up = False
Then at the top of the
elif block for the RT, add
elif comm == "PU": print("Pen up") pen_up = True continue elif comm == "PD": print("Pen down") pen_up = False continue
Move to where we are drawing the line, and move that under an if block –
if not pen_up: dwg.add(...)
However, this will not work.
The reason is that so far we’ve assumed that all our lines are in the format
command value but PU and PD don’t have values.
So we will put together a quick workaround. After reading a line we will try to split it, and then find the length of the resulting array. If it’s 2, we will initialize both
val otherwise, we’ll just initialize
line = line.strip().split(" ") comm ="" val = 0 if len(line) == 2: comm, val = line else: comm = line
Also, we have added a
strip() call to remove the newline at the end.
One more thing which I didn’t do in the previous post is to add
continueafter we handle LT and RT, so do that right away.
Here’s the complete code –
import svgwrite import sys from math import sin, cos, radians # direction_vector gives us current direction. # It is a unit vector direction_vector = complex(0,1) # current_post gives us the current position current_pos = complex(0,0) pen_up = False def parse(inp, out): global pen_up with open(inp) as f: lines =f.readlines() dwg = svgwrite.Drawing(out, (1000, 1000)) dwg.add(dwg.rect((0,0), (1000,1000), fill=svgwrite.rgb(255,255,255))) for line in lines: line = line.strip().split(" ") comm ="" val = 0 if len(line) == 2: comm, val = line else: comm = line val = int(val) prev_pos = current_pos if comm == "FD": go_to_point(-val) elif comm == "BK": go_to_point(+val) elif comm =="LT": rotate(radians(val)) continue elif comm == "RT": rotate(-radians(val)) continue elif comm == "PU": print("Pen up") pen_up = True continue elif comm == "PD": print("Pen down") pen_up = False continue else: print("Unknown command %s, ignoring"%comm) if not pen_up: print("Drawing") dwg.add(dwg.line((prev_pos.real + 500, 500 + prev_pos.imag), (current_pos.real + 500, 500 + current_pos.imag), stroke=svgwrite.rgb(255, 0, 0))) else: print("Not drawing") dwg.save() # Rotating any vector through an angle x is equivalent to # multiplying by cos x + i sin x # if x is negative, it results in a clockwise rotation (RT) # if x is positve, it results in an anticlockwise rotation (LT) def rotate(angle): global direction_vector direction_vector *= complex(round(cos(angle),5), -round(sin(angle),5)) print("Rotating to ", direction_vector) # To move the current position is as easy as # multiplying the unit vector with the distance and adding with current position def go_to_point(distance): global direction_vector, current_pos current_pos += direction_vector * distance print("Moving to: ", current_pos) inp = sys.argv out = sys.argv parse(inp, out)
Let’s try with the following input –
FD 100 RT 90 PU FD 100 RT 90 PD FD 100 RT 90 FD 100
We get this output –
And that’s it for today. In the next installment of this series, we will add error checking, and write a proper parser that doesn’t feel like a hack.
Share this post if you liked it, and subscribe to my blog for more tutorials