# # Copyright (c) 2004, 2005, 2006 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 storage for dimension objects # import math from PythonCAD.Generic import dimension from PythonCAD.Generic import quadtree from PythonCAD.Generic import point from PythonCAD.Generic import circle from PythonCAD.Generic import arc from PythonCAD.Generic import tolerance from PythonCAD.Generic import util # # Dimension storage # class DimQuadtree(quadtree.Quadtree): def __init__(self): super(DimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.Dimension): raise TypeError, "Invalid Dimension object: " + `obj` if obj in self: return _dxmin, _dymin, _dxmax, _dymax = obj.getBounds() _node = self.getTreeRoot() _bounds = _node.getBoundary() _xmin = _ymin = _xmax = _ymax = None _resize = False if _bounds is None: # first node in tree _resize = True _xmin = _dxmin - 1.0 _ymin = _dymin - 1.0 _xmax = _dxmax + 1.0 _ymax = _dymax + 1.0 else: _xmin, _ymin, _xmax, _ymax = _bounds if _dxmin < _xmin: _xmin = _dxmin - 1.0 _resize = True if _dxmax > _xmax: _xmax = _dxmax + 1.0 _resize = True if _dymin < _ymin: _ymin = _dymin - 1.0 _resize = True if _dymax > _ymax: _ymax = _dymax + 1.0 _resize = True if _resize: self.resize(_xmin, _ymin, _xmax, _ymax) _node = self.getTreeRoot() _nodes = [_node] while len(_nodes): _node = _nodes.pop() _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if ((_dxmin > _xmax) or (_dymin > _ymax) or (_dxmax < _xmin) or (_dymax < _ymin)): continue if _node.hasSubnodes(): _xmid = (_xmin + _xmax)/2.0 _ymid = (_ymin + _ymax)/2.0 _ne = _nw = _sw = _se = True if _dxmax < _xmid: # dim on left side _ne = _se = False if _dxmin > _xmid: # dim on right side _nw = _sw = False if _dymax < _ymid: # dim below _nw = _ne = False if _dymin > _ymid: # dim above _sw = _se = False if _ne: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NENODE)) if _nw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NWNODE)) if _sw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SWNODE)) if _se: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SENODE)) else: if obj.inRegion(_xmin, _ymin, _xmax, _ymax): _node.addObject(obj) super(DimQuadtree, self).addObject(obj) obj.connect('moved', self._moveDim) def delObject(self, obj): if obj not in self: return _dxmin, _dymin, _dxmax, _dymax = obj.getBounds() _xmin = _ymin = _xmax = _ymax = None _pdict = {} _nodes = [self.getTreeRoot()] while len(_nodes): _node = _nodes.pop() _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if ((_dxmin > _xmax) or (_dxmax < _xmin) or (_dymin > _ymax) or (_dymax < _ymin)): continue if _node.hasSubnodes(): _xmid = (_xmin + _xmax)/2.0 _ymid = (_ymin + _ymax)/2.0 _ne = _nw = _sw = _se = True if _dxmax < _xmid: # dim on left side _ne = _se = False if _dxmin > _xmid: # dim on right side _nw = _sw = False if _dymax < _ymid: # dim below _nw = _ne = False if _dymin > _ymid: # dim above _sw = _se = False if _ne: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NENODE)) if _nw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NWNODE)) if _sw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SWNODE)) if _se: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SENODE)) else: _node.delObject(obj) # dim may not be in the node ... _parent = _node.getParent() if _parent is not None: _pid = id(_parent) if _pid not in _pdict: _pdict[_pid] = _parent super(DimQuadtree, self).delObject(obj) obj.disconnect(self) for _parent in _pdict.values(): self.purgeSubnodes(_parent) def find(self, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _p1 = args[0] if not isinstance(_p1, point.Point): raise TypeError, "Invalid point: " + `type(_p1)` _p2 = args[1] if not isinstance(_p2, point.Point): raise TypeError, "Invalid point: " + `type(_p2)` _dims = [] if len(self) > 0: _l1 = _p1.getParent() _l2 = _p2.getParent() for _dim in self.getObjects(): _dp1, _dp2 = _dim.getDimPoints() _dl1 = _dp1.getParent() _dl2 = _dp2.getParent() if ((_l1 is _dl1) and (_p1 is _dp1) and (_l2 is _dl2) and (_p2 is _dp2)): _dims.append(_dim) if ((_l1 is _dl2) and (_p1 is _dp2) and (_l2 is _dl1) and (_p2 is _dp1)): _dims.append(_dim) return _dims def _moveDim(self, obj, *args): if obj not in self: raise ValueError, "Dimension not stored in Quadtree: " + `obj` _alen = len(args) if _alen < 4: raise ValueError, "Invalid argument count: %d" % _alen _dxmin = util.get_float(args[0]) _dymin = util.get_float(args[1]) _dxmax = util.get_float(args[2]) _dymax = util.get_float(args[3]) _nodes = [self.getTreeRoot()] while len(_nodes): _node = _nodes.pop() _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if ((_dxmin > _xmax) or (_dxmax < _xmin) or (_dymin > _ymax) or (_dymax < _ymin)): continue if _node.hasSubnodes(): _xmid = (_xmin + _xmax)/2.0 _ymid = (_ymin + _ymax)/2.0 _ne = _nw = _sw = _se = True if _dxmax < _xmid: # dim on left side _ne = _se = False if _dxmin > _xmid: # dim on right side _nw = _sw = False if _dymax < _ymid: # dim below _nw = _ne = False if _dymin > _ymid: # dim above _sw = _se = False if _ne: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NENODE)) if _nw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.NWNODE)) if _sw: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SWNODE)) if _se: _nodes.append(_node.getSubnode(quadtree.QTreeNode.SENODE)) else: _node.delObject(obj) # dim may not be in node ... super(DimQuadtree, self).delObject(obj) obj.disconnect(self) self.addObject(obj) def getClosest(self, x, y, tol=tolerance.TOL): _x = util.get_float(x) _y = util.get_float(y) _t = tolerance.toltest(tol) _dim = _tsep = None _bailout = False _ddict = {} _nodes = [self.getTreeRoot()] while len(_nodes): _node = _nodes.pop() _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if ((_x < (_xmin - _t)) or (_x > (_xmax + _t)) or (_y < (_ymin - _t)) or (_y > (_ymax + _t))): continue if _node.hasSubnodes(): _nodes.extend(_node.getSubnodes()) else: for _d in _node.getObjects(): _did = id(_d) if _did not in _ddict: _pt = _d.mapCoords(_x, _y, _t) if _pt is not None: _px = _py = _pt _sep = math.hypot((_px - _x), (_py - _y)) if _tsep is None: _tsep = _sep _dim = _d else: if _sep < _tsep: _tsep = _sep _dim = _d if _dim is not None and _sep < 1e-10: _bailout = True break if _bailout: break return _dim def getInRegion(self, xmin, ymin, xmax, ymax): _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Illegal values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Illegal values: ymax < ymin" _dims = [] if not len(self): return _dims _nodes = [self.getTreeRoot()] _ddict = {} while len(_nodes): _node = _nodes.pop() if _node.hasSubnodes(): for _subnode in _node.getSubnodes(): _sxmin, _symin, _sxmax, _symax = _subnode.getBoundary() if ((_sxmin > _xmax) or (_symin > _ymax) or (_sxmax < _xmin) or (_symax < _ymin)): continue _nodes.append(_subnode) else: for _dim in _node.getObjects(): _dimid = id(_dim) if _dimid not in _ddict: if _dim.inRegion(_xmin, _ymin, _xmax, _ymax): _dims.append(_dim) _ddict[_dimid] = True return _dims # # LinearDimension Quadtree # class LDimQuadtree(DimQuadtree): def __init__(self): super(LDimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.LinearDimension): raise TypeError, "Invalid LinearDimension object: " + `obj` super(LDimQuadtree, self).addObject(obj) # # HorizontalDimension Quadtree # class HDimQuadtree(DimQuadtree): def __init__(self): super(HDimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.HorizontalDimension): raise TypeError, "Invalid HorizontalDimension object: " + `obj` super(HDimQuadtree, self).addObject(obj) # # VerticalDimension Quadtree # class VDimQuadtree(DimQuadtree): def __init__(self): super(VDimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.VerticalDimension): raise TypeError, "Invalid VerticalDimension object: " + `obj` super(VDimQuadtree, self).addObject(obj) # # RadialDimension Quadtree # class RDimQuadtree(DimQuadtree): def __init__(self): super(RDimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.RadialDimension): raise TypeError, "Invalid RadalDimension object: " + `obj` super(RDimQuadtree, self).addObject(obj) def find(self, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _c1 = args[1] if not isinstance(_c1, (circle.Circle, arc.Arc)): raise TypeError, "Invalid circle/arc: " + `type(_c1)` _dims = [] if len(self) > 0: _l1 = _c1.getParent() for _rdim in self.getObjects(): _rc = _rdim.getDimCircle() _rl = _rc.getParent() if _rl is _l1 and _rc is _c1: _dims.append(_dim) return _dims # # AngularDimension Quadtree # class ADimQuadtree(DimQuadtree): def __init__(self): super(ADimQuadtree, self).__init__() def addObject(self, obj): if not isinstance(obj, dimension.AngularDimension): raise TypeError, "Invalid AngularDimension object: " + `obj` super(ADimQuadtree, self).addObject(obj) def find(self, *args): _alen = len(args) if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _vp = args[0] if not isinstance(_vp, point.Point): raise TypeError, "Invalid point: " + `type(_vp)` _p1 = args[1] if not isinstance(_p1, point.Point): raise TypeError, "Invalid point: " + `type(_p1)` _p2 = args[2] if not isinstance(_p2, point.Point): raise TypeError, "Invalid point: " + `type(_p2)` _dims = [] if len(self) > 0: _vl = _vp.getParent() _l1 = _p1.getParent() _l2 = _p2.getParent() for _adim in self.getObjects(): _avp, _ap1, _ap2 = _adim.getDimPoints() _avl = _avp.getParent() _al1 = _ap1.getParent() _al2 = _ap2.getParent() if ((_vl is _avl) and (_vp is _avp) and (_l1 is _al1) and (_p1 is _ap1) and (_l2 is _al2) and (_p2 is _ap2)): _dims.append(_dim) if ((_vl is _avl) and (_vp is _avp) and (_l1 is _al2) and (_p1 is _ap2) and (_l2 is _al1) and (_p2 is _ap1)): _dims.append(_dim) return _dims