#
# Copyright (c) 2003, 2004, 2005 Art Haas
#
# This file is part of PythonCAD.
#
# PythonCAD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PythonCAD is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
# quadtree class definition
#
# TODO: Look at using weak references ...
from __future__ import generators
from PythonCAD.Generic import message
class QTreeNode(message.Messenger):
__messages = {
'subdivided' : True,
'reparented' : True,
'adjusted' : True,
'full' : True,
}
NENODE = 1
NWNODE = 2
SWNODE = 3
SENODE = 4
def __init__(self):
super(QTreeNode, self).__init__()
self.__subnodes = None
self.__bounds = None
self.__parent = None
self.__threshold = 5
self.__objects = []
def __len__(self):
return len(self.__objects)
def setBoundary(self, xmin, ymin, xmax, ymax):
if self.__subnodes is not None or self.__parent is not None:
raise RuntimeError, "Node cannot have boundary changed."
_xmin = xmin
if not isinstance(_xmin, float):
_xmin = float(xmin)
_ymin = ymin
if not isinstance(_ymin, float):
_ymin = float(ymin)
_xmax = xmax
if not isinstance(_xmax, float):
_xmax = float(xmax)
if _xmax < _xmin:
raise ValueError, "Illegal values: xmax < xmin"
_ymax = ymax
if not isinstance(_ymax, float):
_ymax = float(ymax)
if _ymax < _ymin:
raise ValueError, "Illegal values: ymax < ymin"
self.__bounds = (_xmin, _ymin, _xmax, _ymax)
def unsetBoundary(self):
if self.__subnodes is not None or len(self.__objects) != 0:
raise RuntimeError, "Node cannot have boundary changed."
self.__bounds = None
def getBoundary(self):
return self.__bounds
def setThreshold(self, count):
_t = self.__threshold
_count = count
if not isinstance(_count, int):
_count = int(count)
if _count < 1:
raise ValueError, "Invalid threshold: %d" % _count
if _count != _t:
self.__threshold = _count
self.sendMessage('adjusted', _t)
if len(self) > _count:
self.sendMessage('full')
def getThreshold(self):
return self.__threshold
def setParent(self, parent):
_p = self.__parent
if parent is not None and not isinstance(parent, QTreeNode):
raise TypeError, "Invalid QTreeNode parent: " + `type(parent)`
if parent is not _p:
self.__parent = parent
self.sendMessage('reparented', _p)
def getParent(self):
return self.__parent
def addObject(self, obj):
_seen = False
for _obj in self.__objects:
if _obj is obj:
_seen = True
break
if not _seen:
self.__objects.append(obj)
if self.canSubdivide():
self.sendMessage('full')
def getSubnode(self, subnode):
if self.__subnodes is None:
raise RuntimeError, "QTreeNode has no subnodes."
_ne, _nw, _sw, _se = self.__subnodes
if subnode == QTreeNode.NENODE:
_sub = _ne
elif subnode == QTreeNode.NWNODE:
_sub = _nw
elif subnode == QTreeNode.SWNODE:
_sub = _sw
elif subnode == QTreeNode.SENODE:
_sub = _se
else:
raise ValueError, "Unknown subnode code: %d" % subnode
return _sub
def delObject(self, obj):
for _i in range(len(self.__objects)):
if obj is self.__objects[_i]:
del self.__objects[_i]
break
def getObjects(self):
return self.__objects[:]
def delObjects(self):
del self.__objects[:]
def hasSubnodes(self):
return self.__subnodes is not None
def getSubnodes(self):
return self.__subnodes
def delSubnodes(self):
if self.__subnodes is None:
raise RuntimeError, "QTreeNode has no subnodes."
for _subnode in self.__subnodes:
_subnode.setParent(None)
self.__subnodes = None
def canSubdivide(self):
if not len(self) > self.__threshold:
return False
_objs = self.__objects
_flag = True
for _obj in _objs:
if not hasattr(_obj, '__eq__'):
_flag = False
break
if not _flag:
return len(self) > self.__threshold
#
# _test is an array of booleans:
# _test[i] is True => object is unique
# _test[i] is False => object is equal to another in list
#
# _test[0] is always True, so test from _test[1] to end
#
_test = [True] * len(_objs)
_max = len(_test)
for _i in range(_max - 1):
if _test[_i]: # current object is unique ...
for _j in range((_i + 1), _max):
if _objs[_j] == _objs[_i]:
_test[_j] = False
return _test.count(True) > self.__threshold
def subdivide(self):
if len(self) < self.__threshold:
return
_xmin, _ymin, _xmax, _ymax = self.__bounds
_xmid = (_xmin + _xmax)/2.0
_ymid = (_ymin + _ymax)/2.0
#
# NE Node
#
_nenode = QTreeNode()
_nenode.setBoundary(_xmid, _ymid, _xmax, _ymax)
_nenode.setThreshold(self.__threshold)
_nenode.setParent(self)
#
# NW Node
#
_nwnode = QTreeNode()
_nwnode.setBoundary(_xmin, _ymid, _xmid, _ymax)
_nwnode.setThreshold(self.__threshold)
_nwnode.setParent(self)
#
# SW Node
#
_swnode = QTreeNode()
_swnode.setBoundary(_xmin, _ymin, _xmid, _ymid)
_swnode.setThreshold(self.__threshold)
_swnode.setParent(self)
#
# SE Node
#
_senode = QTreeNode()
_senode.setBoundary(_xmid, _ymin, _xmax, _ymid)
_senode.setThreshold(self.__threshold)
_senode.setParent(self)
#
self.__subnodes = (_nenode, _nwnode, _swnode, _senode)
self.delObjects()
self.sendMessage('subdivided')
def sendsMessage(self, m):
if m in QTreeNode.__messages:
return True
return super(QTreeNode, self).sendsMessage(m)
class Quadtree(message.Messenger):
__messages = { }
def __init__(self):
super(Quadtree, self).__init__()
self.__objects = {}
self.__queued = []
self.__splitnode = None
self.__splitting = False
self.__node = QTreeNode()
self.__node.connect('full', self._splitTreeNode)
def __nonzero__(self):
return len(self.__objects) != 0
def __len__(self):
return len(self.__objects)
def __contains__(self, obj):
_oid = id(obj)
return _oid in self.__objects
def getTreeRoot(self):
return self.__node
def getNodes(self, *args):
_nodes = [self.__node]
while len(_nodes):
_node = _nodes.pop()
if _node.hasSubnodes():
_nodes.extend(_node.getSubnodes())
else:
yield _node
def addObject(self, obj):
_oid = id(obj)
if _oid not in self.__objects:
self.__objects[_oid] = obj
if self.__splitnode is not None and not self.__splitting:
self.__splitting = True
_node = self.__splitnode
_objs = _node.getObjects()
_node.subdivide()
for _subnode in _node.getSubnodes():
_subnode.connect('full', self._splitTreeNode)
_root = self.__node
self.__node = self.__splitnode
try:
for _obj in _objs:
_oid = id(_obj)
if _oid not in self.__objects:
raise ValueError, "Lost object: " + str(_obj)
del self.__objects[_oid]
self.addObject(_obj)
if _oid not in self.__objects:
raise ValueError, "self.addObject() failed: " + str(_obj)
finally:
self.__node = _root
self.__splitnode = None
self.__splitting = False
# set to False for Quadtree consistency testing
if True or self.__splitnode is not None:
return
_objcount = len(self.__objects)
_nodes = [self.__node]
_nobj = {}
while len(_nodes):
_node = _nodes.pop()
if _node.hasSubnodes():
_nodes.extend(_node.getSubnodes())
else:
for _obj in _node.getObjects():
_oid = id(_obj)
if _oid not in _nobj:
_nobj[_oid] = True
_nc = len(_nobj)
if _nc > _objcount:
print "Count inconsistency: %d > %d" % (_nc, _objcount)
print "Quadtree objects:"
for _obj in self.__objects.values():
print "obj: " + str(_obj)
_bounds = self.__node.getBoundary()
print "tree bounds: " + str(_bounds)
_nodes = [self.__node]
_nobj = {}
while len(_nodes):
_node = _nodes.pop()
_bounds = _node.getBoundary()
if _node.hasSubnodes():
print "Node with subnodes: " + str(_bounds)
_nodes.extend(_node.getSubnodes())
else:
print "Base node: " + str(_bounds)
for _obj in _node.getObjects():
print "obj: " + str(_obj)
_oid = id(_obj)
if _oid not in _nobj:
_nobj[_oid] = _obj
if _oid not in self.__objects:
print "###Object lost###"
raise RuntimeError, "Inconsistent quadtree"
def getObject(self, obj):
_oid = id(obj)
return self.__objects.get(_oid) # returns None if _oid not found
def getObjects(self):
return self.__objects.values()
def delObject(self, obj):
_oid = id(obj)
if _oid in self.__objects:
del self.__objects[_oid]
def delObjects(self):
_nodes = [self.__node]
while len(_nodes):
_node = _nodes.pop()
if _node.hasSubnodes():
_nodes.extend(_node.getSubnodes())
_node.delSubnodes()
else:
for _obj in _node.getObjects():
_obj.disconnect(self)
_node.delObjects()
if _node.getParent() is not None:
_node.setParent(None)
_node.disconnect(self)
self.__objects.clear()
self.__node.unsetBoundary()
def queueObject(self, obj):
_oid = id(obj)
if _oid in self.__objects:
raise ValueError, "Object already stored in Quadtree: " + `obj`
self.__queued.append(obj)
def emptyQueue(self):
_objs = self.__queued[:]
del self.__queued[:]
return _objs
def find(self, *params):
return []
def getClosest(self, x, y, tol):
return None
def getInRegion(self, xmin, ymin, xmax, ymax):
return []
def resize(self, xmin, ymin, xmax, ymax):
_xmin = xmin
if not isinstance(_xmin, float):
_xmin = float(xmin)
_ymin = ymin
if not isinstance(_ymin, float):
_ymin = float(ymin)
_xmax = xmax
if not isinstance(_xmax, float):
_xmax = float(xmax)
if _xmax < _xmin:
raise ValueError, "Illegal values: xmax < xmin"
_ymax = ymax
if not isinstance(_ymax, float):
_ymax = float(ymax)
if _ymax < _ymin:
raise ValueError, "Illegal values: ymax < ymin"
_root = self.__node
if _root is not self.__splitnode:
if not _root.hasSubnodes():
_root.setBoundary(_xmin, _ymin, _xmax, _ymax)
else:
_objs = self.__objects.values()
self.delObjects()
_root.setBoundary(_xmin, _ymin, _xmax, _ymax)
for _obj in _objs:
self.addObject(_obj)
def _splitTreeNode(self, node, *args):
if self.__splitnode is None:
self.__splitnode = node
def purgeSubnodes(self, node):
if not isinstance(node, QTreeNode):
raise TypeError, "Invalid node: " + `type(node)`
_tmpnode = node
_parent = node.getParent()
while _parent is not None:
_tmpnode = _parent
_parent = _tmpnode.getParent()
if self.__node is not _tmpnode:
raise ValueError, "Node not in tree."
if node.hasSubnodes():
_flag = True
_count = 0
for _subnode in node.getSubnodes():
if _subnode.hasSubnodes():
_flag = False
break
_count = _count + len(_subnode)
if _flag and _count < node.getThreshold():
_objs = []
for _subnode in node.getSubnodes():
_objs.extend(_subnode.getObjects())
node.delSubnodes()
for _obj in _objs:
Quadtree.delObject(self, _obj)
self.addObject(_obj)
def sendsMessage(self, m):
if m in Quadtree.__messages:
return True
return super(Quadtree, self).sendsMessage(m)