Commit bd575e2b authored by gijs's avatar gijs

New version of the plotbot

parent 6516d27a
#!/usr/bin/env python2
import irc.bot
from datetime import datetime
from random import randint
import re
import subprocess
import time
import thread
MSG_FOUND_IMAGE = "Hey, thats an image, let me draw it for you"
MSG_IGNORED_IMAGE = "Hey, thats an image, but I'm already drawing"
MSG_FINISHED_IMAGE = "Hey, I finished drawing that image"
image_pattern = re.compile('http(s)?://.*\.(png|jpg|bmp|jpeg|gif)', re.I)
ext_pattern = re.compile('\.(png|jpg|bmp|jpeg|gif)$', re.I)
margin_pattern = re.compile('(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)')
state_pattern = re.compile('currently (.+)')
finished_pattern = re.compile('great news')
nick_plotbot = 'botopera_roland'
class PlotBot(irc.bot.SingleServerIRCBot):
def __init__(self, channel, nickname, server, port=6667):
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
self.channel = channel
self.pens = []
self.margins = None
self.image = None
self.pen = None
def get_pen(self, nick):
if nick not in self.pens:
self.pens.append(nick)
return self.pens.index(nick) + 1
def get_nick(self, source):
if '!' in source:
return source.split('!',1)[0]
return source
def on_welcome(self, c, e):
c.join(self.channel,)
print "Bot connected"
self.send(c, 'what is the pagesize?')
def on_privmsg(self, c, e):
msg = e.arguments[0]
print "Received msg: '{0}'".format(msg)
if finished_pattern.search(msg) <> None:
self.image = None
self.send_public(c, MSG_FINISHED_IMAGE)
elif margin_pattern.search(msg) <> None:
print 'found margins'
margin_match = margin_pattern.search(msg)
self.margins = {
'left': int(margin_match.group(1)),
'bottom': int(margin_match.group(2)),
'right': int(margin_match.group(3)),
'top': int(margin_match.group(4))
}
print self.margins
elif state_pattern.search(msg) <> None:
state = state_pattern.search(msg).group(1)
if state == 'waiting':
print ('DRAW Image')
self.draw_image(c)
else:
print ('drop Image')
self.drop_image(c)
def send(self, c, msg):
max_length = 350
if len(msg) <= max_length + 2:
c.privmsg(nick_plotbot, msg)
else:
c.privmsg(nick_plotbot, msg[0:max_length])
time.sleep(1)
self.send(c, msg[max_length:])
def send_public(self, c, msg):
c.privmsg(self.channel, msg)
def draw_image(self, c):
print self.image
if self.image <> None:
extension_match = ext_pattern.search(self.image)
print extension_match
if (extension_match <> None):
extension = extension_match.group(1)
localName = 'drawings/latest'
imgWidth = '2000'
imgHeight = '2000'
subprocess.call([
'./hpgl-line-drawing.sh',
self.image,
localName,
extension,
imgWidth,
imgHeight
])
handle = open('{0}.hpgl'.format(localName), 'r')
hpgl = ''.join(handle.readlines()).replace('\n', '')
width = self.margins['right'] - self.margins['left']
height = self.margins['top'] - self.margins['bottom']
scale = .9
x = randint(2000, width - 5000) * -1
y = randint(2000, height - 5000) * -1
thread.start_new_thread(self.send, (c, '<<SC{0},{1},{2},{3};SP{4};FS1;VS10;PU;{5};PU>>'.format(x, width + x, y, y + height, self.pen, hpgl)))
def drop_image(self, c):
self.image = None
def on_pubmsg(self, c, e):
message = e.arguments[0]
nick = self.get_nick(e.source)
found_image = image_pattern.search(message)
if found_image <> None and self.margins <> None:
if self.image == None:
self.image = found_image.group(0)
self.pen = self.get_pen(nick)
self.send_public(c, MSG_FOUND_IMAGE)
self.send(c, 'what are you doing?')
else:
self.send_public(c, MSG_IGNORED_IMAGE)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='drawbot')
parser.add_argument('--host', default="localhost", help='host')
parser.add_argument('--port', type=int, default=6667, help='port')
parser.add_argument('channel', help='channel to join')
args = parser.parse_args()
if not args.channel.startswith("#"):
args.channel = "#"+args.channel
bot = PlotBot(args.channel, 'drawbot', args.host, args.port)
bot.start()
\ No newline at end of file
from copy import deepcopy
# Character object. Character information is stored inside this object
class Character (object):
def __init__ (self, source = False):
self.key = source["key"]
self._width = source["width"]
self._height = source["height"]
self._lines = source["lines"]
if 'margins' in source:
self._margins = source["margins"]
else:
self._margins = [0,0,0,0]
self.lines = deepcopy (self._lines)
self.shape = []
self.curve_resolution = 10
self.length = 0
self._scale = 1
@property
def width (self):
return self._width * self.scale
@property
def height (self):
return self._height * self.scale
@property
def margins (self):
return [val * self.scale for val in self._margins]
def render (self, resolution = False):
if resolution <> False:
self.curve_resolution = resolution
if len (self.lines) > 0 and len (self.lines[0]) > 0:
self.shape = []
position = (self._height, 0)
for line in self.lines:
points = []
for segment in line:
if len(segment) > 2:
# Curve
if len (points) > 0:
curve_points = [position]
else:
curve_points = []
for i in range (0,len(segment),2):
curve_points.append ((segment[i], segment[i+1]))
for point in bezier_interpolation (curve_points, self.curve_resolution, 1):
points.append ((point[0] - position[0], point[1] - position[1]))
#points.append ((point[0], point[1]))
position = (point[0], point[1])
else:
points.append ((segment[0] - position[0], segment[1] - position[1]))
#points.append ((segment[0], segment[1]))
position = (segment[0], segment[1])
self.shape.append(points)
def hpgl (self, offset = (0,0)):
buff = ['PA{0},{1}'.format (self.y + offset[0], self.x + offset[1])]
for line in self.shape:
buff.append ('PR{0:.1f},{1:.1f}'.format (float (line[0][0]), float (line[0][1]))) # switch to relative.
points = ['{0:.1f},{1:.1f}'.format (float (point[0]), float (point[1])) for point in line[1:]]
buff.append ('PD{0}'.format (','.join (points)))
buff.append ('PU')
return ';'.join (buff)
@property
def scale (self):
return self._scale
@scale.setter
def scale (self, scale):
self._scale = scale
self.shape = [[(point[0] * scale, point[1] * scale) for point in line] for line in self.shape]
# Loop through all lines, and points to scale them
def validate (self):
return True if self.patt.match (self.source) <> None else False
\ No newline at end of file
#from chiplotle.core.imports.package_import import _package_import
#
#_package_import(__path__[0], locals( ))
#
#def remove_all_but_types(lst):
# '''Keep only classes.'''
# from types import TypeType
# for k, v in lst.items( ):
# if not isinstance(v, TypeType):
# del(lst[k])
#
#remove_all_but_types(locals( ))
from dpx2000 import DPX2000
from dpx3300 import DPX3300
from dpx3500 import DPX3500
from dxy1300 import DXY1300
from dxy880 import DXY880
from grx400ag import GRX400AG
from grx400ag import GRX400AG as GRX0400AG
from hp7475a import HP7475A
from hp7550a import HP7550A
from hp7575a import HP7575A
from hp7576a import HP7576A
from hp7585b import HP7585B
from hp7595a import HP7595A
from hp7596a import HP7596A
from plotter import Plotter
"""
* This file is part of chiplotle.
*
* http://music.columbia.edu/cmc/chiplotle
"""
from chiplotle.plotters.drawingplotter import _DrawingPlotter
class DPX3300(_DrawingPlotter):
def __init__(self, ser, **kwargs):
self.allowedHPGLCommands = tuple(['\x1b.', 'AA','AR','BL','CA','CC',
'CI','CP','CS','CT','DC','DF','DI','DL','DP','DR','DT','EA','EP',
'ER','ES','EW','FP','FS','FT','IM','IN','IP','IW','LB','LO','LT',
'OA','OC','OD','OE','OF','OH','OI','OL','OO','OP','OS','OT','OW',
'PA','PB','PD','PM','PU','PR','PT','RA','RO','RR','SA','SC','SI',
'SL','SM','SP','SR','SS','TL','UC','UF','VS','WG','XT','YT'])
_DrawingPlotter.__init__(self, ser, **kwargs)
self.type = "DPX-3300"
"""
* This file is part of chiplotle.
*
* http://music.columbia.edu/cmc/chiplotle
"""
from chiplotle.plotters.drawingplotter import _DrawingPlotter
class DPX3500(_DrawingPlotter):
def __init__(self, ser, **kwargs):
self.allowedHPGLCommands = tuple(['\x1b.', 'AA','AR','BL','CA','CC',
'CI','CP','CS','CT','DC','DF','DI','DL','DP','DR','DT','EA','EP',
'ER','ES','EW','FP','FS','FT','IM','IN','IP','IW','LB','LO','LT',
'OA','OC','OD','OE','OF','OH','OI','OL','OO','OP','OS','OT','OW',
'PA','PB','PD','PM','PU','PR','PT','RA','RO','RR','SA','SC','SI',
'SL','SM','SP','SR','SS','TL','UC','UF','VS','WG','XT','YT'])
_DrawingPlotter.__init__(self, ser, **kwargs)
self.type = "DPX-3500"
"""
* This file is part of chiplotle.
*
* http://music.columbia.edu/cmc/chiplotle
"""
from chiplotle.plotters.drawingplotter import _DrawingPlotter
class GRX400AG (_DrawingPlotter):
def __init__(self, ser, **kwargs):
self.allowedHPGLCommands = tuple(['\x1b.', 'AA','AF','AH','AP','AR',
'BF','BL','CA','CC','CI','CM','CP','CS','CT','CV','DC','DF','DI',
'DL','DP','DR','DS','DT','EA','EP','ER','ES','EW','FP','FS','FT',
'GC','IM','IN','IP','IV','IW','KY','LB','LO','LT','NR','OA','OC',
'OD','OE','OF','OG','OH','OI','OK','OL','OO','OP','OS','OT','OW',
'PA','PB','PD','PG','PM','PR','PT','PU','RA','RO','RP','RR','SA',
'SC','SI','SL','SM','SP','SR','SS','TL','UC','UF','VS','WD','WG',
'XT','YT'])
_DrawingPlotter.__init__(self, ser, **kwargs)
self.type = "GRX-400AG"
"""
* This file is part of chiplotle.
*
* http://music.columbia.edu/cmc/chiplotle
"""
from chiplotle.plotters.drawingplotter import _DrawingPlotter
class GRX400AG (_DrawingPlotter):
def __init__(self, ser, **kwargs):
self.allowedHPGLCommands = tuple(['\x1b.', 'AA','AF','AH','AP','AR',
'BF','BL','CA','CC','CI','CM','CP','CS','CT','CV','DC','DF','DI',
'DL','DP','DR','DS','DT','EA','EP','ER','ES','EW','FP','FS','FT',
'GC','IM','IN','IP','IV','IW','KY','LB','LO','LT','NR','OA','OC',
'OD','OE','OF','OG','OH','OI','OK','OL','OO','OP','OS','OT','OW',
'PA','PB','PD','PG','PM','PR','PT','PU','RA','RO','RP','RR','SA',
'SC','SI','SL','SM','SP','SR','SS','TL','UC','UF','VS','WD','WG',
'XT','YT'])
_DrawingPlotter.__init__(self, ser, **kwargs)
self.type = "GRX-400AG"
#!/usr/bin/env python2
from random import randint
from sys import argv
from PIL import Image
inpath = argv[1]
outpath = argv[2]
max_width = int(argv[3])
max_height = int(argv[4])
im_in = Image.open(inpath)
if (im_in.format == 'GIF'):
seek_image = Image.open(inpath)
frames = 1
while frames < 100:
try:
seek_image.seek(frames)
frames += 1
except EOFError:
break;
frame = randint(0,frames-1) if frames > 1 else 0
im_in.seek(frame)
width,height = im_in.size
scale = 1
if width > 2000:
scale = 200.0 / width
elif height > 2000:
scale = 2000.0 / height
if scale <> 1:
im_in.resize((
int(width * scale),
int(height * scale),
))
width,height = im_in.size
im_in = im_in.copy()
if width > max_width or height > max_height:
x = randint(0, max(0, width - max_width))
y = randint(0, max(0, height - max_height))
box = (x, y, min(width, x + max_width), min(height, y + max_width))
im_in = im_in.crop(box)
im_in.save(outpath)
\ No newline at end of file
from character import Character
from copy import deepcopy
import os
import json
class Font (object):
def __init__ (self, path = False, resolution = False, scale = False):
self.path = ''
self.cache = True
self.source = {}
self.chars = {}
self.resolution = 10 if resolution == False else resolution
self.length = 0
self.name = ''
self.cacheDir = 'shape_font_cache' #'~/tmp/shape_font_cache'
self.cacheFilePath = '{0}/{1}-{2}.json' # Basepath, name, resolution
if path <> False:
self.load (path)
if resolution <> False:
self.render (resolution = resolution)
if scale <> False:
self.scale (scale)
def load (self, path = False):
if path <> False:
self.path = path
#try:
with open(self.path, 'r') as font_file:
self.source = json.load(font_file)
font_file.close()
self.name = self.source["name"]
print self.name, self.source["name"]
for char in self.source["chars"]:
char = Character (char)
self.addChar (char)
#except:
#print 'could not load font'
#return False
#return True
def write (self, path = False):
self.scale (1)
writer = FontWriter (self)
return writer.write (path)
def get (self, key):
if key in self.chars:
return deepcopy (self.chars[key])
else:
return False
def addChar (self, char):
self.chars[char.key] = char
def getChar (self, char):
return self.get (ord (char))
def render (self, resolution = False):
if resolution <> False:
self.resolution = resolution
if self.cache == True:
if self.loadCache () == True:
return True
for key in self.chars:
self.chars[key].render(self.resolution)
if self.cache == True:
self.writeCache ()
def scale (self, scale = 1):
self.height = self.chars[self.chars.keys()[0]]._height * scale * 1.60
for key in self.chars:
self.chars[key].scale = scale
@property
def cacheFile (self):
return self.cacheFilePath.format (self.cacheDir, self.name, self.resolution)
def loadCache (self):
if self.cacheExists():
cache = json.load (open (self.cacheFile, 'r'))
self.putCacheObject (cache)
return True
else:
return False
def writeCache (self):
if self.cacheDirExists () == False:
self.createCacheDir ()
handle = open (self.cacheFile, 'w')
return json.dump (self.getCacheObject (), handle)
def cacheExists (self):
if os.path.exists (self.cacheFile):
return True
else:
if self.cacheDirExists () == False:
self.createCacheDir ()
return False
def cacheDirExists (self):
return os.path.exists (self.cacheDir)
def createCacheDir (self):
os.makedirs (self.cacheDir)
def getCacheObject (self):
return {char: self.chars[char].shape for char in self.chars}
def putCacheObject (self, cache):
for char in cache:
self.chars[int (char)].shape = cache[char]
\ No newline at end of file
This diff is collapsed.
from writer import Writer
# Exports the font object to an JSON file
class FontWriter (object):
pointReplacementPatt = re.compile ("(\s+)(\-?\d+),\n\s+(\-?\d+)", flags=re.M)
def __init__ (self, font = False):
if font <> False:
self.font = font
def write (self, path):
if self.font <> False:
writeList = []
for char in self.font.chars:
#writeObject = {}
#writeObject['key'] = self.font.chars[char].key
#writeObject['width'] = self.font.chars[char].width
#writeObject['height'] = self.font.chars[char].height
#writeObject['lines'] = self.font.chars[char].lines
#
writeObject = dict (
key = self.font.chars[char].key,
width = self.font.chars[char].width,
height = self.font.chars[char].height,
lines = self.font.chars[char].lines,
margins = self.font.chars[char].margins
)
writeList.append (writeObject)
self.writer = Writer ({'name': self.font.name, 'chars': writeList})
buff = self.pointReplacementPatt.sub ("\\1\\2,\\3", self.writer.build ())
try:
with open (path, 'w') as self.f:
self.f.write (buff)
self.f.close ()
except IOError:
return False
return True
\ No newline at end of file
#!/bin/bash
DOWNLOAD=${1}
NAME=${2}
EXT=${3}
MAXWIDTH=${4}
MAXHEIGHT=${5}
# Download imagefile
wget -O ${NAME}.${EXT} ${DOWNLOAD}
# Cut selection
./crop.py ${NAME}.${EXT} ${NAME}.png ${MAXWIDTH} ${MAXHEIGHT}
convert ${NAME}.png ${NAME}.bmp
# Remove tmp files
# rm ${NAME} ${NAME}.png
# First convert to vector and despeckle
potrace -t 20 -o ${NAME}.ps ${NAME}.bmp
# Convert it again into bitmap
convert ${NAME}.ps ${NAME}.jpg
# Use centerline to turn into linedrawing
autotrace -centerline -color-count=2 -background-color=FFFFFF -output-file=${NAME}.svg ${NAME}.jpg
# rm ${NAME}.jpg
# Through inkscape
inkscape -z -P ${NAME}.ps ${NAME}.svg
# HPGL
pstoedit -f plot-hpgl ${NAME}.ps ${NAME}.dirty.hpgl
# Clean HPGL
./hpgl-distiller -i ${NAME}.dirty.hpgl -o ${NAME}.hpgl
\ No newline at end of file
def write(hpgl):
hpgl += ';OI'
self.plotter.write(hpgl)
while ready == False:
if plotter._serial_port.inWaiting() < 1:
answer = self.plotter._serial_port.readline()
if re.match('0400', answer):
self.state = WAITING
else:
time.sleep(1.0 / 8)
"""
The HPGL bot litens to private messages, which will be
forwarded to the plotter, except for when it receives
it
"""
from chiplotle.tools.plottertools._instantiate_plotter import _instantiate_plotter
from chiplotle.tools.serialtools import what_plotter_in_port
import irc.bot
from plotterMem import enlargeMemory
import re
import serial
import thread
import time
PLOTTER_SIGNATURE = '0400'
PLOTTER_PORT= '/dev/ttyUSB0'
STATE_WAITING = 'waiting'
STATE_PLOTTING = 'plotting'
STATE_LISTENING = 'listening'
OPEN_STREAM_REGEX = re.compile('^\s*<<(.*)')
CLOSE_STREAM_REGEX = re.compile('(.*)>>\s*$')
MSG_TOO_BUSY = "Sorry, I've no time for it right now."
MSG_LISTENING_TO_SOMEONE_ELSE = "Sorry, I'm currently listening to someone else, try again soon?"
MSG_STARTED_STREAM = "Seems you are about to plot a lot..."
MSG_GOT_PART = "mkay..."
MSG_GOT_IT = "Got it, it'll be ready soon."
MSG_FINISHED = "Hey, great news, your image is ready"
class HPGLBot(irc.bot.SingleServerIRCBot):
def __init__(self, channel, nickname, server, port=6667):
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
self.channel = channel
self.plotter = None
self.wait = 3
self.margins = {'left': 0, 'right': 0, 'top': 0, 'bottom': 0}
self._listen_max = 300
self._listening_buffer = ''
self._listening_since = None
self._listening_to = None
self.buff = None
self.state = STATE_WAITING
self.get_plotter()
def on_privmsg(self, c, e):
msg = e.arguments[0]
print "Received msg: '{0}'".format(msg)
whom = self.get_nick(e.source)
if msg[-1] == '?':
if 'doing' in msg <> None or 'state' in msg <> None:
return self.answer(c, whom, 'I\'m currently {0}'.format(self.state))
margins = self.get_margins()
plotter = self.get_plotter()
if plotter <> None:
if 'pagesize'in msg:
return self.answer(c, whom, '{0}, {1}, {2}, {3}'.format(margins['left'], margins['bottom'], margins['right'], margins['top']))
if 'left' in msg: