diff --git a/arrange_spread2.py b/arrange_spread2.py new file mode 100644 index 0000000..80544a2 --- /dev/null +++ b/arrange_spread2.py @@ -0,0 +1,318 @@ +import svg +import random + +# rounds to the next biggest even number +def roundeven(num): + return (num+1)/2*2 + +def arrange_in_layer(abin, pwidth, pheight, rot_article=False): + # articles are longer than wider + # default rotation: width: x-direction + # height: y-direction + + layer = list() + rest = list() + root = {'x': 0, 'y': 0, + 'width': pwidth, 'height': pheight, + 'article': None, + 'down': None, + 'right': None} + + # traverse the tree until a node is found that is big enough for article + # with size width x height and return this node or None if not found + def find_node(root, width, height): + if root is None: + return None + elif root['article']: + return (find_node(root['right'], width, height) + or find_node(root['down'], width, height)) + elif width <= root['width'] and height <= root['height']: + return root + else: + return None + + # after finding a node where an article fits, put article into the node and + # create childnodes + def split_node(node, width, height, article): + node['article'] = article + node['article']['PlacePosition']['X'] = node['x']+width/2 + node['article']['PlacePosition']['Y'] = node['y']+height/2 + if node['width'] > 0 and node['height']-height > 0: + node['down'] = {'x': node['x'], 'y': node['y']+height, + 'width': node['width'], + 'height': node['height']-height, + 'article': None, + 'down': None, + 'right': None} + else: + node['down'] = None + if node['width']-width > 0 and height > 0: + node['right'] = {'x': node['x']+width, 'y': node['y'], + 'width': node['width']-width, + 'height': height, + 'article': None, + 'down': None, + 'right': None} + else: + node['right'] = None + return node + + # for each article in abin, check and place article at a node. If it doesnt + # fit, try to rotate. If it still doesnt fit, append to rest + for article in abin: + # output format only accepts integer positions, round article sizes up + # to even numbers + owidth, oheight = article['Article']['Length'], article['Article']['Width'] + if rot_article: + article['Orientation'] = 2 + width, height = roundeven(oheight), roundeven(owidth) + else: + article['Orientation'] = 1 + width, height = roundeven(owidth), roundeven(oheight) + + node = find_node(root, width, height) + if (node): + node = split_node(node, width, height, article) + else: + # rotate article + # output format only accepts integer positions, round article sizes up + # to even numbers + if rot_article: + article['Orientation'] = 1 + width, height = roundeven(owidth), roundeven(oheight) + else: + article['Orientation'] = 2 + width, height = roundeven(oheight), roundeven(owidth) + node = find_node(root, width, height) + if (node): + node = split_node(node, width, height, article) + else: + # rotate back + if rot_article: + article['Orientation'] = 2 + else: + article['Orientation'] = 1 + rest.append(article) + + # gather all articles that were + def find_articles(node): + if not node['article']: + return + layer.append(node['article']) + if node['right']: + find_articles(node['right']) + if node['down']: + find_articles(node['down']) + + find_articles(root) + + return root, layer, rest + +# generate a list of random articles of three different type +# each type is of the same size and color +# numbers of articles of each type linearly depend on their area +# articles are generated with more width that height +def generate_bin(): + abin = [] + for i in 1,2,3: + w, h = random.randint(20,150), random.randint(20,150) + if h > w: + w, h = h, w + color = random.randint(0,255), random.randint(0,255), random.randint(0,255) + for j in range(200000/(w*h)): + abin.append({'x':0, 'y':0, 'width':w, 'height':h, 'color':color}) + return abin + +# given a node tree with articles inside, spread them out over the full +# available area evenly +def spread_articles(root): + def get_width(article): + if article['Orientation'] == 1: + return article['Article']['Length'] + else: + return article['Article']['Width'] + + def get_height(article): + if article['Orientation'] == 1: + return article['Article']['Width'] + else: + return article['Article']['Length'] + + # get only nodes on the left branch of the tree. This is all nodes below + def get_down_nodes(node): + if node is None or not node['article']: + return [] + else: + return [node] + get_down_nodes(node['down']) + + # move this node and its whole subtree down + def move_tree_down(node, y): + if not node['article']: + return + node['article']['PlacePosition']['Y'] += y + if node['right']: + move_tree_down(node['right'], y) + if node['down']: + move_tree_down(node['down'], y) + + # for each child on the very left, spread vertically and adjust subtree y + # position accordingly + def spread_vertically(node): + downnodes = get_down_nodes(node) + # process innermost nodes before outer nodes + for n in downnodes: + if n['right']: + spread_vertically(n['right']) + if len(downnodes) == 0: + return + elif len(downnodes) == 1: + # arrange them in the center of parent + # treat the article height as even and round the gap size to the + # next smallest even number + gap = (node['height']-roundeven(get_height(downnodes[0]['article'])))/4*2 + move_tree_down(node, gap) + else: + # get the sum of all heights of the leftmodes articles as if they + # had even heights + sumdownnodes = sum([roundeven(get_height(n['article'])) for n in downnodes]) + # do some fancy math to figure out even gap sizes between + # the nodes + d, m = divmod((node['height']-sumdownnodes)/2, len(downnodes)-1) + gaps = (m)*[(d+1)*2]+((len(downnodes)-1)-m)*[d*2] + # iteratively move trees down by vgap except for first row + for node, gap in zip(downnodes[1:], gaps): + move_tree_down(node, gap) + + # for a given node, return a tuple consisting of a list of nodes that + # make out the longest row in horizontal direction and a list of nodes that + # start a shorter end + def get_max_horiz_nodes(node): + if node is None or not node['article']: + return [], [] + elif node['down'] and node['down']['article']: + # if the node has an article below, check out the rightbranch + rightbranch, sr = get_max_horiz_nodes(node['right']) + rightbranch = [node] + rightbranch + # as well as the down branch + downbranch, sd = get_max_horiz_nodes(node['down']) + # get information about the last article in each branch + ar = rightbranch[len(rightbranch)-1]['article'] + ad = downbranch[len(downbranch)-1]['article'] + # and return as the first tuple entry the branch that stretches the + # longest while having as the second tuple entry the nodes that + # were dismissed as starting shorter branches + if ar['PlacePosition']['X']+get_width(ar)/2 > ad['PlacePosition']['X']+get_width(ad)/2: + return rightbranch, sr+[downbranch[0]] + else: + return downbranch, sd+[rightbranch[0]] + else: + # if there is no article below, just recursively call itself on the + # next node to the right + rightbranch, short = get_max_horiz_nodes(node['right']) + return [node] + rightbranch, short + + # move a node and the article inside to the right and reduce node width + # recursively call for children + def move_tree_right(node, x): + if not node['article']: + return + node['article']['PlacePosition']['X'] += x + node['x'] += x + node['width'] -= x + if node['right']: + move_tree_right(node['right'], x) + if node['down']: + move_tree_right(node['down'], x) + + # for each child on the very right, spread horizontally and adjust subtree + # x position accordingly + def spread_horizontally(node): + maxhoriznodes, short = get_max_horiz_nodes(node['right']) + maxhoriznodes = [node] + maxhoriznodes + if len(maxhoriznodes) == 0: + return + elif len(maxhoriznodes) == 1: + # arrange them in the center of parent + # treat article width as even and round the gap size to the next + # smallest even number + gap = (node['width']-roundeven(get_width(maxhoriznodes[0]['article'])))/4*2 + maxhoriznodes[0]['article']['PlacePosition']['X'] += gap + else: + # get the sum of all widths of the articles that make the longest + # row of articles as if they had even widths + summaxhoriznodes= sum([roundeven(get_width(n['article'])) for n in maxhoriznodes]) + # do some fancy math to figure out even gap sizes between the nodes + d, m = divmod((node['width']-summaxhoriznodes)/2, len(maxhoriznodes)-1) + gaps = (m)*[(d+1)*2]+((len(maxhoriznodes)-1)-m)*[d*2] + # iteratively move trees right by hgap except for first node + for node, gap in zip(maxhoriznodes[1:], gaps): + move_tree_right(node, gap) + # recursively call for all nodes starting a shorter subtree + for node in short: + spread_horizontally(node) + + # spread nodes vertically + spread_vertically(root) + + # and horizontally + for node in get_down_nodes(root): + spread_horizontally(node) + +# sanity checks +def sanity_check(layer): + def intersects(a1, a2): + return (a1['x'] < a2['x']+a2['width'] + and a1['x']+a1['width'] > a2['x'] + and a1['y'] < a2['y']+a2['height'] + and a1['y']+a1['height'] > a2['y']) + odds = list() + overhangs = list() + inters = list() + for article1 in layer: + if (article1['x']%2 != 0 + or article1['y']%2 != 0): + odds.append(article1) + if (article1['x'] < 0 + or article1['y'] < 0 + or article1['x']+article1['width'] > pwidth + or article1['y']+article1['height'] > pheight): + overhangs.append(article1) + for article2 in layer: + if article1 == article2: + continue + if intersects(article1, article2): + inters.append((article1, article2)) + for odd in odds: + print "odd:", odd + for overhang in overhangs: + print "overhang:", overhang + for inter in inters: + print "intersect:", inter + if len(odds) or len(overhangs) or len(inters): + print layer + exit(1) + +# draw layer of articles +def draw_layer(filename, layer): + scene = svg.Scene(filename, (pwidth, pheight)) + for a in layer: + scene.add(svg.Rectangle((a['x'], a['y']),(a['width'], a['height']),a['color'])) + scene.write() + +# return all articles in a node tree +def find_articles(node): + if node is None or not node['article']: + return [] + else: + return [node['article']] + find_articles(node['right']) + find_articles(node['down']) + +if __name__ == "__main__": + abin = generate_bin() + pwidth, pheight = 800, 600 + abin = sorted(abin, key=lambda article: article['width']*article['height'], reverse=True) + root, layer, rest = arrange_in_layer(abin, pwidth, pheight) + draw_layer('test1', layer) + spread_articles(root) + layer = find_articles(root) + draw_layer('test2', layer) + sanity_check(layer) diff --git a/bruteforce2.py b/bruteforce2.py new file mode 100644 index 0000000..3d43e7b --- /dev/null +++ b/bruteforce2.py @@ -0,0 +1,79 @@ +import sys +import subprocess +import itertools +import shutil +from util import xmlfiletodict, dicttoxmlfile, get_pallet, get_articles, get_packlist_dict +from arrange_spread2 import arrange_in_layer, spread_articles, find_articles +import cPickle +from binascii import b2a_base64 + +def get_layers(bins, pallet, rot_article=False, rot_pallet=False): + for abin in bins: + bins[abin] = sorted(bins[abin], key=lambda article: article['Article']['Length']*article['Article']['Width'], reverse=True) + plength, pwidth = (pallet['Dimensions']['Length'], pallet['Dimensions']['Width']) + root, layer, rest = arrange_in_layer(bins[abin], plength, pwidth, rot_article=rot_article) + while layer: + spread_articles(root) + + occupied_area = 0 + for article in layer: + length, width = article['Article']['Length'], article['Article']['Width'] + occupied_area += length*width + + # print "layer occupation:", occupied_area/float(plength*pwidth) + if occupied_area/float(plength*pwidth) <= 0.7: + rot_article, rot_pallet = (yield None, layer) + else: + rot_article, rot_pallet = (yield layer, None) + + root, layer, rest = arrange_in_layer(rest, plength, pwidth, rot_article=rot_article) + +def main(): + if len(sys.argv) != 3: + print "usage:", sys.argv[0], "order.xml packlist.xml" + exit(1) + + orderline = xmlfiletodict(sys.argv[1]) + pallet = get_pallet(orderline) + articles = get_articles(orderline) + bins = dict() + + for article in articles: + abin = bins.get(article['Article']['Height']) + if abin: + abin.append(article) + else: + bins[article['Article']['Height']] = [article] + + scores = list() + + stuff1 = list() + + #for order in itertools.product([True, False], repeat=12): + for order in [[True]*12,]: + rests = list() + layers = list() + + it = get_layers(bins, pallet, order[0], False) + layer, rest = it.next() + if layer: + layers.append(layer) + if rest: + rests.append(rest) + + fail = True + for rot_article in order[1:]: + try: + layer, rest = it.send((rot_article, False)) + if layer: + layers.append(layer) + if rest: + rests.append(rest) + except StopIteration: + fail = False + break + if fail: + raise Exception("finished early") + print b2a_base64(cPickle.dumps((layers, rests, pallet))), +if __name__ == "__main__": + main() diff --git a/bruteforce3.py b/bruteforce3.py new file mode 100644 index 0000000..1a19098 --- /dev/null +++ b/bruteforce3.py @@ -0,0 +1,81 @@ +import sys +import subprocess +import itertools +import shutil +from util import xmlfiletodict, dicttoxmlfile, get_pallet, get_articles, get_packlist_dict +from arrange_spread2 import arrange_in_layer, spread_articles, find_articles +import cPickle +import marshal +from binascii import a2b_base64 +import tempfile +import os + +def evaluate_layers_rests(layers, rests, scores, pallet): + rest_layers = list() + # sort rests by space they cover and move them to the center of the pile + # append them to the layer list + for rest in sorted(rests, key=lambda rest: sum([article['Article']['Length']*article['Article']['Width'] for article in rest]), reverse=True): + plength, pwidth = (pallet['Dimensions']['Length'], pallet['Dimensions']['Width']) + root, layer, rest = arrange_in_layer(rest, plength, pwidth) + + com_x = 0 + com_y = 0 + for article in layer: + com_x += article['PlacePosition']['X'] + com_y += article['PlacePosition']['Y'] + com_x, com_y = com_x/len(layer), com_y/len(layer) + + diff_x, diff_y = plength*0.5-com_x, pwidth*0.5-com_y + + for article in layer: + article['PlacePosition']['X'] += diff_x + article['PlacePosition']['Y'] += diff_y + + rest_layers.append(layer) + + for permut_layers in itertools.permutations(layers): + pack_sequence = 1 + pack_height = 0 + articles_to_pack = list() + + for layer in list(permut_layers)+rest_layers: + pack_height += layer[0]['Article']['Height'] + #if pack_height > pallet['Dimensions']['MaxLoadHeight']: + # break + for article in layer: + article['PackSequence'] = pack_sequence + article['PlacePosition']['Z'] = pack_height + articles_to_pack.append(article) + pack_sequence += 1 + + packlist = get_packlist_dict(pallet, articles_to_pack) + + _, tmp = tempfile.mkstemp() + dicttoxmlfile(packlist, tmp) + + # ugly, ugly, ugly, ugly hack - dont copy this... + score = float(subprocess.check_output("../palletandtruckviewer-3.0/palletViewer -o " + +sys.argv[1]+" -p "+tmp + +" -s ../icra2011TestFiles/scoreAsPlannedConfig1.xml --headless | grep Score", shell=True).split(' ')[1].strip()) + scores.append(score) + if score > max(scores+[0]): + shutil.move(tmp, sys.argv[2]) + else: + os.remove(tmp) + +def main(): + scores = list() + for arg in sys.argv[3:]: + layers, rests, pallet = cPickle.loads(a2b_base64(arg)) + evaluate_layers_rests(layers, rests, scores, pallet) + + print max(scores) + #print "max:", max(scores) + #print "min:", min(scores) + #mean = sum(scores)/len(scores) + #print "mean:", mean + #from math import sqrt + #print "stddev:", sqrt(sum([(x-mean)**2 for x in scores])/len(scores)) + +if __name__ == "__main__": + main() diff --git a/svg.py b/svg.py new file mode 100644 index 0000000..30fa071 --- /dev/null +++ b/svg.py @@ -0,0 +1,42 @@ +import os + +class Scene: + def __init__(self, name, size): + self.name = name + self.items = [] + self.size = size + + def add(self, item): + self.items.append(item) + + def svgstr(self): + svgstr = "\n" + svgstr += "\n"%self.size + svgstr += " \n" + for item in self.items: + svgstr += item.svgstr() + svgstr += " \n\n" + return svgstr + + def write(self, filename=None): + if not filename: + filename = self.name + ".svg" + with open(filename, "w") as f: + f.write(self.svgstr()) + +class Rectangle: + def __init__(self, pos, size, color): + self.pos = pos + self.size = size + self.color = color + + def svgstr(self): + svgstr = " \n"%self.color + return svgstr + +if __name__ == "__main__": + scene = Scene('test', (400, 400)) + scene.add(Rectangle((0,0),(100,100),(255,0,0))) + scene.write() diff --git a/util.py b/util.py new file mode 100644 index 0000000..4ea5f6b --- /dev/null +++ b/util.py @@ -0,0 +1,197 @@ +from xml.etree import ElementTree + +def xmltodict(element): + if not isinstance(element, ElementTree.Element): + raise ValueError("must pass xml.etree.ElementTree.Element object") + + def xmltodict_handler(parent_element): + result = dict() + for element in parent_element: + if len(element): + obj = xmltodict_handler(element) + else: + obj = element.text + + if result.get(element.tag): + if hasattr(result[element.tag], "append"): + result[element.tag].append(obj) + else: + result[element.tag] = [result[element.tag], obj] + else: + result[element.tag] = obj + return result + + return {element.tag: xmltodict_handler(element)} + + +def dicttoxml(element): + if not isinstance(element, dict): + raise ValueError("must pass dict type") + if len(element) != 1: + raise ValueError("dict must have exactly one root key") + + def dicttoxml_handler(result, key, value): + if isinstance(value, list): + for e in value: + dicttoxml_handler(result, key, e) + elif isinstance(value, basestring): + elem = ElementTree.Element(key) + elem.text = value + result.append(elem) + elif isinstance(value, int) or isinstance(value, float): + elem = ElementTree.Element(key) + elem.text = str(value) + result.append(elem) + elif value is None: + result.append(ElementTree.Element(key)) + else: + res = ElementTree.Element(key) + for k, v in value.items(): + dicttoxml_handler(res, k, v) + result.append(res) + + result = ElementTree.Element(element.keys()[0]) + for key, value in element[element.keys()[0]].items(): + dicttoxml_handler(result, key, value) + return result + +def xmlfiletodict(filename): + return xmltodict(ElementTree.parse(filename).getroot()) + +def dicttoxmlfile(element, filename): + ElementTree.ElementTree(dicttoxml(element)).write(filename) + +def xmlstringtodict(xmlstring): + return xmltodict(ElementTree.fromstring(xmlstring).getroot()) + +def dicttoxmlstring(element): + return ElementTree.tostring(dicttoxml(element)) + +def get_pallet(orderline): + p = orderline['Message']['PalletInit']['Pallets']['Pallet'] + return { + 'PalletNumber': int(p['PalletNumber']), + 'Description': p['Description'], + 'Dimensions': { + 'MaxLoadHeight': int(p['Dimensions']['MaxLoadHeight']), + 'MaxLoadWeight': int(p['Dimensions']['MaxLoadWeight']), + 'Length': int(p['Dimensions']['Length']), + 'Width': int(p['Dimensions']['Width']), + } + } + +def get_articles(orderline): + articles = list() + for o in orderline['Message']['Order']['OrderLines']['OrderLine']: + for barcode in o['Barcodes']['Barcode']: + articles.append( + {'ApproachPoint1': {'X': 0, 'Y': 0, 'Z': 0}, + 'ApproachPoint2': {'X': 0, 'Y': 0, 'Z': 0}, + 'ApproachPoint3': {'X': 0, 'Y': 0, 'Z': 0}, + 'Article': { 'Description': o['Article']['Description'], + 'ID': int(o['Article']['ID']), + 'Type': int(o['Article']['Type']), # currently only Type=1 is allowed + 'Family': int(o['Article']['Family']), + 'Length': int(o['Article']['Length']), # should be larger than width + 'Width': int(o['Article']['Width']), + 'Height': int(o['Article']['Height']), + 'Weight': int(o['Article']['Weight']) # in grams + }, + 'Barcode': barcode, + 'Orientation': 1, # 1: 0 deg the long side parallel to X direction + # 2: 90 deg the long side parallel to Y direction + 'PackSequence': 0, + 'PlacePosition': {'X': 0, 'Y': 0, 'Z': 0} + } + ) + return articles + +def get_packlist_dict(pallet, articles): + pallet['Packages'] = {'Package': articles} + return {'Response': + {'PackList': + {'OrderID': '1', + 'PackPallets': + {'PackPallet': pallet } + } + } + } + +if __name__ == "__main__": + tree = ElementTree.parse('../icra2011TestFiles/GT/gt_d1r1.wpacklist.xml') + #tree = ElementTree.parse('../icra2011TestFiles/palDay1R1Order.xml') + root = tree.getroot() + xmldict = xmltodict(root) + + from pprint import pprint + #for package in xmldict['PackList']['PackPallets']['PackPallet']['Packages']['Package']: + # pprint(package) + + + root = dicttoxml(xmldict) + + xmldict = xmltodict(root) + pprint(xmldict) + + packages = [{'ApproachPoint1': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'ApproachPoint2': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'ApproachPoint3': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'Article': {'Description': '3', + 'Family': '0', + 'Height': '41', + 'ID': '3', + 'Length': '44', + 'Type': '0', + 'Weight': '500', + 'Width': '132'}, + 'Barcode': None, + 'Orientation': '1', + 'PackSequence': '1', + 'PlacePosition': {'X': '286', + 'Y': '330', + 'Z': '41'}, + 'StackHeightBefore': '0'}, + {'ApproachPoint1': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'ApproachPoint2': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'ApproachPoint3': {'X': '0', + 'Y': '0', + 'Z': '0'}, + 'Article': {'Description': '4', + 'Family': '0', + 'Height': '41', + 'ID': '4', + 'Length': '66', + 'Type': '0', + 'Weight': '550', + 'Width': '132'}, + 'Barcode': None, + 'Orientation': '1', + 'PackSequence': '54', + 'PlacePosition': {'X': '33', + 'Y': '66', + 'Z': '164'}, + 'StackHeightBefore': '0'}] + + packlist = {'Response': {'PackList': {'OrderID': '1', + 'PackPallets': {'PackPallet': {'BruttoWeight': '63000', + 'Description': None, + 'Dimensions': {'Length': '308', + 'MaxLoadHeight': '406', + 'MaxLoadWeight': '99999', + 'Width': '396'}, + 'NumberofPackages': '54', + 'Overhang': {'Length': '0', + 'Width': '0'}, + 'Packages': {'Package': packages}, + 'PalletNumber': '1'}}}}} +