# # Copyright (c) 2003, 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 # # # classes for polyline objects # from __future__ import generators import math from PythonCAD.Generic import graphicobject from PythonCAD.Generic import tolerance from PythonCAD.Generic import style from PythonCAD.Generic import linetype from PythonCAD.Generic import color from PythonCAD.Generic import point from PythonCAD.Generic import util from PythonCAD.Generic import quadtree class Polyline(graphicobject.GraphicObject): """A class representing a polyline. A polyline is essentially a number of segments that connect end to end. A Polyline has the following methods: getPoints(): Return the points of the Polyline. {get/set}Point(): Get/Set one of the points of the Polyline addPoint(): Add a new point into the Polyline. delPoint(): Remove a point from the Polyline. move(): Move the Polyline. length(): Get the Polyline length. mapCoords(): Test if a coordinate pair is within some distance to a Polyline. inRegion(): Test if the Polyline is visible in some area. clone(): Make an identical copy of a Polyline. """ __defstyle = None __messages = { 'moved' : True, 'point_changed' : True, 'added_point' : True, 'deleted_point' : True, } def __init__(self, plist, st=None, lt=None, col=None, th=None, **kw): """Initialize a Polyline object. Polyline(plist) The argument 'plist' is a tuple or list containing Point objects. There should be two or more Points in the list. """ if not isinstance(plist, (tuple, list)): raise TypeError, "Invalid Point list/tuple: " + `type(plist)` _pts = [] _count = len(plist) if _count < 2: raise ValueError, "Invalid list count: %d" % _count for _i in range(_count): _obj = plist[_i] if not isinstance(_obj, point.Point): _obj = point.Point(plist[_i]) _pts.append(_obj) _plist = [] for _pt in _pts: try: # no exception means an equal point already found _i = _plist.index(_pt) _plist.append(_plist[_i]) except: # no equal point found _plist.append(_pt) if len(_plist) < 2: raise ValueError, "Invalid point list: " + str(plist) _st = st if _st is None: _st = self.getDefaultStyle() super(Polyline, self).__init__(_st, lt, col, th, **kw) self.__pts = _plist for _pt in _plist: _pt.connect('moved', self.__movePoint) _pt.connect('change_pending', self.__pointChangePending) _pt.connect('change_complete', self.__pointChangeComplete) _pt.storeUser(self) def __len__(self): return len(self.__pts) def __str__(self): return "Polyline" # fixme def __eq__(self, obj): """Compare two Polyline objects for equality. """ if not isinstance(obj, Polyline): return False if obj is self: return True _val = False _ppts = obj.getPoints() _pcount = len(_ppts) _spts = self.__pts _scount = len(_spts) if _pcount == _scount: _val = True for _i in range(_scount): if _ppts[_i] != _spts[_i]: _val = False break if not _val: # check reversed point list of second polyline _val = True _ppts.reverse() for _i in range(_scount): if _ppts[_i] != _spts[_i]: _val = False break return _val def __ne__(self, obj): """Compare two Polyline objects for inequality. """ if not isinstance(obj, Polyline): return True if obj is self: return False _val = True _ppts = obj.getPoints() _pcount = len(_ppts) _spts = self.__pts _scount = len(_spts) if _pcount == _scount: _val = False for _i in range(_scount): if _ppts[_i] != _spts[_i]: _val = True break if _val: # check reversed point list of second polyline _val = False _ppts.reverse() for _i in range(_scount): if _ppts[_i] != _spts[_i]: _val = True break return _val def getDefaultStyle(cls): if cls.__defstyle is None: _s = style.Style(u'Polyline Default Style', linetype.Linetype(u'Solid', None), color.Color(0xffffff), 1.0) cls.__defstyle = _s return cls.__defstyle getDefaultStyle = classmethod(getDefaultStyle) def setDefaultStyle(cls, s): if not isinstance(s, style.Style): raise TypeError, "Invalid style: " + `type(s)` cls.__defstyle = s setDefaultStyle = classmethod(setDefaultStyle) def finish(self): for _pt in self.__pts: _pt.disconnect(self) _pt.freeUser(self) self.__pts = None super(Polyline, self).finish() def setStyle(self, s): """Set the Style of the Polyline. setStyle(s) This method extends GraphicObject::setStyle(). """ _s = s if _s is None: _s = self.getDefaultStyle() super(Polyline, self).setStyle(_s) def getValues(self): """Return values comprising the Polyline. getValues() This method extends the GraphicObject::getValues() method. """ _data = super(Polyline, self).getValues() _data.setValue('type', 'polyline') _pts = [] for _pt in self.__pts: _pts.append(_pt.getID()) _data.setValue('points', _pts) return _data def getPoints(self): """Get the points of the Polyline. getPoints() This function returns a list containing all the Point objects that define the Polyline. """ return self.__pts[:] def getNumPoints(self): """Return the number of Point objects defining the Polyline. getNumPoints() """ return len(self) def getPoint(self, i): """Return a single Point object used for defining the Polyline. getPoint(i) The argument 'i' must be an integer, and its value represents the i'th Point used to define the Polyline. """ return self.__pts[i] def setPoint(self, i, p): """Set a Point of the Polyline. setPoint(i, p) The argument 'i' must be an integer, and its value represents the i'th Point used to define the Polyline. Argument 'p' must be a Point. """ if self.isLocked(): raise RuntimeError, "Setting point not allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid Point for Polyline point: " + `type(p)` _pt = self.__pts[i] if _pt is not p: _pt.disconnect(self) _pt.freeUser(self) self.startChange('point_changed') self.__pts[i] = p self.endChange('point_changed') self.sendMessage('point_changed', _pt, p) p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10: _pts = [] for _p in self.__pts: if _p is p: # the new point _pts.append((_pt.x, _pt.y)) else: # existing points _pts.append((_p.x, _p.y)) self.sendMessage('moved', _pts) self.modified() def addPoint(self, i, p): """Add a Point to the Polyline. addPoint(i, p) The argument 'i' must be an integer, and argument 'p' must be a Point. The Point is added into the list of points comprising the Polyline as the i'th point. """ if self.isLocked(): raise RuntimeError, "Adding point not allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid Point for Polyline point: " + `type(p)` self.startChange('added_point') self.__pts.insert(i, p) self.endChange('added_point') self.sendMessage('added_point', i, p) p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) _pts = [] for _p in self.__pts: if _p is not p: # skip the new point _pts.append((_p.x, _p.y)) self.sendMessage('moved', _pts) self.modified() def delPoint(self, i): """Remove a Point from the Polyline. delPoint(i) The argument i represents the index of the point to remove from the list of points defining the Polyline. The point will be removed only if the polyline will still have at least two Points. """ if self.isLocked(): raise RuntimeError, "Deleting point not allowed - object locked." if len(self.__pts) > 2: _p = self.__pts[i] _pts = [] for _pt in self.__pts: _pts.append((_pt.x, _pt.y)) self.startChange('deleted_point') del self.__pts[i] self.endChange('deleted_point') _p.freeUser(self) _p.disconnect(self) self.sendMessage('deleted_point', i, _p) self.modified() def move(self, dx, dy): """Move a Polyline. move(dx, dy) The first argument gives the x-coordinate displacement, and the second gives the y-coordinate displacement. Both values should be floats. """ _locked = self.isLocked() if not _locked: for _pt in self.__pts: if _pt.isLocked(): _locked = True break if _locked: raise RuntimeError, "Moving polyline not allowed - object locked." _dx = util.get_float(dx) _dy = util.get_float(dy) if abs(_dx) > 1e-10 or abs(_dy) > 1e-10: _coords = [] self.ignore('moved') try: for _pt in self.__pts: _coords.append(_pt.getCoords()) _pt.move(_dx, _dy) finally: self.receive('moved') self.sendMessage('moved', _coords) def length(self): """Return the length of the Polyline. length() The length is the sum of the lengths of all the sub-segments in the Polyline """ _length = 0.0 _pts = self.__pts _count = len(_pts) - 1 for _i in range(_count): _sublength = _pts[_i + 1] - _pts[_i] _length = _length + _sublength return _length def getBounds(self): """Return the bounding rectangle around a Polyline. getBounds() This method returns a tuple of four values: (xmin, ymin, xmax, ymax) """ _pts = self.__pts _pxmin = None _pymin = None _pxmax = None _pymax = None for _pt in _pts: _px, _py = _pt.getCoords() if _pxmin is None or _px < _pxmin: _pxmin = _px if _pymin is None or _py < _pymin: _pymin = _py if _pxmax is None or _px > _pxmax: _pxmax = _px if _pymax is None or _py > _pymax: _pymax = _py return _pxmin, _pymin, _pxmax, _pymax def mapCoords(self, x, y, tol=tolerance.TOL): """Return the nearest Point on the Polyline by the x/y coordinates. 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 Point object on the Polyline. 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 method returns None. """ _x = util.get_float(x) _y = util.get_float(y) _t = tolerance.toltest(tol) _count = len(self.__pts) - 1 for _i in range(_count): _x1, _y1 = self.__pts[_i].getCoords() _x2, _y2 = self.__pts[_i + 1].getCoords() _pt = util.map_coords(_x, _y, _x1, _y1, _x2, _y2, _t) if _pt is not None: return _pt return None def inRegion(self, xmin, ymin, xmax, ymax, fully=False): """Return whether or not a Polyline exists within a region. isRegion(xmin, ymin, xmax, ymax[, fully]) The four arguments define the boundary of an area, and the method returns True if the Polyline lies within that area. If the optional argument 'fully' is used and is True, then both endpoints of the Polyline must lie within the boundary. Otherwise, the method 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) _pxmin, _pymin, _pxmax, _pymax = self.getBounds() if ((_pxmax < _xmin) or (_pxmin > _xmax) or (_pymax < _ymin) or (_pymin > _ymax)): return False if fully: if ((_pxmin > _xmin) and (_pymin > _ymin) and (_pxmax < _xmax) and (_pymax < _ymax)): return True return False _pts = self.__pts for _i in range(len(_pts) - 1): _x1, _y1 = _pts[_i].getCoords() _x2, _y2 = _pts[_i + 1].getCoords() if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax): return True return False 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): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _x = util.get_float(args[0]) _y = util.get_float(args[1]) _seen = False _coords = [] for _pt in self.__pts: if p is _pt: _coords.append((_x, _y)) _seen = True else: _coords.append(_pt.getCoords()) if not _seen: raise ValueError, "Unexpected Polyline point: " + `p` self.sendMessage('moved', _coords) def clone(self): """Create an identical copy of a Polyline. clone() """ _cpts = [] for _pt in self.__pts: _cpts.append(_pt.clone()) _st = self.getStyle() _lt = self.getLinetype() _col = self.getColor() _th = self.getThickness() return Polyline(_cpts, _st, _lt, _col, _th) def sendsMessage(self, m): if m in Polyline.__messages: return True return super(Polyline, self).sendsMessage(m) # # Quadtree Polyline storage # class PolylineQuadtree(quadtree.Quadtree): def __init__(self): super(PolylineQuadtree, self).__init__() def getNodes(self, *args): _alen = len(args) if _alen != 4: raise ValueError, "Expected 4 arguments, got %d" % _alen _pxmin = util.get_float(args[0]) _pymin = util.get_float(args[1]) _pxmax = util.get_float(args[2]) if not _pxmax > _pxmin: raise ValueError, "xmax not greater than xmin" _pymax = util.get_float(args[3]) if not _pymax > _pymin: raise ValueError, "ymax not greater than ymin" _nodes = [self.getTreeRoot()] while len(_nodes): _node = _nodes.pop() _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if ((_pxmin > _xmax) or (_pxmax < _xmin) or (_pymin > _ymax) or (_pymax < _ymin)): continue if _node.hasSubnodes(): _xmid = (_xmin + _xmax)/2.0 _ymid = (_ymin + _ymax)/2.0 _ne = _nw = _sw = _se = True if _pxmax < _xmid: # polyline on left side _ne = _se = False if _pxmin > _xmid: # polyline on right side _nw = _sw = False if _pymax < _ymid: # polyline below _nw = _ne = False if _pymin > _ymid: # polyline 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: yield _node def addObject(self, obj): if not isinstance(obj, Polyline): raise TypeError, "Invalid Polyline object: " + `type(obj)` if obj in self: return _pxmin, _pymin, _pxmax, _pymax = obj.getBounds() _bounds = self.getTreeRoot().getBoundary() _xmin = _ymin = _xmax = _ymax = None _resize = False if _bounds is None: # first node in tree _resize = True _xmin = _pxmin - 1.0 _ymin = _pymin - 1.0 _xmax = _pxmax + 1.0 _ymax = _pymax + 1.0 else: _xmin, _ymin, _xmax, _ymax = _bounds if _pxmin < _xmin: _xmin = _pxmin - 1.0 _resize = True if _pxmax > _xmax: _xmax = _pxmax + 1.0 _resize = True if _pymin < _ymin: _ymin = _pymin - 1.0 _resize = True if _pymax > _ymax: _ymax = _pymax + 1.0 _resize = True if _resize: self.resize(_xmin, _ymin, _xmax, _ymax) for _node in self.getNodes(_pxmin, _pymin, _pxmax, _pymax): _xmin, _ymin, _xmax, _ymax = _node.getBoundary() if obj.inRegion(_xmin, _ymin, _xmax, _ymax): _node.addObject(obj) super(PolylineQuadtree, self).addObject(obj) obj.connect('moved', self._movePolyline) def delObject(self, obj): if obj not in self: return _pxmin, _pymin, _pxmax, _pymax = obj.getBounds() _pdict = {} for _node in self.getNodes(_pxmin, _pymin, _pxmax, _pymax): _node.delObject(obj) # polyline 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(PolylineQuadtree, self).delObject(obj) obj.disconnect(self) for _parent in _pdict.values(): self.purgeSubnodes(_parent) def find(self, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if not isinstance(args[0], list): raise TypeError, "Invalid coordinate list: " + `type(args[0])` _coords = [] _xmin = _xmax = _ymin = _ymax = None for _arg in args[0]: if not isinstance(_arg, tuple): raise TypeError, "Invalid coordinate tuple: " + `type(_arg)` if len(_arg) != 2: raise ValueError, "Invalid coodinate tuple: " + str(_arg) _x = util.get_float(_arg[0]) _y = util.get_float(_arg[1]) _coords.append((_x, _y)) if _xmin is None or _x < _xmin: _xmin = _x if _xmax is None or _x > _xmax: _xmax= _x if _ymin is None or _y < _ymin: _ymin = _y if _ymax is None or _y > _ymax: _ymax = _y _t = tolerance.TOL if _alen > 1: _t = tolerance.toltest(args[1]) _xmin = _xmin - _t _ymin = _ymin - _t _xmax = _xmax + _t _ymax = _ymax + _t _plines = [] for _pline in self.getInRegion(_xmin, _ymin, _xmax, _ymax): _pts = _pline.getPoints() if len(_pts) != len(_coords): continue _hit = False for _i in range(len(_pts)): _px, _py = _pts[_i].getCoords() if ((abs(_px - _coords[_i][0]) > _t) or (abs(_py - _coords[_i][1]) > _t)): continue _hit = True if not _hit: _pts.reverse() for _i in range(len(_pts)): _px, _py = _pts[_i].getCoords() if ((abs(_px - _coords[_i][0]) > _t) or (abs(_py - _coords[_i][1]) > _t)): continue _hit = True if _hit: _plines.append(_pline) return _plines def _movePolyline(self, obj, *args): if obj not in self: raise ValueError, "Polyline not stored in Quadtree: " + `obj` _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if not isinstance(args[0], list): raise TypeError, "Invalid coordinate list: " + `type(args[0])` _pxmin = _pxmax = _pymin = _pymax = None for _arg in args[0]: if not isinstance(_arg, tuple): raise TypeError, "Invalid coordinate tuple: " + `type(_arg)` if len(_arg) != 2: raise ValueError, "Invalid coodinate tuple: " + str(_arg) _x = util.get_float(_arg[0]) _y = util.get_float(_arg[1]) if _pxmin is None or _x < _pxmin: _pxmin = _x if _pxmax is None or _x > _pxmax: _pxmax= _x if _pymin is None or _y < _pymin: _pymin = _y if _pymax is None or _y > _pymax: _pymax = _y for _node in self.getNodes(_pxmin, _pymin, _pxmax, _pymax): _node.delObject(obj) # polyline may not be in node ... super(PolylineQuadtree, 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) _polyline = _tsep = None _bailout = False _pdict = {} _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 _p in _node.getObjects(): _pid = id(_p) if _pid not in _pdict: for _pt in _p.getPoints(): _px, _py = _pt.getCoords() if ((abs(_px - _x) < 1e-10) and (abs(_py - _y) < 1e-10)): _polyline = _p _bailout = True break _pdict[_pid] = True if _bailout: break _pt = _p.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 _polyline = _p else: if _sep < _tsep: _tsep = _sep _polyline = _p if _bailout: break return _polyline 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" _polylines = [] if not len(self): return _polylines _nodes = [self.getTreeRoot()] _pdict = {} 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 _p in _node.getObjects(): _pid = id(_p) if _pid not in _pdict: if _p.inRegion(_xmin, _ymin, _xmax, _ymax): _polylines.append(_p) _pdict[_pid] = True return _polylines # # Polyline history class # class PolylineLog(graphicobject.GraphicObjectLog): def __init__(self, p): if not isinstance(p, Polyline): raise TypeError, "Invalid polyline: " + `type(p)` super(PolylineLog, self).__init__(p) p.connect('point_changed', self.__pointChanged) p.connect('added_point', self.__addedPoint) p.connect('deleted_point', self.__deletedPoint) def __pointChanged(self, p, *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 endpoint: " + `type(_old)` _new = args[1] if not isinstance(_new, point.Point): raise TypeError, "Invalid new endpoint: " + `type(_new)` self.saveUndoData('point_changed', _old.getID(), _new.getID()) def __addedPoint(self, p, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _idx = args[0] if not isinstance(_idx, int): raise TypeError, "Invalid point index: " + `type(_idx)` _p = args[1] if not isinstance(_p, point.Point): raise TypeError, "Invalid point: " + `type(_p)` self.saveUndoData('added_point', _idx, _p.getID()) def __deletedPoint(self, p, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _idx = args[0] if not isinstance(_idx, int): raise TypeError, "Invalid point index: " + `type(_idx)` _p = args[1] if not isinstance(_p, point.Point): raise TypeError, "Invalid point: " + `type(_p)` self.saveUndoData('deleted_point', _idx, _p.getID()) def execute(self, undo, *args): util.test_boolean(undo) _alen = len(args) if _alen == 0: raise ValueError, "No arguments to execute()" _p = self.getObject() _op = args[0] _pts = _p.getPoints() if _op == 'point_changed': if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _oid = args[1] _nid = args[2] _parent = _p.getParent() if _parent is None: raise ValueError, "Polyline has no parent - cannot undo" self.ignore(_op) try: if undo: _setpt = _parent.getObject(_oid) if _setpt is None or not isinstance(_setpt, point.Point): raise ValueError, "Old endpoint missing: id=%d" % _oid _p.startUndo() try: _seen = False for _i in range(len(_pts)): _pt = _pts[_i] if _pt.getID() == _nid: _p.setPoint(_i, _setpt) _seen = True break if not _seen: raise ValueError, "Unexpected point ID: %d" % _nid finally: _p.endUndo() else: _setpt = _parent.getObject(_nid) if _setpt is None or not isinstance(_setpt, point.Point): raise ValueError, "New point missing: id=%d" % _nid _pts = _p.getPoints() _p.startRedo() try: _seen = False for _i in range(len(_pts)): _pt = _pts[_i] if _pt.getID() == _oid: _p.setPoint(_i, _setpt) _seen = True break if not _seen: raise ValueError, "Unexpected point ID: %d" % _nid finally: _p.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _oid, _nid) elif _op == 'added_point': if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _idx = args[1] _pid = args[2] self.ignore(_op) try: if undo: self.ignore('deleted_point') try: _p.startUndo() try: _p.delPoint(_idx) finally: _p.endUndo() finally: self.receive('deleted_point') else: _parent = _p.getParent() if _parent is None: raise ValueError, "Polyline has no parent - cannot undo" _pt = _parent.getObject(_pid) if _pt is None or not isinstance(_pt, point.Point): raise ValueError, "Point missing: id=%d" % _pid _p.startRedo() try: _p.addPoint(_idx, _pt) finally: _p.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _idx, _pid) elif _op == 'deleted_point': if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _idx = args[1] _pid = args[2] self.ignore(_op) try: if undo: _parent = _p.getParent() if _parent is None: raise ValueError, "Polyline has no parent - cannot undo" _pt = _parent.getObject(_pid) if _pt is None or not isinstance(_pt, point.Point): raise ValueError, "Point missing: id=%d" % _pid self.ignore('added_point') try: _p.startUndo() try: _p.addPoint(_idx, _pt) finally: _p.endUndo() finally: self.receive('added_point') else: _p.startRedo() try: _p.delPoint(_idx) finally: _p.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _idx, _pid) else: super(PolylineLog, self).execute(undo, *args)