Advertisement
ijontichy

plotterlol

Aug 31st, 2011
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.78 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. from Tkinter import *
  4. import math
  5. from collections import Iterable
  6.  
  7.  
  8. def flatten(x):
  9.     for i in x:
  10.         if isinstance(i, Iterable):
  11.             yield flatten(i)
  12.         else:
  13.             yield i
  14.  
  15.  
  16. class PlotError    (Exception):   pass
  17. class NoLastError  (PlotError):   pass
  18. class NoPlotError  (PlotError):   pass
  19. class NoLineError  (NoPlotError): pass
  20. class NoCircleError(NoPlotError): pass
  21.  
  22.  
  23.  
  24. class Vector2(object):
  25.     """A simple vector class for use in the plotter."""
  26.     def __init__(self, x1, y1, x2, y2):
  27.         self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
  28.  
  29.     @property
  30.     def coord(self):
  31.         return ((self.x1, self.y1), (self.x2, self.y2))
  32.  
  33.     def setCoord(self, coord1, coord2):
  34.         if (not isinstance(coord1, tuple)) or (not isinstance(coord2, tuple)):
  35.             raise ValueError("not coordinates")
  36.  
  37.         if (len(coord1) != 2) or (len(coord2) != 2):
  38.             raise ValueError("proper coordinates not specified (invalid length)")
  39.  
  40.         try:
  41.             for i in (coord1 + coord2):
  42.                     i = int(i)
  43.         except ValueError:
  44.             raise ValueError("proper coordinates not specified (non-integer)")
  45.  
  46.         self.x1, self.y1, self.x2, self.y2 = coord1 + coord2
  47.  
  48.     @property
  49.     def velocity(self):
  50.         return (self.x2-self.x1, self.y2-self.y1)
  51.  
  52.     @property
  53.     def center(self):
  54.         vx, vy = [i/2.0 for i in self.velocity]
  55.         return (self.x1+vx, self.y1+vy)
  56.  
  57.     @property
  58.     def signs(self):
  59.         return [(1 if i >= 0 else -1) for i in self.center]
  60.  
  61.     @property
  62.     def magnitude(self):
  63.         vel = self.velocity
  64.         return ((vel[0]**2)+(vel[1]**2))**0.5  # it's pythagorean
  65.  
  66.     @property
  67.     def norm(self):
  68.         vel = self.velocity
  69.         mag = self.magnitude
  70.         return tuple(i / mag for i in vel)
  71.  
  72.     def __add__(self, v2):  # with one of the uglier list comprehensions ever
  73.         x1, y1, x2, y2 = [i+j for i, j in zip(flatten(self.coord), flatten(v2.coord))]
  74.         return Vector2(x1, y1, x2, y2)
  75.  
  76.     def __repr__(self):
  77.         return "Vector2{0}".format(self.coord)
  78.  
  79. class Plotter(Frame):
  80.     width      = 750
  81.     height     = 750
  82.     MAXLASTLEN = 0
  83.  
  84.     lineTagStr = "L:{x1}:{y1}x{x2}:{y2}"
  85.     circleTagStr = "C:{x}:{y}r{rad}s{spokes}"
  86.     polygonTagStr = "P:{x}:{y}r{rad}s{sides}"
  87.  
  88.     #####
  89.     ###
  90.     # BEGIN INTERNAL FUNCTIONS
  91.     ###
  92.     #####
  93.  
  94.  
  95.     def __init__(self, parent=None, width=width, height=height):
  96.         Frame.__init__(self, parent)
  97.         self.canvas = Canvas(self, width=width, height=height, bg="white")
  98.         self.plotCenter = (int(width/2), int(height/2))
  99.         self.canvas.pack()
  100.         for x in range(0, self.plotCenter[0], 10):
  101.             x1 = self.plotCenter[0]+x
  102.             x2 = self.plotCenter[0]-x
  103.             self.canvas.create_line(x1, 0, x1, height, fill="#CCCCCC", tag="grid")
  104.             self.canvas.create_line(x2, 0, x2, height, fill="#CCCCCC", tag="grid")
  105.  
  106.         for y in range(0, self.plotCenter[1], 10):
  107.             y1 = self.plotCenter[1]+y
  108.             y2 = self.plotCenter[1]-y
  109.             self.canvas.create_line(0, y1, width, y1, fill="#CCCCCC", tag="grid")
  110.             self.canvas.create_line(0, y2, width, y2, fill="#CCCCCC", tag="grid")
  111.         self.__lines = []
  112.         self.__polygons = []
  113.         self.__circles = []
  114.         self.__last = {}
  115.  
  116.  
  117.     @property
  118.     def plots(self):
  119.         return self.__lines + self.__polygons + self.__circles
  120.  
  121.     def __plot(self, x1, y1, x2, y2):
  122.         x1, y1, x2, y2 = [int(i) for i in (x1, y1, x2, y2)]
  123.  
  124.         nx1, ny1, nx2, ny2 = (x1+self.plotCenter[0], -y1+self.plotCenter[1],
  125.                               x2+self.plotCenter[0], -y2+self.plotCenter[1])
  126.  
  127.         tagStr = Plotter.lineTagStr.format(x1=x1, x2=x2, y1=y1, y2=y2)
  128.  
  129.         self.__lines.append(((x1, y1), (x2, y2), tagStr))
  130.         self.canvas.create_line(nx1, ny1, nx2, ny2, fill="black", tag=tagStr)
  131.  
  132.         return nx1, ny1, nx2, ny2
  133.  
  134.  
  135.     def __plotCircle(self, x, y, radius, spokes=0):
  136.         x, y, radius = [int(i) for i in (x, y, radius)]
  137.  
  138.         nx, ny = self.plotCenter[0]+x, self.plotCenter[1]-y
  139.         tagStr = Plotter.circleTagStr.format(x=x, y=y, rad=radius, spokes=spokes)
  140.         self.__circles.append(((x, y), radius, tagStr))
  141.         self.canvas.create_oval(nx-radius, ny-radius, nx+radius, ny+radius,
  142.                                 outline="black", tag=tagStr)
  143.         if spokes > 0:
  144.             spokeInterval = 180.0/spokes
  145.             spokeCount = 0
  146.             spokeDegrees = []
  147.             while spokeCount < 180:
  148.                 spokeDegrees.append(spokeCount)
  149.                 spokeCount += spokeInterval
  150.  
  151.             for spoke in spokeDegrees:
  152.                 x1 = radius * math.sin(math.radians(spoke))
  153.                 y1 = radius * math.cos(math.radians(spoke))
  154.                 nx1, nx2 = nx+x1, nx-x1
  155.                 ny1, ny2 = ny+y1, ny-y1
  156.                 self.canvas.create_line(nx1, ny1, nx2, ny2, fill="black",
  157.                                         tag=tagStr)
  158.  
  159.         return nx, ny, radius, spokes
  160.  
  161.  
  162.     def __plotPolygon(self, x, y, radius, sides, rotation=0):
  163.         # TODO: add spokes PROPERLY
  164.         x, y, radius, sides = [int(i) for i in (x, y, radius, sides)]
  165.         nx, ny = self.plotCenter[0]+x, self.plotCenter[1]-y
  166.         tagStr = Plotter.polygonTagStr.format(x=x, y=y, rad=radius, sides=sides,
  167.                                               rot=rotation)
  168.         self.__polygons.append(((x, y), radius, tagStr))
  169.  
  170.         cornerInterval = 360.0/sides
  171.         cornerCount = 0.0
  172.         cornerDegrees = []
  173.         cornerCoords = []
  174.  
  175.         while cornerCount < 360.0:
  176.             cornerDegrees.append(cornerCount + rotation)
  177.             cornerCount += cornerInterval
  178.  
  179.         for corner in cornerDegrees:
  180.             x1 = radius * math.sin(math.radians(corner))
  181.             y1 = radius * math.cos(math.radians(corner))
  182.             cornerCoords += [(nx+x1, ny+y1)]
  183.  
  184.         corners = list(flatten(cornerCoords))
  185.  
  186.         self.canvas.create_polygon(*corners, outline="black", fill="",
  187.                                    tag=tagStr)
  188.  
  189.         return nx, ny, radius, sides
  190.  
  191.  
  192.     def __remove(self, x1, y1, x2, y2):
  193.         x1, y1, x2, y2 = [int(i) for i in (x1, y1, x2, y2)]
  194.  
  195.         nx1, ny1, nx2, ny2 = (x1+self.plotCenter[0], -y1+self.plotCenter[1],
  196.                               x2+self.plotCenter[0], -y2+self.plotCenter[1])
  197.  
  198.         tagStr = Plotter.lineTagStr.format(x1=x1, x2=x2, y1=y1, y2=y2)
  199.  
  200.         tagTuple = ((x1, y1), (x2, y2), tagStr)
  201.         if tagTuple in self.__lines:
  202.             self.__lines.remove(tagTuple)
  203.             self.canvas.delete(tagStr)
  204.             return nx1, ny1, nx2, ny2, tagStr
  205.         else:
  206.             reason = "no such line: {0}".format(tagStr)
  207.             raise NoLineError(reason)
  208.  
  209.  
  210.     def __removeCircle(self, x, y, radius, spokes):
  211.         x, y, radius = [int(i) for i in (x, y, radius)]
  212.  
  213.         nx, ny = self.plotCenter[0]+x, self.plotCenter[1]-y
  214.  
  215.         tagStr = Plotter.circleTagStr.format(x=x, y=y, rad=radius, spokes=spokes)
  216.         tagTuple = ((x, y), radius, tagStr)
  217.  
  218.         if tagTuple in self.__circles:
  219.             self.__circles.remove(tagTuple)
  220.             self.canvas.delete(tagStr)
  221.             return nx, ny, radius
  222.         else:
  223.             reason = "no such circle: {0}".format(tagStr)
  224.             raise NoCircleError(reason)
  225.  
  226.  
  227.     def __removePolygon(self, x, y, radius, sides, rotation):
  228.         x, y, radius, sides = [int(i) for i in (x, y, radius, sides)]
  229.  
  230.         nx, ny = self.plotCenter[0]+x, self.plotCenter[1]-y
  231.  
  232.         tagStr = Plotter.polygonTagStr.format(x=x, y=y, rad=radius, sides=sides,
  233.                                               rot=rotation)
  234.         tagTuple = ((x, y), radius, tagStr)
  235.  
  236.         if tagTuple in self.__polygons:
  237.             self.__polygons.remove(tagTuple)
  238.             self.canvas.delete(tagStr)
  239.             return nx, ny, radius, sides
  240.         else:
  241.             reason = "no such polygon: {0}".format(tagStr)
  242.             raise NoCircleError(reason)
  243.  
  244.  
  245.     def __trimLast(self, key):
  246.         if Plotter.MAXLASTLEN < 1:
  247.             return
  248.         l = len(self.__last[key])
  249.         if l > Plotter.MAXLASTLEN:
  250.             self.__last[key] = self.__last[key][l-Plotter.MAXLASTLEN:]
  251.  
  252.     #####
  253.     ###
  254.     # END INTERNAL FUNCTIONS
  255.     ###
  256.     #####
  257.  
  258.     def clear(self):
  259.         for line in self.plots:
  260.             self.canvas.delete(line[2])
  261.  
  262.         self.__lines = []
  263.         self.__last  = {}
  264.  
  265.  
  266.     def plotLine(self, x1, y1, x2, y2):
  267.         self.__plot(x1, y1, x2, y2)
  268.  
  269.         if 'line' not in self.__last:
  270.             self.__last['line'] = [(x1, y1, x2, y2)]
  271.         else:
  272.             self.__last['line'].append((x1, y1, x2, y2))
  273.             self.__trimLast('line')
  274.  
  275.  
  276.     def removeLine(self, x1, y1, x2, y2):
  277.         self.__remove(x1, y1, x2, y2)
  278.         if 'line' in self.__last:
  279.             while (x1, y1, x2, y2) in self.__last['line']:
  280.                 self.__last['line'].remove((x1, y1, x2, y2))
  281.  
  282.  
  283.     def popLine(self):
  284.         if 'line' not in self.__last:
  285.             raise NoLastError('there is no line history')
  286.         if not self.__last['line']:
  287.             raise NoLastError('line history exhausted')
  288.         coord = self.__last['line'].pop()
  289.         self.__remove(*coord)
  290.         return coord
  291.  
  292.  
  293.     def calculatePerpend(self, x1, y1, x2, y2):
  294.         vec = Vector2(x1, y1, x2, y2) # vectors simplified this so damned much
  295.         dx, dy, cx, cy = vec.center * 2
  296.         sx, sy = vec.signs
  297.         px, py = [(abs(i) * j) for (i, j) in zip((dy, dx), (-sx, sy))]
  298.         return cx, cy, px, py      # ^ apply sign, but flip px's sign
  299.  
  300.  
  301.     def plotPerpend(self, x1, y1, x2, y2):
  302.         self.__plot(x1, y1, x2, y2)
  303.         cx, cy, px, py =  self.calculatePerpend(x1, y1, x2, y2)
  304.         coord = (cx+px, cy+py, cx-px, cy-py)
  305.         self.__plot(*coord)
  306.         if 'perpend' not in self.__last:
  307.             self.__last['perpend'] = [((x1, y1, x2, y2), coord)]
  308.         else:
  309.             self.__last['perpend'].append(((x1, y1, x2, y2), coord))
  310.             self.__trimLast('perpend')
  311.  
  312.  
  313.     def removePerpend(self, x1, y1, x2, y2):
  314.         self.__remove(x1, y1, x2, y2)
  315.         cx, cy, px, py =  self.calculatePerpend(x1, y1, x2, y2)
  316.         coord = (cx+px, cy+py, cx-px, cy-py)
  317.         self.__remove(*coord)
  318.         if 'perpend' in self.__last:
  319.             while ((x1, y1, x2, y2), coord) in self.__last['perpend']:
  320.                 self.__last['perpend'].remove(((x1, y1, x2, y2), coord))
  321.  
  322.  
  323.     def popPerpend(self):
  324.         if 'perpend' not in self.__last:
  325.             raise NoLastError('there is no perpendicular history')
  326.         if not self.__last['perpend']:
  327.             raise NoLastError('perpendicular history exhausted')
  328.         coord, coord2 = self.__last['perpend'].pop()
  329.  
  330.         self.__remove(*coord)
  331.         self.__remove(*coord2)
  332.         return coord, coord2
  333.  
  334.  
  335.     def plotParallels(self, x1, y1, x2, y2, length):
  336.         px, py =  self.calculatePerpend(x1, y1, x2, y2)[2:]
  337.         vec = Vector2(0, 0, px, py)
  338.         npx, npy = [i*length for i in vec.norm]
  339.         self.__plot(x1+npx, y1+npy, x1-npx, y1-npy)
  340.         self.__plot(x2+npx, y2+npy, x2-npx, y2-npy)
  341.         if 'parallel' not in self.__last:
  342.             self.__last['parallel'] = [((x1, y1, x2, y2), length)]
  343.         else:
  344.             self.__last['parallel'].append(((x1, y1, x2, y2), length))
  345.             self.__trimLast('parallel')
  346.  
  347.  
  348.     def removeParallels(self, x1, y1, x2, y2, length):
  349.         px, py =  self.calculatePerpend(x1, y1, x2, y2)[2:]
  350.         vec = Vector2(0, 0, px, py)
  351.         npx, npy = [i*length for i in vec.norm]
  352.         self.__remove(x1+npx, y1+npy, x1-npx, y1-npy)
  353.         self.__remove(x2+npx, y2+npy, x2-npx, y2-npy)
  354.         if 'parallel' in self.__last:
  355.             while ((x1, y1, x2, y2), length) in self.__last['parallel']:
  356.                 self.__last['parallel'].remove(((x1, y1, x2, y2), length))
  357.  
  358.  
  359.     def popParallels(self):
  360.         if 'parallel' not in self.__last:
  361.             raise NoLastError('there is no parallel history')
  362.         if not self.__last['parallel']:
  363.             raise NoLastError('parallel history exhausted')
  364.  
  365.         coord, length = self.__last['parallel'].pop()
  366.         x1, y1, x2, y2 = coord
  367.  
  368.         px, py =  self.calculatePerpend(x1, y1, x2, y2)[2:]
  369.         vec = Vector2(0, 0, px, py)
  370.         npx, npy = [i*length for i in vec.norm]
  371.         coord = (x1+npx, y1+npy, x1-npx, y1-npy)
  372.         coord2 = (x2+npx, y2+npy, x2-npx, y2-npy)
  373.         self.__remove(*coord)
  374.         self.__remove(*coord2)
  375.         return coord, coord2
  376.  
  377.  
  378.     def plotCircle(self, x, y, radius, spokes):
  379.         self.__plotCircle(x, y, radius, spokes)
  380.  
  381.         if 'circle' not in self.__last:
  382.             self.__last['circle'] = [(x, y, radius, spokes)]
  383.         else:
  384.             self.__last['circle'].append((x, y, radius, spokes))
  385.             self.__trimLast('circle')
  386.  
  387.  
  388.     def removeCircle(self, x, y, radius, spokes=0):
  389.         self.__removeCircle(x, y, radius, spokes)
  390.         if 'circle' in self.__last:
  391.             while (x, y, radius, spokes) in self.__last['circle']:
  392.                 self.__last['circle'].remove((x, y, radius, spokes))
  393.  
  394.  
  395.     def popCircle(self):
  396.         if 'circle' not in self.__last:
  397.             raise NoLastError('there is no circle history')
  398.         if not self.__last['circle']:
  399.             raise NoLastError('circle history exhausted')
  400.         coord = self.__last['circle'].pop()
  401.         self.__removeCircle(*coord)
  402.         return coord
  403.  
  404.  
  405.     def plotPolygon(self, x, y, radius, sides, rotation):
  406.         self.__plotPolygon(x, y, radius, sides, rotation)
  407.  
  408.         if 'polygon' not in self.__last:
  409.             self.__last['polygon'] = [(x, y, radius, sides, rotation)]
  410.         else:
  411.             self.__last['polygon'].append((x, y, radius, sides, rotation))
  412.             self.__trimLast('polygon')
  413.  
  414.  
  415.     def removePolygon(self, x, y, radius, sides, rotation):
  416.         self.__removePolygon(x, y, radius, sides, rotation)
  417.         if 'polygon' in self.__last:
  418.             while (x, y, radius, sides, rotation) in self.__last['polygon']:
  419.                 self.__last['polygon'].remove((x, y, radius, sides, rotation))
  420.  
  421.  
  422.     def popPolygon(self):
  423.         if 'polygon' not in self.__last:
  424.             raise NoLastError('there is no polygon history')
  425.         if not self.__last['polygon']:
  426.             raise NoLastError('polygon history exhausted')
  427.         coord = self.__last['polygon'].pop()
  428.         self.__removePolygon(*coord)
  429.         return coord
  430.  
  431.  
  432. if __name__ == "__main__": # self-test code
  433.     import doctest, random
  434.     doctest.testmod()
  435.  
  436.     root = Tk()
  437.     root.title("Plotter")
  438.     plot = Plotter(root, random.randrange(400,600), random.randrange(400,600))
  439.     plot.pack()
  440.     root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement