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"
+ 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'}}}}}
+