#
# Copyright (c) 2002, 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
#
#
# a construction line defined by two points
#
from __future__ import generators
import math
from PythonCAD.Generic import conobject
from PythonCAD.Generic import tolerance
from PythonCAD.Generic import point
from PythonCAD.Generic import quadtree
from PythonCAD.Generic import util
class CLine(conobject.ConstructionObject):
"""A class for construction lines defined by two distinct Points.
A CLine object is derived from the conobject class, so it shares
the functionality of that class. In addition, a CLine instance
has two attributes:
p1: A Point object representing the first keypoint
p2: A Point object representing the second keypoint
A CLine has the following methods:
getKeypoints(): Return the two points the CLine is defined by.
{get/set}P1: Get/Set the first keypoint of the CLine.
{get/set}P2: Get/Set the second keypoint of the CLine.
move(): Move a CLine
mapCoords(): Return the nearest point on a CLine to a coordinate pair.
inRegion(): Return whether the CLine passes through a bounded region.
clone(): Return an identical copy of a CLine.
"""
__messages = {
'moved' : True,
'keypoint_changed' : True
}
def __init__(self, p1, p2, **kw):
"""Initialize a CLine object.
CLine(p1, p2)
Both arguments are Point objects that the CLine passes through.
"""
_p1 = p1
if not isinstance(_p1, point.Point):
_p1 = point.Point(p1)
_p2 = p2
if not isinstance(_p2, point.Point):
_p2 = point.Point(p2)
if _p1 is _p2:
raise ValueError, "A CLine must have two different keypoints."
super(CLine, self).__init__(**kw)
self.__p1 = _p1
_p1.storeUser(self)
_p1.connect('moved', self.__movePoint)
_p1.connect('change_pending', self.__pointChangePending)
_p1.connect('change_complete', self.__pointChangeComplete)
self.__p2 = _p2
_p2.storeUser(self)
_p2.connect('moved', self.__movePoint)
_p2.connect('change_pending', self.__pointChangePending)
_p2.connect('change_complete', self.__pointChangeComplete)
def __eq__(self, obj):
"""Compare one CLine to another for equality.
"""
if not isinstance(obj, CLine):
return False
if obj is self:
return True
_sp1, _sp2 = self.getKeypoints()
_op1, _op2 = obj.getKeypoints()
_sv = abs(_sp1.x - _sp2.x) < 1e-10
_sh = abs(_sp1.y - _sp2.y) < 1e-10
_ov = abs(_op1.x - _op2.x) < 1e-10
_oh = abs(_op1.y - _op2.y) < 1e-10
_val = False
if _sv and _ov: # both vertical
if abs(_sp1.x - _op1.x) < 1e-10:
_val = True
elif _sh and _oh: # both horizontal
if abs(_sp1.y - _op1.y) < 1e-10:
_val = True
else:
if (not (_sv or _sh)) and (not (_ov or _oh)):
_sx1, _sy1 = _sp1.getCoords()
_sx2, _sy2 = _sp2.getCoords()
_ox1, _oy1 = _op1.getCoords()
_ox2, _oy2 = _op2.getCoords()
_ms = (_sy2 - _sy1)/(_sx2 - _sx1)
_bs = _sy1 - (_ms * _sx1)
_ty = (_ms * _ox1) + _bs
if abs(_ty - _oy1) < 1e-10:
_ty = (_ms * _ox2) + _bs
if abs(_ty - _oy2) < 1e-10:
_val = True
return _val
def __ne__(self, obj):
"""Compare one CLine to another for inequality.
"""
if not isinstance(obj, CLine):
return True
if obj is self:
return False
_sp1, _sp2 = self.getKeypoints()
_op1, _op2 = obj.getKeypoints()
_sv = abs(_sp1.x - _sp2.x) < 1e-10
_sh = abs(_sp1.y - _sp2.y) < 1e-10
_ov = abs(_op1.x - _op2.x) < 1e-10
_oh = abs(_op1.y - _op2.y) < 1e-10
_val = True
if _sv and _ov: # both vertical
if abs(_sp1.x - _op1.x) < 1e-10:
_val = False
elif _sh and _oh: # both horizontal
if abs(_sp1.y - _op1.y) < 1e-10:
_val = False
else:
if (not (_sv or _sh)) and (not (_ov or _oh)):
_sx1, _sy1 = _sp1.getCoords()
_sx2, _sy2 = _sp2.getCoords()
_ox1, _oy1 = _op1.getCoords()
_ox2, _oy2 = _op2.getCoords()
_ms = (_sy2 - _sy1)/(_sx2 - _sx1)
_bs = _sy1 - (_ms * _sx1)
_ty = (_ms * _ox1) + _bs
if abs(_ty - _oy1) < 1e-10:
_ty = (_ms * _ox2) + _bs
if abs(_ty - _oy2) < 1e-10:
_val = False
return _val
def __str__(self):
return "Construction Line through %s and %s" % (self.__p1, self.__p2)
def finish(self):
self.__p1.disconnect(self)
self.__p1.freeUser(self)
self.__p2.disconnect(self)
self.__p2.freeUser(self)
self.__p1 = self.__p2 = None
super(CLine, self).finish()
def getValues(self):
_data = super(CLine, self).getValues()
_data.setValue('type', 'cline')
_data.setValue('p1', self.__p1.getID())
_data.setValue('p2', self.__p2.getID())
return _data
def getKeypoints(self):
"""Return the two keypoints of this CLine.
getKeypoints()
"""
return self.__p1, self.__p2
def getP1(self):
"""Return the first keypoint Point of the CLine.
getP1()
"""
return self.__p1
def setP1(self, p):
"""Set the first keypoint Point of the CLine.
setP1(p)
Argument 'p' must be a Point.
"""
if self.isLocked():
raise RuntimeError, "Setting keypoint not allowed - object locked."
if not isinstance(p, point.Point):
raise TypeError, "Invalid keypoint: " + `type(p)`
if p is self.__p2 or p == self.__p2:
raise ValueError, "CLines must have two different keypoints."
_kp = self.__p1
if _kp is not p:
_kp.disconnect(self)
_kp.freeUser(self)
self.startChange('keypoint_changed')
self.__p1 = p
self.endChange('keypoint_changed')
self.sendMessage('keypoint_changed', _kp, p)
p.storeUser(self)
p.connect('moved', self.__movePoint)
p.connect('change_pending', self.__pointChangePending)
p.connect('change_complete', self.__pointChangeComplete)
if abs(_kp.x - p.x) > 1e-10 or abs(_kp.y - p.y) > 1e-10:
_x, _y = self.__p2.getCoords()
self.sendMessage('moved', _kp.x, _kp.y, _x, _y)
self.modified()
p1 = property(getP1, setP1, None, "First keypoint of the CLine.")
def getP2(self):
"""Return the second keypoint Point of the CLine.
getP2()
"""
return self.__p2
def setP2(self, p):
"""Set the second keypoint Point of the CLine.
setP2(p)
Argument 'p' must be a Point.
"""
if self.isLocked():
raise RuntimeError, "Setting keypoint not allowed - object locked."
if not isinstance(p, point.Point):
raise TypeError, "Invalid keypoint: " + `type(p)`
if p is self.__p1 or p == self.__p1:
raise ValueError, "CLines must have two different keypoints."
_kp = self.__p2
if _kp is not p:
_kp.disconnect(self)
_kp.freeUser(self)
self.startChange('keypoint_changed')
self.__p2 = p
self.endChange('keypoint_changed')
self.sendMessage('keypoint_changed', _kp, p)
p.storeUser(self)
p.connect('moved', self.__movePoint)
p.connect('change_pending', self.__pointChangePending)
p.connect('change_complete', self.__pointChangeComplete)
if abs(_kp.x - p.x) > 1e-10 or abs(_kp.y - p.y) > 1e-10:
_x, _y = self.__p1.getCoords()
self.sendMessage('moved', _x, _y, _kp.x, _kp.y)
self.modified()
p2 = property(getP2, setP2, None, "Second keypoint of the CLine.")
def move(self, dx, dy):
"""Move a CLine.
move(dx, dy)
The first argument gives the x-coordinate displacement,
and the second gives the y-coordinate displacement. Both
values should be floats.
"""
if self.isLocked() or self.__p1.isLocked() or self.__p2.isLocked():
raise RuntimeError, "Moving CLine not allowed - object locked."
_dx = util.get_float(dx)
_dy = util.get_float(dy)
if abs(_dx) > 1e-10 or abs(_dy) > 1e-10:
_x1, _y1 = self.__p1.getCoords()
_x2, _y2 = self.__p2.getCoords()
self.ignore('moved')
try:
self.__p1.move(_dx, _dy)
self.__p2.move(_dx, _dy)
finally:
self.receive('moved')
self.sendMessage('moved', _x1, _y1, _x2, _y2)
def mapCoords(self, x, y, tol=tolerance.TOL):
"""Return the nearest Point on the CLine to a coordinate pair.
mapCoords(x, y[, tol])
The function has two required arguments:
x: A Float value giving the 'x' coordinate
y: A Float value giving the 'y' coordinate
There is a single optional argument:
tol: A float value equal or greater than 0.0
This function is used to map a possibly near-by coordinate pair to a
actual Point on the CLine. If the distance between the actual
Point and the coordinates used as an argument is less than the tolerance,
the actual Point is returned. Otherwise, this function returns None.
"""
_x = util.get_float(x)
_y = util.get_float(y)
_t = tolerance.toltest(tol)
_x1, _y1 = self.__p1.getCoords()
_x2, _y2 = self.__p2.getCoords()
_sqlen = pow((_x2 - _x1), 2) + pow((_y2 - _y1), 2)
if _sqlen < 1e-10: # both points the same
raise RuntimeError, "CLine points coincident."
_r = ((_x - _x1)*(_x2 - _x1) + (_y - _y1)*(_y2 - _y1))/_sqlen
_px = _x1 + _r * (_x2 - _x1)
_py = _y1 + _r * (_y2 - _y1)
if abs(_px - _x) < _t and abs(_py - _y) < _t:
return _px, _py
return None
def getProjection(self, x, y):
"""Find the projection point of some coordinates on the CLine.
getProjection(x, y)
Arguments 'x' and 'y' should be float values.
"""
_x = util.get_float(x)
_y = util.get_float(y)
_x1, _y1 = self.__p1.getCoords()
_x2, _y2 = self.__p2.getCoords()
_sqlen = pow((_x2 - _x1), 2) + pow((_y2 - _y1), 2)
_rn = ((_x - _x1) * (_x2 - _x1)) + ((_y - _y1) * (_y2 - _y1))
_r = _rn/_sqlen
_px = _x1 + _r * (_x2 - _x1)
_py = _y1 + _r * (_y2 - _y1)
return _px, _py
def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
"""Return whether or not a CLine passes through a region.
isRegion(xmin, ymin, xmax, ymax)
The four arguments define the boundary of an area, and the
function returns True if the CLine passes within the area.
Otherwise, the function returns False.
"""
_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"
util.test_boolean(fully)
if fully:
return False
_x1, _y1 = self.__p1.getCoords()
_x2, _y2 = self.__p2.getCoords()
_xdiff = _x2 - _x1
_ydiff = _y2 - _y1
_val = False
if _xmin < _x1 < _xmax and _ymin < _y1 < _ymax:
_val = True
elif _xmin < _x2 < _xmax and _ymin < _y2 < _ymax:
_val = True
elif abs(_xdiff) < 1e-10: # vertical line
if _xmin < _x1 < _xmax:
_val = True
elif abs(_ydiff) < 1e-10: # horizontal line
if _ymin < _y1 < _ymax:
_val = True
else:
_slope = _ydiff/_xdiff
_yint = _y1 - _slope*_x1
if _ymin < (_slope*_xmin + _yint) < _ymax: # hits left side
_val = True
elif _ymin < (_slope*_xmax + _yint) < _ymax: # hits right side
_val = True
else: # hits bottom - no need to check top ...
_xymin = (_ymin - _yint)/_slope
if _xmin < _xymin < _xmax:
_val = True
return _val
def __pointChangePending(self, p, *args):
_alen = len(args)
if _alen < 1:
raise ValueError, "Invalid argument count: %d" % _alen
if args[0] == 'moved':
self.startChange('moved')
def __pointChangeComplete(self, p, *args):
_alen = len(args)
if _alen < 1:
raise ValueError, "Invalid argument count: %d" % _alen
if args[0] == 'moved':
self.endChange('moved')
def __movePoint(self, p, *args):
_plen = len(args)
if _plen < 2:
raise ValueError, "Invalid argument count: %d" % _plen
_x = util.get_float(args[0])
_y = util.get_float(args[1])
_p1 = self.__p1
_p2 = self.__p2
if p is _p1:
_x1 = _x
_y1 = _y
_x2, _y2 = _p2.getCoords()
if abs(_p1.x - _x2) < 1e-10 and abs(_p1.y - _y2) < 1e-10:
raise RuntimeError, "CLine points coincident."
elif p is _p2:
_x1, y1 = _p1.getCoords()
_x2 = _x
_y2 = _y
if abs(_p2.x - _x1) < 1e-10 and abs(_p2.y - _y1) < 1e-10:
raise RuntimeError, "CLine points coincident."
else:
raise ValueError, "Unexpected CLine keypoint: " + `p`
self.sendMessage('moved', _x1, _y1, _x2, _y2)
def clone(self):
"""Create an identical copy of a CLine.
clone()
"""
_cp1 = self.__p1.clone()
_cp2 = self.__p2.clone()
return CLine(_cp1, _cp2)
def clipToRegion(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"
_p1, _p2 = self.getKeypoints()
_x1, _y1 = _p1.getCoords()
_x2, _y2 = _p2.getCoords()
_coords = None
if abs(_x2 - _x1) < 1e-10: # vertical
if _xmin < _x1 < _xmax:
_coords = (_x1, _ymin, _x1, _ymax)
elif abs(_y2 - _y1) < 1e-10: # horiztonal
if _ymin < _y1 < _ymax:
_coords = (_xmin, _y1, _xmax, _y1)
else:
#
# the CLine can be parameterized as
#
# x = u * (x2 - x1) + x1
# y = u * (y2 - y1) + y1
#
# for u = 0, x => x1, y => y1
# for u = 1, x => x2, y => y2
#
# The following is the Liang-Barsky Algorithm
# for segment clipping modified slightly for
# construction lines
#
_dx = _x2 - _x1
_dy = _y2 - _y1
# print "dx: %g; dy: %g" % (_dx, _dy)
_P = [-_dx, _dx, -_dy, _dy]
_q = [(_x1 - _xmin), (_xmax - _x1), (_y1 - _ymin), (_ymax - _y1)]
_u1 = None
_u2 = None
_valid = True
for _i in range(4):
# print "i: %d" % _i
_pi = _P[_i]
_qi = _q[_i]
# print "p[i]: %g; q[i]: %g" % (_pi, _qi)
if abs(_pi) < 1e-10: # this should be caught earlier ...
if _qi < 0.0:
_valid = False
break
else:
_r = _qi/_pi
# print "r: %g" % _r
if _pi < 0.0:
# print "testing u1 ..."
if _u2 is not None and _r > _u2:
# print "r > u2 (%g)" % _u2
_valid = False
break
if _u1 is None or _r > _u1:
# print "setting u1 = r"
_u1 = _r
else:
# print "testing u2 ..."
if _u1 is not None and _r < _u1:
# print "r < u1 (%g)" % _u1
_valid = False
break
if _u2 is None or _r < _u2:
# print "setting u2 = r"
_u2 = _r
if _valid:
_coords = (((_u1 * _dx) + _x1),
((_u1 * _dy) + _y1),
((_u2 * _dx) + _x1),
((_u2 * _dy) + _y1))
return _coords
def sendsMessage(self, m):
if m in CLine.__messages:
return True
return super(CLine, self).sendsMessage(m)
def intersect_region(cl, xmin, ymin, xmax, ymax):
if not isinstance(cl, CLine):
raise TypeError, "Invalid CLine: " + `type(cl)`
_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"
_p1, _p2 = cl.getKeypoints()
_p1x, _p1y = _p1.getCoords()
_p2x, _p2y = _p2.getCoords()
_x1 = _y1 = _x2 = _y2 = None
if abs(_p2x - _p1x) < 1e-10: # vertical
if _xmin < _p1x < _xmax:
_x1 = _p1x
_y1 = _ymin
_x2 = _p1x
_y2 = _ymax
elif abs(_p2y - _p1y) < 1e-10: # horiztonal
if _ymin < _p1y < _ymax:
_x1 = _xmin
_y1 = _p1y
_x2 = _xmax
_y2 = _p1y
else:
_slope = (_p2y - _p1y)/(_p2x - _p1x)
_yint = _p1y - (_p1x * _slope)
#
# find y for x = xmin
#
_yt = (_slope * _xmin) + _yint
if _ymin < _yt < _ymax:
# print "hit at y for x=xmin"
_x1 = _xmin
_y1 = _yt
#
# find y for x = xmax
#
_yt = (_slope * _xmax) + _yint
if _ymin < _yt < _ymax:
# print "hit at y for x=xmax"
if _x1 is None:
_x1 = _xmax
_y1 = _yt
else:
_x2 = _xmax
_y2 = _yt
if _x2 is None:
#
# find x for y = ymin
#
_xt = (_ymin - _yint)/_slope
if _xmin < _xt < _xmax:
# print "hit at x for y=ymin"
if _x1 is None:
_x1 = _xt
_y1 = _ymin
else:
_x2 = _xt
_y2 = _ymin
if _x2 is None:
#
# find x for y = ymax
#
_xt = (_ymax - _yint)/_slope
if _xmin < _xt < _xmax:
# print "hit at x for y=ymax"
if _x1 is None:
_x1 = _xt
_y1 = _ymax
else:
_x2 = _xt
_y2 = _ymax
return _x1, _y1, _x2, _y2
#
# Quadtree CLine storage
#
class CLineQuadtree(quadtree.Quadtree):
def __init__(self):
super(CLineQuadtree, self).__init__()
def getNodes(self, *args):
_alen = len(args)
if _alen != 4:
raise ValueError, "Expected 4 arguments, got %d" % _alen
_x1 = util.get_float(args[0])
_y1 = util.get_float(args[1])
_x2 = util.get_float(args[2])
_y2 = util.get_float(args[3])
_h = abs(_y2 - _y1) < 1e-10
_v = abs(_x2 - _x1) < 1e-10
if _h and _v: # both coords are identical
raise ValueError, "CLine singularity - identical coords."
_nodes = [self.getTreeRoot()]
while len(_nodes):
_node = _nodes.pop()
_xmin, _ymin, _xmax, _ymax = _node.getBoundary()
if _node.hasSubnodes():
_xmid = (_xmin + _xmax)/2.0
_ymid = (_ymin + _ymax)/2.0
_ne = _nw = _sw = _se = False
if _v:
if _x1 < _xmin or _x1 > _xmax:
continue
if _x1 < _xmid: # cline on left
_sw = _nw = True
else:
_se = _ne = True
elif _h:
if _y1 < _ymin or _y1 > _ymax:
continue
if _y1 < _ymid: # cline below
_sw = _se = True
else:
_nw = _ne = True
else:
_ne = _nw = _sw = _se = True
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:
yield _node
def addObject(self, obj):
if not isinstance(obj, CLine):
raise TypeError, "Invalid CLine: " + `type(obj)`
if obj in self:
return
_p1, _p2 = obj.getKeypoints()
_x1, _y1 = _p1.getCoords()
_x2, _y2 = _p2.getCoords()
_bounds = self.getTreeRoot().getBoundary()
_xmin = _ymin = _xmax = _ymax = None
_sxmin = min(_x1, _x2)
_sxmax = max(_x1, _x2)
_symin = min(_y1, _y2)
_symax = max(_y1, _y2)
_resize = False
if _bounds is None: # first node in tree
_resize = True
_xmin = _sxmin - 1.0
_ymin = _symin - 1.0
_xmax = _sxmax + 1.0
_ymax = _symax + 1.0
else:
_xmin, _ymin, _xmax, _ymax = _bounds
if _sxmin < _xmin:
_xmin = _sxmin - 1.0
_resize = True
if _sxmax > _xmax:
_xmax = _sxmax + 1.0
_resize = True
if _symin < _ymin:
_ymin = _symin - 1.0
_resize = True
if _symax > _ymax:
_ymax = _symax + 1.0
_resize = True
if _resize:
self.resize(_xmin, _ymin, _xmax, _ymax)
for _node in self.getNodes(_x1, _y1, _x2, _y2):
_xmin, _ymin, _xmax, _ymax = _node.getBoundary()
if obj.inRegion(_xmin, _ymin, _xmax, _ymax):
_node.addObject(obj)
super(CLineQuadtree, self).addObject(obj)
obj.connect('moved', self._moveCLine)
def delObject(self, obj):
if obj not in self:
return
_p1, _p2 = obj.getKeypoints()
_x1, _y1 = _p1.getCoords()
_x2, _y2 = _p2.getCoords()
_pdict = {}
for _node in self.getNodes(_x1, _y1, _x2, _y2):
_node.delObject(obj) # cline 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(CLineQuadtree, self).delObject(obj)
obj.disconnect(self)
for _parent in _pdict.values():
self.purgeSubnodes(_parent)
def find(self, *args):
_alen = len(args)
if _alen < 4:
raise ValueError, "Invalid argument count: %d" % _alen
_x1 = util.get_float(args[0])
_y1 = util.get_float(args[1])
_x2 = util.get_float(args[2])
_y2 = util.get_float(args[3])
_t = tolerance.TOL
if _alen > 4:
_t = tolerance.toltest(args[4])
_xmin = min(_x1, _x2) - _t
_ymin = min(_y1, _y2) - _t
_xmax = max(_x1, _x2) + _t
_ymax = max(_y1, _y2) + _t
_clines = []
for _cline in self.getInRegion(_xmin, _ymin, _xmax, _ymax):
_p1, _p2 = _cline.getKeypoints()
if ((abs(_p1.x - _x1) < _t) and
(abs(_p1.y - _y1) < _t) and
(abs(_p2.x - _x2) < _t) and
(abs(_p2.y - _y2) < _t)):
_clines.append(_cline)
elif ((abs(_p1.x - _x2) < _t) and
(abs(_p1.y - _y2) < _t) and
(abs(_p2.x - _x1) < _t) and
(abs(_p2.y - _y1) < _t)):
_clines.append(_cline)
else:
pass
return _clines
def _moveCLine(self, obj, *args):
if obj not in self:
raise ValueError, "CLine not stored in Quadtree: " + `obj`
_alen = len(args)
if _alen < 4:
raise ValueError, "Invalid argument count: %d" % _alen
_x1 = util.get_float(args[0])
_y1 = util.get_float(args[1])
_x2 = util.get_float(args[2])
_y2 = util.get_float(args[3])
for _node in self.getNodes(_x1, _y1, _x2, _y2):
_node.delObject(obj) # cline may not be in node ...
super(CLineQuadtree, 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)
_cline = _tsep = None
_cdict = {}
_nodes = [self.getTreeRoot()]
while len(_nodes):
_node = _nodes.pop()
if _node.hasSubnodes():
_nodes.extend(_node.getSubnodes())
else:
for _c in _node.getObjects():
_cid = id(_c)
if _cid not in _cdict:
_cx, _cy = _c.getProjection(_x, _y)
if abs(_cx - _x) < _t and abs(_cy - _y) < _t:
_sep = math.hypot((_cx - _x), (_cy - _y))
if _tsep is None:
_tsep = _sep
_cline = _c
else:
if _sep < _tsep:
_tsep = _sep
_cline = _c
return _cline
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"
_clines = []
if not len(self):
return _clines
_nodes = [self.getTreeRoot()]
_cdict = {}
while len(_nodes):
_node = _nodes.pop()
if _node.hasSubnodes():
for _subnode in _node.getSubnodes():
_nodes.append(_subnode)
else:
for _cline in _node.getObjects():
_cid = id(_cline)
if _cid not in _cdict:
if _cline.inRegion(_xmin, _ymin, _xmax, _ymax):
_clines.append(_cline)
_cdict[_cid] = True
return _clines
#
# CLine history class
#
class CLineLog(conobject.ConstructionObjectLog):
def __init__(self, c):
if not isinstance(c, CLine):
raise TypeError, "Invalid CLine: " + `type(c)`
super(CLineLog, self).__init__(c)
c.connect('keypoint_changed', self._keypointChange)
def _keypointChange(self, c, *args):
_alen = len(args)
if _alen < 2:
raise ValueError, "Invalid argument count: %d" % _alen
_old = args[0]
if not isinstance(_old, point.Point):
raise TypeError, "Invalid old keypoint: " + `type(_old)`
_new = args[1]
if not isinstance(_new, point.Point):
raise TypeError, "Invalid new keypoint: " + `type(_new)`
self.saveUndoData('keypoint_changed', _old.getID(), _new.getID())
def execute(self, undo, *args):
util.test_boolean(undo)
_alen = len(args)
if len(args) == 0:
raise ValueError, "No arguments to execute()"
_c = self.getObject()
_p1, _p2 = _c.getKeypoints()
_op = args[0]
if _op == 'keypoint_changed':
if _alen < 3:
raise ValueError, "Invalid argument count: %d" % _alen
_oid = args[1]
_nid = args[2]
_parent = _c.getParent()
if _parent is None:
raise ValueError, "CLine has no parent - cannot undo"
self.ignore(_op)
try:
if undo:
_pt = _parent.getObject(_oid)
if _pt is None or not isinstance(_pt, point.Point):
raise ValueError, "Old keypoint missing: id=%d" % _oid
_c.startUndo()
try:
if _p1.getID() == _nid:
_c.setP1(_pt)
elif _p2.getID() == _nid:
_c.setP2(_pt)
else:
raise ValueError, "Unexpected keypoint ID: %d" % _nid
finally:
_c.endUndo()
else:
_pt = _parent.getObject(_nid)
if _pt is None or not isinstance(_pt, point.Point):
raise ValueError, "New keypoint missing: id=%d" % _nid
_c.startRedo()
try:
if _p1.getID() == _oid:
_c.setP1(_pt)
elif _p2.getID() == _oid:
_c.setP2(_pt)
else:
raise ValueError, "Unexpected keypoint ID: %d" % _oid
finally:
_c.endRedo()
finally:
self.receive(_op)
self.saveData(undo, _op, _oid, _nid)
else:
super(CLineLog, self).execute(undo, *args)