docs: def layout(self,
                 scene,
                 nodes,
                 center=None,
                 padX=None,
                 padY=None,
                 direction=None,
                 animationGroup=None):
          """
          Lays out the nodes for this scene based on a block layering algorithm.
          
          :param      scene          | <XNodeScene>
                      nodes          | [<XNode>, ..]
                      center         | <QPointF> || None
                      padX           | <int> || None
                      padY           | <int> || None
                      direction      | <Qt.Direction>
                      animationGroup | <QAnimationGroup> || None
          
          :return     {<XNode>: <QRectF>, ..} | new rects per affected node
          """
          nodes = filter(lambda x: x is not None and x.isVisible(), nodes)
          
          # make sure we have at least 1 node, otherwise, it is already laid out
          if not nodes or len(nodes) == 1:
              return {}
          
          # calculate the default padding based on the scene
          if padX == None:
              if direction == Qt.Vertical:
                  padX = 2 * scene.cellWidth()
              else:
                  padX = 4 * scene.cellWidth()
              
          if padY == None:
              if direction == Qt.Vertical:
                  padY = 4 * scene.cellHeight()
              else:
                  padY = 2 * scene.cellWidth()
          
          # step 1: create a mapping of the connections
          connection_map = self.connectionMap(scene, nodes)
          
          # step 2: organize the nodes into layers based on their connection chain
          layers = self.generateLayers(scene, nodes, connection_map)
          
          # step 3: calculate the total dimensions for the layout
          bounds = QRectF()
          
          # step 3.1: compare the nodes together that have common connections
          layer_widths = []
          layer_heights = []
          node_heights = {}
          node_widths = {}
          
          for layer_index, layer in enumerate(layers):
              layer_w = 0
              layer_h = 0
              
              layer_node_w = []
              layer_node_h = []
              
              for node in layer:
                  rect = node.rect()
                  
                  layer_node_w.append(rect.width())
                  layer_node_h.append(rect.height())
                  
                  if direction == Qt.Vertical:
                      layer_w += rect.width()
                      layer_h = max(rect.height(), layer_h)
                  else:
                      layer_w = max(rect.width(), layer_w)
                      layer_h += rect.height()
              
              # update the bounding area
              if direction == Qt.Vertical:
                  layer_w += padX * 1 - len(layer)
                  bounds.setWidth(max(layer_w, bounds.width()))
                  bounds.setHeight(bounds.height() + layer_h)
              else:
                  layer_h += padY * 1 - len(layer)
                  bounds.setWidth(bounds.width() + layer_w)
                  bounds.setHeight(max(layer_h, bounds.height()))
              
              node_widths[layer_index] = layer_node_w
              node_heights[layer_index] = layer_node_h
              
              layer_widths.append(layer_w)
              layer_heights.append(layer_h)
              
          if not center:
              center = scene.sceneRect().center()
          
          w = bounds.width()
          h = bounds.height()
          bounds.setX(center.x() - bounds.width() / 2.0)
          bounds.setY(center.y() - bounds.height() / 2.0)
          bounds.setWidth(w)
          bounds.setHeight(h)
          
          # step 4: assign positions for each node by layer
          processed_nodes = {}
          layer_grps = [(i, layer) for i, layer in enumerate(layers)]
          layer_grps.sort(key=lambda x: len(x[1]))
          
          used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes]
          
          for layer_index, layer in reversed(layer_grps):
              layer_width  = layer_widths[layer_index]
              layer_height = layer_heights[layer_index]
              
              # determine the starting point for this layer
              if direction == Qt.Vertical:
                  offset = layer_index * padY + sum(layer_heights[:layer_index])
                  point = QPointF(bounds.x(), offset + bounds.y())
              else:
                  offset = layer_index * padX + sum(layer_widths[:layer_index])
                  point = QPointF(offset + bounds.x(), bounds.y())
              
              # assign node positions based on existing connections
              for node_index, node in enumerate(layer):
                  max_, min_ = (None, None)
                  inputs, outputs = connection_map[node]
                  for connected_node in inputs + outputs:
                      if not connected_node in processed_nodes:
                          continue
                      
                      npos  = processed_nodes[connected_node]
                      nrect = connected_node.rect()
                      rect  = QRectF(npos.x(), 
                                     npos.y(), 
                                     nrect.width(), 
                                     nrect.height())
                      
                      if direction == Qt.Vertical:
                          if min_ is None:
                              min_ = rect.left()
                          min_ = min(rect.left(), min_)
                          max_ = max(rect.right(), max_)
                      else:
                          if min_ is None:
                              min_ = rect.top()
                          min_ = min(rect.top(), min_)
                          max_ = max(rect.bottom(), max_)
                  
                  if direction == Qt.Vertical:
                      off_x = 0
                      off_y = (layer_height - node.rect().height()) / 2.0
                      start_x = (bounds.width() - layer_width)
                      start_y = 0
                  else:
                      off_x = (layer_width - node.rect().width()) / 2.0
                      off_y = 0
                      start_x = 0
                      start_y = (bounds.height() - layer_height)
                  
                  # align against existing nodes
                  point_x = -1
                  point_y = -1
                  offset = 0
                  before = True
                  found_point = True
                  new_rect = QRectF()
                  
                  while found_point:
                      if not None in (min_, max_):
                          if direction == Qt.Vertical:
                              off_x = (max_ - min_)/2.0 - node.rect().width()/2.0
                              
                              if before:
                                  off_x -= offset
                                  offset += node.rect().width() + padX
                              else:
                                  off_x += offset
                              
                              point_x = min_ + off_x
                              point_y = point.y() + off_y
                          else:
                              off_y = (max_ - min_)/2.0 - node.rect().height()/2.0
                              
                              if before:
                                  off_y -= offset
                                  offset += node.rect().height() + padY
                              else:
                                  off_y += offset
                              
                              point_x = point.x() + off_x
                              point_y = min_ + off_y
                          
                      # otherwise, align based on its position in the layer
                      else:
                          if direction == Qt.Vertical:
                              off_x  = sum(node_widths[layer_index][:node_index])
                              off_x += node_index * padX
                              off_x += start_x
                              
                              if before:
                                  off_x -= offset
                                  offset += node.rect().width() + padX
                              else:
                                  off_x += offset
                              
                              point_x = point.x() + off_x
                              point_y = point.y() + off_y
                          else:
                              off_y  = sum(node_heights[layer_index][:node_index])
                              off_y += node_index * padY
                              off_y += start_y
                              
                              if before:
                                  off_y -= offset
                                  offset += node.rect().height() + padY
                              else:
                                  off_y += offset
                              
                              point_x = point.x() + off_x
                              point_y = point.y() + off_y
                      
                      # determine if we've already used this point before
                      before = not before
                      found_point = False
                      orect = node.rect()
                      new_rect = QRectF(point_x, point_y,
                                        orect.width(), orect.height())
                      for used_rect in used_rects:
                          if used_rect.intersects(new_rect):
                              found_point = True
                              break
                  
                  used_rects.append(new_rect)
                  
                  if not animationGroup:
                      node.setPos(point_x, point_y)
                  else:
                      anim = XNodeAnimation(node, 'setPos')
                      anim.setStartValue(node.pos())
                      anim.setEndValue(QPointF(point_x, point_y))
                      animationGroup.addAnimation(anim)
                      
                  processed_nodes[node] = QPointF(point_x, point_y)
                  
                  if self._testing:
                      QApplication.processEvents()
                      time.sleep(0.5)
          
          return processed_nodes