# # Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007 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 # # # tool stuff # import math import types import array from PythonCAD.Generic import util from PythonCAD.Generic import color from PythonCAD.Generic import linetype from PythonCAD.Generic import style from PythonCAD.Generic.point import Point from PythonCAD.Generic.segment import Segment from PythonCAD.Generic.circle import Circle from PythonCAD.Generic.arc import Arc from PythonCAD.Generic.leader import Leader from PythonCAD.Generic.polyline import Polyline from PythonCAD.Generic.hcline import HCLine from PythonCAD.Generic.vcline import VCLine from PythonCAD.Generic.acline import ACLine from PythonCAD.Generic.cline import CLine from PythonCAD.Generic.ccircle import CCircle from PythonCAD.Generic.text import TextStyle, TextBlock from PythonCAD.Generic import dimension from PythonCAD.Generic.layer import Layer from PythonCAD.Generic import tangent class Tool(object): """A generic tool object. This class is meant to be a base class for tools. A Tool instance the following attributes: list: A list the tool can use to store objects handlers: A dictionary used to store functions A Tool object has the following methods: {set/get/del/has}Handler(): Store/retrive/delete/test a handler for an event. clearHandlers(): Unset all the handlers in the tool. reset(): Restore the tool to a a default state. initialize(): Retore the tool to its original state. {set/get}Filter(): Set/Get a filtering procedure for object testing. {set/get}Location(): Store/retrieve an image-based coordinate pair. {set/get}CurrentPoint(): Store/retrieve a screen-based coordinate pair. clearCurrentPoint(): Set the screen-based coordinate to None. create(): Instantiate the object the tool is designed to create. """ def __init__(self): """Instantiate a Tool. t = Tool() """ super(Tool, self).__init__() self.__objlist = [] self.__objtype = None self.__fproc = None self.__handlers = {} self.__location = None self.__curpoint = None self.__points = [] self.__xpts = array.array('d') self.__ypts = array.array('d') self.__shift = None def __len__(self): """Return the number of objects in the list via len(). """ return len(self.__objlist) def __iter__(self): """Make the Tool iterable. """ return iter(self.__objlist) def getList(self): """Return the Tool's object list. getList() """ return self.__objlist list = property(getList, None, None, "Tool object list.") def reset(self): """Restore the Tool to its initial state. reset() This function purges the Tool's object list and handler dictionary. """ del self.__objlist[:] self.__handlers.clear() self.__location = None self.__curpoint = None del self.__points[:] del self.__xpts[:] del self.__ypts[:] self.__shift = None def initialize(self): self.reset() def setHandler(self, key, func): """Set a handler for the Tool. setHandler(key, func) There are two arguments for this function: key: A string used to identify a particular action func: A function object There are no restrictions on what the function 'func' does, the argument count, etc. Any call to setHandler() with a key that is already stored replaces the old 'func' argument with the new one. The function argument may be None, and the key argument must be a string. """ if key is None: raise ValueError, "Key value cannot be None." if not isinstance(key, str): raise TypeError, "Invalid key type: " + `type(key)` if func and not isinstance(func, types.FunctionType): raise TypeError, "Invalid function type: " + `type(func)` self.__handlers[key] = func def getHandler(self, key): """Return the function for a particular key. getHandler(key) Given argument 'key', the function associated with it is returned. A KeyError is raised if the argument 'key' had not be used to store a function. """ if not isinstance(key, str): raise TypeError, "Invalid key type: " + `type(key)` if not self.__handlers.has_key(key): raise KeyError, "Invalid key '%s'" % key return self.__handlers[key] def delHandler(self, key): """Delete the handler associated with a particular key delHandler(key) The argument 'key' should be a string. """ if self.__handlers.has_key(key): del self.__handlers[key] def hasHandler(self, key): """Check if there is a handler stored for a given key. hasHandler(key) The 'key' argument must be a string. The function returns 1 if there is a handler for that key, 0 otherwise. """ _k = key if not isinstance(_k, str): raise TypeError, "Invalid key type: " + `type(key)` return self.__handlers.has_key(_k) def clearHandlers(self): """Unset all the handlers for the tool. clearHandlers() This function does not alter the Tool's object list. """ self.__handlers.clear() def setObjtype(self, objtype): """Store the type of objects on which the tool operates. setObjtype(objtype) Argument 'objtype' can be a single type, a tuple of types, or 'None'. """ if not isinstance(objtype, (tuple, types.NoneType, types.TypeType)): raise TypeError, "Invalid objtype: " + `type(objtype)` if objtype is not None: if isinstance(objtype, tuple): for _obj in objtype: if not isinstance(_obj, types.TypeType): raise TypeError, "Invalid objtype: " + `type(_obj)` self.__objtype = objtype def getObjtype(self): """Return the type of object on which the tool operates. getObjtype() This method returns the value set in a setObjtype(), or None if no object types have been specified. """ return self.__objtype def setFilter(self, proc): """Store a procedure used to examine selected objects. setFilter(proc) Argument 'proc' must be a callable procedure. """ if not callable(proc): raise TypeError, "Invalid filter procedure: " + `type(proc)` self.__fproc = proc def getFilter(self): """Return a stored procedure. getFilter() This method returns the procedure stored vai setFilter() or None. """ return self.__fproc def pushObject(self, obj): """Add an object to the Tool's object list. pushObject(obj) """ self.__objlist.append(obj) def popObject(self): """Remove the last object on the Tool's object list. popObject() If the object list is empty, this function returns None. """ _obj = None if len(self.__objlist): _obj = self.__objlist.pop() return _obj def delObjects(self): """Remove all objects from the Tool's object list. delObjects() This function does not alter the Tool's handlers. """ del self.__objlist[:] def getObject(self, idx): """Access an object in the tool. getObject(idx) The argument 'idx' is the index into the list of stored objects. """ return self.__objlist[idx] def setLocation(self, x, y): """Store an x/y location in the tool. setLocation(x,y) Store an x-y coordinate in the tool. Both arguments should be floats """ _x = util.get_float(x) _y = util.get_float(y) self.__location = (_x,_y) def getLocation(self): """Return the stored location in the tool getLocation() """ return self.__location def clearLocation(self): """Reset the location to an empty value. clearLocation() """ self.__location = None def setCurrentPoint(self, x, y): """Set the tool's current point. setCurrentPoint(x,y) Store an x-y coordinate in the tool. Both arguments should be int. """ _x = x if not isinstance(_x, int): _x = int(x) _y = y if not isinstance(_y, int): _y = int(y) self.__curpoint = (_x, _y) def getCurrentPoint(self): """Return the tool's current point value. getCurrentPoint() """ return self.__curpoint def clearCurrentPoint(self): """Reset the current point to an empty value clearCurrentPoint() """ self.__curpoint = None def create(self, image): """Create an object the tool is designed to construct. create(image) The argument 'image' is an image in which the newly created object will be added. In the Tool class, this method does nothing. It is meant to be overriden in classed using the Tool class as a base class. """ pass # override # # The ZoomTool, PasteTool, SelectTool, and DeselectTool classes are # subclasses of the Tool class but with no additional functionality (yet) class ZoomTool(Tool): pass class PasteTool(Tool): pass class SelectTool(Tool): pass class DeselectTool(Tool): pass class PointTool(Tool): """A specialized tool for drawing Point objects. The PointTool class is derived from the Tool class, so it shares the methods and attributes of that class. The PointTool class has the following additional methods: {get/set}Point(): Get/Set a x/y coordinate in the tool. """ def __init__(self): super(PointTool, self).__init__() self.__point = None def setPoint(self, x, y): """Store an x/y coordinate in the tool setPoint(x, y) Arguments 'x' and 'y' should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__point = (_x, _y) def getPoint(self): """Get the stored x/y coordinates from the tool. getPoint() This method returns a tuple containing the values passed in with the setPoint() method, or None if that method has not been invoked. """ return self.__point def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(PointTool, self).reset() self.__point = None def create(self, image): """Create a new Point and add it to the image. create(image) This method overrides the Tool::create() method. """ if self.__point is not None: _active_layer = image.getActiveLayer() _x, _y = self.__point _p = Point(_x, _y) _active_layer.addObject(_p) self.reset() class SegmentTool(Tool): """A Specialized tool for drawing Segment objects. The SegmentTool class is derived from the Tool class, so it shares the attributes and methods of that class. The SegmentTool class has the following additional methods: {get/set}FirstPoint(): Get/Set the first point of the Segment. {get/set}SecondPoint(): Get/Set the second point of the Segment. """ def __init__(self): super(SegmentTool, self).__init__() self.__first_point = None self.__second_point = None def setFirstPoint(self, x, y): """Store the first point of the Segment. setFirstPoint(x, y) Arguments 'x' and 'y' should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__first_point = (_x, _y) def getFirstPoint(self): """Get the first point of the Segment. getFirstPoint() This method returns a tuple holding the coordinates stored by invoking the setFirstPoint() method, or None if that method has not been invoked. """ return self.__first_point def setSecondPoint(self, x, y): """Store the second point of the Segment. setSecondPoint(x, y) Arguments 'x' and 'y' should be floats. If the tool has not had the first point set with setFirstPoint(), a ValueError exception is raised. """ if self.__first_point is None: raise ValueError, "SegmentTool first point is not set." _x = util.get_float(x) _y = util.get_float(y) self.__second_point = (_x, _y) def getSecondPoint(self): """Get the second point of the Segment. getSecondPoint() This method returns a tuple holding the coordinates stored by invoking the setSecondPoint() method, or None if that method has not been invoked. """ return self.__second_point def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(SegmentTool, self).reset() self.__first_point = None self.__second_point = None def create(self, image): """Create a new Segment and add it to the image. create(image) This method overrides the Tool::create() method. """ if (self.__first_point is not None and self.__second_point is not None): _active_layer = image.getActiveLayer() _x1, _y1 = self.__first_point _x2, _y2 = self.__second_point _pts = _active_layer.find('point', _x1, _y1) if len(_pts) == 0: _p1 = Point(_x1, _y1) _active_layer.addObject(_p1) else: _p1 = _pts[0] _pts = _active_layer.find('point', _x2, _y2) if len(_pts) == 0: _p2 = Point(_x2, _y2) _active_layer.addObject(_p2) else: _p2 = _pts[0] _s = image.getOption('LINE_STYLE') _seg = Segment(_p1, _p2, _s) _l = image.getOption('LINE_TYPE') if _l != _s.getLinetype(): _seg.setLinetype(_l) _c = image.getOption('LINE_COLOR') if _c != _s.getColor(): _seg.setColor(_c) _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) > 1e-10: _seg.setThickness(_t) _active_layer.addObject(_seg) self.reset() class RectangleTool(SegmentTool): """A Specialized tool for drawing rectangles. The RectangleTool is derived from the SegmentTool, so it shares all the methods and attributes of that class. A RectangleTool creates four Segments in the shape of a rectangle in the image. """ def __init__(self): super(RectangleTool, self).__init__() def create(self, image): """Create Segments and add them to the image. create(image) This method overrides the SegmentTool::create() method. """ _p1 = self.getFirstPoint() _p2 = self.getSecondPoint() if _p1 is not None and _p2 is not None: _x1, _y1 = _p1 _x2, _y2 = _p2 _active_layer = image.getActiveLayer() _pts = _active_layer.find('point', _x1, _y1) if len(_pts) == 0: _p1 = Point(_x1, _y1) _active_layer.addObject(_p1) else: _p1 = _pts[0] _pts = _active_layer.find('point', _x1, _y2) if len(_pts) == 0: _p2 = Point(_x1, _y2) _active_layer.addObject(_p2) else: _p2 = _pts[0] _pts = _active_layer.find('point', _x2, _y2) if len(_pts) == 0: _p3 = Point(_x2, _y2) _active_layer.addObject(_p3) else: _p3 = _pts[0] _pts = _active_layer.find('point', _x2, _y1) if len(_pts) == 0: _p4 = Point(_x2, _y1) _active_layer.addObject(_p4) else: _p4 = _pts[0] _s = image.getOption('LINE_STYLE') _l = image.getOption('LINE_TYPE') if _l == _s.getLinetype(): _l = None _c = image.getOption('LINE_COLOR') if _c == _s.getColor(): _c = None _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) < 1e-10: _t = None _seg = Segment(_p1, _p2, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) _seg = Segment(_p2, _p3, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) _seg = Segment(_p3, _p4, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) _seg = Segment(_p4, _p1, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) self.reset() class CircleTool(Tool): """A Specialized tool for drawing Circle objects. The CircleTool is derived from the Tool class, so it shares all the methods and attributes of that class. The CircleTool class has the following addtional methods: {set/get}Center(): Set/Get the center point location of the circle. {set/get}Radius(): Set/Get the radius of the circle. """ def __init__(self): super(CircleTool, self).__init__() self.__center = None self.__radius = None def setCenter(self, x, y): """Set the center point location of the circle. setCenter(x, y) The arguments 'x' and 'y' give the location for the center of the circle. """ _x = util.get_float(x) _y = util.get_float(y) self.__center = (_x, _y) def getCenter(self): """Get the center point location of the circle. getCenter() This method returns the coordinates stored with the setCenter() method, or None if that method has not been called. """ return self.__center def setRadius(self, radius): """Set the radius of the circle. setRadius(radius) The argument 'radius' must be a float value greater than 0.0 """ _r = util.get_float(radius) if not _r > 0.0: raise ValueError, "Invalid radius: %g" % _r self.__radius = _r def getRadius(self): """Get the radius of the circle. getRadius() This method returns the value specified from the setRadius() call, or None if that method has not been invoked. """ return self.__radius def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(CircleTool, self).reset() self.__center = None self.__radius = None def create(self, image): """Create a new Circle and add it to the image. create(image) This method overrides the Tool::create() method. """ if (self.__center is not None and self.__radius is not None): _active_layer = image.getActiveLayer() _x, _y = self.__center _r = self.__radius _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _cp = Point(_x, _y) _active_layer.addObject(_cp) else: _cp = _pts[0] _s = image.getOption('LINE_STYLE') _circle = Circle(_cp, _r, _s) _l = image.getOption('LINE_TYPE') if _l != _s.getLinetype(): _circle.setLinetype(_l) _c = image.getOption('LINE_COLOR') if _c != _s.getColor(): _circle.setColor(_c) _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) > 1e-10: _circle.setThickness(_t) _active_layer.addObject(_circle) self.reset() class TwoPointCircleTool(CircleTool): """A specialized class for drawing Circles between two points. The TwoPointCircleTool class is derived from the CircleTool class, so it shares all the methods and attributes of that class. The TwoPointCircleTool class has the following addtional methods: {set/get}FirstPoint(): Set/Get the first point used to define the circle. {set/get}SecondPoint(): Set/Get the second point used to define the circle. """ def __init__(self): super(TwoPointCircleTool, self).__init__() self.__first_point = None self.__second_point = None def setFirstPoint(self, x, y): """Set the first point used to define the location of the circle. setFirstPoint(x, y) Arguments 'x' and 'y' give the location of a point. """ _x = util.get_float(x) _y = util.get_float(y) self.__first_point = (_x, _y) def getFirstPoint(self): """Get the first point used to define the location of the circle. getFirstPoint() This method returns a tuple holding the values used when the setFirstPoint() method was called, or None if that method has not yet been used. """ return self.__first_point def setSecondPoint(self, x, y): """Set the second point used to define the location of the circle. setSecondPoint(x, y) Arguments 'x' and 'y' give the location of a point. Invoking this method before the setFirstPoint() method will raise a ValueError. """ if self.__first_point is None: raise ValueError, "First point is not set" _x = util.get_float(x) _y = util.get_float(y) _x1, _y1 = self.__first_point _xc = (_x + _x1)/2.0 _yc = (_y + _y1)/2.0 _radius = math.hypot((_x - _x1), (_y - _y1))/2.0 self.setCenter(_xc, _yc) self.setRadius(_radius) self.__second_point = (_x, _y) def getSecondPoint(self): """Get the second point used to define the location of the circle. getSecondPoint() This method returns a tuple holding the values used when the setSecondPoint() method was called, or None if that method has not yet been used. """ return self.__second_point def reset(self): """Restore the tool to its initial state. reset() This method extends CircleTool::reset(). """ super(TwoPointCircleTool, self).reset() self.__first_point = None self.__second_point = None class ArcTool(CircleTool): """A specialized tool for drawing Arc objects. The ArcTool is Derived from the CircleTool class, so it shares all the attributes and methods of that class. The ArcTool class has the following addtional methods: {set/get}StartAngle(): Set/Get the start angle of the arc {set/get}EndAngle(): Set/Get the end angle of the arc. """ def __init__(self): super(ArcTool, self).__init__() self.__start_angle = None self.__end_angle = None def setStartAngle(self, angle): """Set the start angle of the arc. setStartAngle(angle) The argument 'angle' should be a float value between 0.0 and 360.0 """ _angle = util.make_c_angle(angle) self.__start_angle = _angle def getStartAngle(self): """Return the start angle of the arc. getStartAngle() This method returns the value defined in the previous setStartAngle() call, or None if that method has not been called. """ return self.__start_angle def setEndAngle(self, angle): """Set the start angle of the arc. setStartAngle(angle) The argument 'angle' should be a float value between 0.0 and 360.0 """ _angle = util.make_c_angle(angle) self.__end_angle = _angle def getEndAngle(self): """Return the end angle of the arc. getEndAngle() This method returns the value defined in the previous setEndAngle() call, or None if that method has not been called. """ return self.__end_angle def reset(self): """Restore the tool to its initial state. reset() This method extends CircleTool::reset(). """ super(ArcTool, self).reset() self.__start_angle = None self.__end_angle = None def create(self, image): """Create a new Arc and add it to the image. create(image) This method overrides the CircleTool::create() method. """ _center = self.getCenter() _radius = self.getRadius() _sa = self.__start_angle _ea = self.__end_angle if (_center is not None and _radius is not None and _sa is not None and _ea is not None): _active_layer = image.getActiveLayer() _x, _y = _center _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _cp = Point(_x, _y) _active_layer.addObject(_cp) else: _cp = _pts[0] _s = image.getOption('LINE_STYLE') _arc = Arc(_cp, _radius, _sa, _ea, _s) _l = image.getOption('LINE_TYPE') if _l != _s.getLinetype(): _arc.setLinetype(_l) _c = image.getOption('LINE_COLOR') if _c != _s.getColor(): _arc.setColor(_c) _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) > 1e-10: _arc.setThickness(_t) for _ep in _arc.getEndpoints(): _ex, _ey = _ep _pts = _active_layer.find('point', _ex, _ey) if len(_pts) == 0: _lp = Point(_ex, _ey) _active_layer.addObject(_lp) _active_layer.addObject(_arc) self.reset() # # The ChamferTool and FilletTool class are subclasses of # the Tool class but have no additional functionality (yet) # class ChamferTool(Tool): pass class FilletTool(Tool): pass class LeaderTool(Tool): """A specialized tool for drawing Leader objects. The LeaderTool class is derived from the Tool class, so it shares the methods and attributes of that class. The LeaderTool class has the following addtional methods: {set/get}FirstPoint(): Set/Get the first point of the Leader. {set/get}MidPoint(): Set/Get the second point of the Leader. {set/get}FinalPoint(): Set/Get the final point of the Leader. """ def __init__(self): super(LeaderTool, self).__init__() self.__start_point = None self.__mid_point = None self.__end_point = None def setFirstPoint(self, x, y): """Set the first point used to define the Leader. setFirstPoint(x, y) Arguments 'x' and 'y' give the location of a point. """ _x = util.get_float(x) _y = util.get_float(y) self.__start_point = (_x, _y) def getFirstPoint(self): """Get the first point used to define the Leader. getFirstPoint() This method returns a tuple holding the values used when the setFirstPoint() method was called, or None if that method has not yet been used. """ return self.__start_point def setMidPoint(self, x, y): """Set the second point used to define the Leader. setMidPoint(x, y) Arguments 'x' and 'y' give the location of a point. If the first point has not been set this method raises a ValueError. """ if self.__start_point is None: raise ValueError, "First point not set in LeaderTool." _x = util.get_float(x) _y = util.get_float(y) self.__mid_point = (_x, _y) def getMidPoint(self): """Get the second point used to define the Leader. getMidPoint() This method returns a tuple holding the values used when the setMidPoint() method was called, or None if that method has not yet been used. """ return self.__mid_point def setFinalPoint(self, x, y): """Set the first point used to final point of the Leader. setFinalPoint(x, y) Arguments 'x' and 'y' give the location of a point. This method raises an error if the first point or second point have not been set. """ if self.__start_point is None: raise ValueError, "First point not set in LeaderTool." if self.__mid_point is None: raise ValueError, "Second point not set in LeaderTool." _x = util.get_float(x) _y = util.get_float(y) self.__end_point = (_x, _y) def getFinalPoint(self): """Get the third point used to define the Leader. getFinalPoint() This method returns a tuple holding the values used when the setFinalPoint() method was called, or None if that method has not yet been used. """ return self.__end_point def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(LeaderTool, self).reset() self.__start_point = None self.__mid_point = None self.__end_point = None def create(self, image): """Create a new Leader and add it to the image. create(image) This method overrides the Tool::create() method. """ if (self.__start_point is not None and self.__mid_point is not None and self.__end_point is not None): _active_layer = image.getActiveLayer() _x1, _y1 = self.__start_point _x2, _y2 = self.__mid_point _x3, _y3 = self.__end_point _pts = _active_layer.find('point', _x1, _y1) if len(_pts) == 0: _p1 = Point(_x1, _y1) _active_layer.addObject(_p1) else: _p1 = _pts[0] _pts = _active_layer.find('point', _x2, _y2) if len(_pts) == 0: _p2 = Point(_x2, _y2) _active_layer.addObject(_p2) else: _p2 = _pts[0] _pts = _active_layer.find('point', _x3, _y3) if len(_pts) == 0: _p3 = Point(_x3, _y3) _active_layer.addObject(_p3) else: _p3 = _pts[0] _size = image.getOption('LEADER_ARROW_SIZE') _s = image.getOption('LINE_STYLE') _leader = Leader(_p1, _p2, _p3, _size, _s) _l = image.getOption('LINE_TYPE') if _l != _s.getLinetype(): _leader.setLinetype(_l) _c = image.getOption('LINE_COLOR') if _c != _s.getColor(): _leader.setColor(_c) _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) > 1e-10: _leader.setThickness(_t) _active_layer.addObject(_leader) self.reset() class PolylineTool(Tool): """A specialized tool for drawing Polyline objects. The PolylineTool class is derived from the Tool class, so it shares all the attributes and methods of that class. The PolylineTool class has the following addtional methods: storePoint(): Store a point used to define the Polyline. getPoint(): Retrieve a point used to define the Polyline. getLastPoint(): Retrieve the last point used to define the Polyline. getPoints(): Get the list of points that define the Polyline. """ def __init__(self): super(PolylineTool, self).__init__() self.__points = [] def __len__(self): return len(self.__points) def storePoint(self, x, y): """Store a point that will define a Polyline. storePoint(x, y) The arguments 'x' and 'y' should be float values. There is no limit as to how long a Polyline should be, so each invocation of this method appends the values to the list of stored points. """ _x = util.get_float(x) _y = util.get_float(y) self.__points.append((_x, _y)) def getPoint(self, i): """Retrieve a point used to define a Polyline. getPoint(i) Argument 'i' represents the index in the list of points that defines the polyline. Negative indicies will get points from last-to-first. Using an invalid index will raise an error. This method returns a tuple holding the x/y coordinates. """ return self.__points[i] def getPoints(self): """Get all the points that define the Polyline. getPoints() This method returns a list of tuples holding the x/y coordinates of all the points that define the Polyline. """ return self.__points[:] def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(PolylineTool, self).reset() del self.__points[:] def create(self, image): """Create a new Polyline and add it to the image. create(image) This method overrides the Tool::create() method. """ if len(self.__points): _pts = [] _active_layer = image.getActiveLayer() for _pt in self.__points: _x, _y = _pt _lpts = _active_layer.find('point', _x, _y) if len(_lpts) == 0: _p = Point(_x, _y) _active_layer.addObject(_p) _pts.append(_p) else: _pts.append(_lpts[0]) _s = image.getOption('LINE_STYLE') _pline = Polyline(_pts, _s) _l = image.getOption('LINE_TYPE') if _l != _s.getLinetype(): _pline.setLinetype(_l) _c = image.getOption('LINE_COLOR') if _c != _s.getColor(): _pline.setColor(_c) _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) > 1e-10: _pline.setThickness(_t) _active_layer.addObject(_pline) self.reset() class PolygonTool(Tool): """A specialized to for creating Polygons from Segments. The PolygonTool will create an uniformly sized polygon from Segment entities. The minimum number of sides is three, creating an equilateral triangle. There is no maximum number of sides, though realistically any polygon with more than 20 or so sides is unlikely to be drawn. As the PolygonTool is derived from the Tool class, it shares all the attributes and method of that class. The PolygonTool has the following additional methods: {get/set}SideCount(): Get/Set the number of sides in the polygon. {get/set}External() Get/Set if the polygon is drawn inside or outside a circle. {get/set}Center(): Get/Set the center location of the polygon. getCoords(): Get the coordinates of the polygon corners. """ def __init__(self): super(PolygonTool, self).__init__() self.__nsides = None self.__increment = None self.__external = False self.__center = None self.__xpts = array.array("d") self.__ypts = array.array("d") def setSideCount(self, count): """Set the number of sides of the polygon to create. setSideCount(count) Argument "count" should be an integer value greater than 2. """ _count = count if not isinstance(_count, int): _count = int(count) if _count < 3: raise ValueError, "Invalid count: %d" % _count self.__nsides = _count self.__increment = (360.0/float(_count)) * (math.pi/180.0) for _i in range(_count): self.__xpts.insert(_i, 0.0) self.__ypts.insert(_i, 0.0) def getSideCount(self): """Get the number of sides of the polygon to be created. getSideCount() A ValueError exception is raised if the side count has not been set with setSideCount() """ if self.__nsides is None: raise ValueError, "No side count defined." return self.__nsides def setExternal(self): """Create the polygon on the outside of a reference circle. setExternal() By default the polygon is drawing completely contained within a circle. Invoking this method will created the polygon so that all sides are outside the circle. """ self.__external = True def getExternal(self): """Test if the polygon will be created outside a circle. getExternal() If the setExternal() method has been called, this method will return True. By default this method will return False. """ return self.__external def setCenter(self, x, y): """Define the center of the polygon. setCenter(x, y) Arguments 'x' and 'y' should be float values. """ _x = util.get_float(x) _y = util.get_float(y) self.__center = (_x, _y) def getCenter(self): """Retrieve the center of the polygon to be created. getCenter() This method returns a tuple holding two float values containing the 'x' and 'y' coordinates of the polygon center. A ValueError is raised if the center has not been set with a prior call to setCenter(). """ if self.__center is None: raise ValueError, "Center is undefined." return self.__center def getCoord(self, i): """Get one of the coordinates of the polygon corners. getCoord(i) Argument "i" should be an integer value such that: 0 <= i <= number of polygon sides """ _x = self.__xpts[i] _y = self.__ypts[i] return _x, _y def setLocation(self, x, y): """Set the tool location. setLocation(x, y) This method extends Tool::setLocation() and calculates the polygon points. """ super(PolygonTool, self).setLocation(x, y) _x, _y = self.getLocation() _count = self.__nsides _inc = self.__increment if self.__external: _offset = _inc/2.0 else: _offset = 0.0 _cx, _cy = self.__center _xsep = _x - _cx _ysep = _y - _cy _angle = math.atan2(_ysep, _xsep) + _offset _rad = math.hypot(_xsep, _ysep)/math.cos(_offset) _xp = self.__xpts _yp = self.__ypts for _i in range(_count): _xp[_i] = _cx + (_rad * math.cos(_angle)) _yp[_i] = _cy + (_rad * math.sin(_angle)) _angle = _angle + _inc def create(self, image): """Create a Polygon from Segments and add it to the image. create(image) This method overrides the Tool::create() method. """ if len(self.__xpts): _active_layer = image.getActiveLayer() _s = image.getOption('LINE_STYLE') _l = image.getOption('LINE_TYPE') if _l == _s.getLinetype(): _l = None _c = image.getOption('LINE_COLOR') if _c == _s.getColor(): _c = None _t = image.getOption('LINE_THICKNESS') if abs(_t - _s.getThickness()) < 1e-10: _t = None _count = self.__nsides _xp = self.__xpts _yp = self.__ypts _x = _xp[0] _y = _yp[0] # # find starting point ... # _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _p0 = Point(_x, _y) _active_layer.addObject(_p0) else: _p0 = _pts[0] # # make segments for all the points ... # _p1 = _p0 for _i in range(1, _count): _x = _xp[_i] _y = _yp[_i] _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _pi = Point(_x, _y) _active_layer.addObject(_pi) else: _pi = _pts[0] _seg = Segment(_p1, _pi, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) _p1 = _pi # # now add closing segment ... # _seg = Segment(_p1, _p0, _s, linetype=_l, color=_c, thickness=_t) _active_layer.addObject(_seg) self.reset() def reset(self): """Restore the PolygonTool to its original state. reset() This method extends Tool::reset() """ super(PolygonTool, self).reset() # self.__nsides = None # self.__increment = None # self.__external = False # make this adjustable? self.__center = None for _i in range(self.__nsides): self.__xpts[_i] = 0.0 self.__ypts[_i] = 0.0 class HCLineTool(PointTool): """A specialized tool for drawing HCLine objects. The HCLineTool class is derived from the PointTool class so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(HCLineTool, self).__init__() def create(self, image): """Create a new HCLine and add it to the image. create(image) This method overrides the Tool::create() method. """ _p = self.getPoint() if _p is not None: _active_layer = image.getActiveLayer() _x, _y = _p _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _pt = Point(_x, _y) _active_layer.addObject(_pt) else: _pt = _pts[0] _hcl = HCLine(_pt) _active_layer.addObject(_hcl) self.reset() class VCLineTool(PointTool): """A specialized tool for drawing VCLine objects. The VCLineTool class is derived from the PointTool class so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(VCLineTool, self).__init__() def create(self, image): """Create a new VCLine and add it to the image. create(image) This method overrides the Tool::create() method. """ _p = self.getPoint() if _p is not None: _active_layer = image.getActiveLayer() _x, _y = _p _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _pt = Point(_x, _y) _active_layer.addObject(_pt) else: _pt = _pts[0] _vcl = VCLine(_pt) _active_layer.addObject(_vcl) self.reset() class ACLineTool(PointTool): """A specialized tool for drawing ACLine objects. The ACLineTool class is derived from the PointTool class so it shares all the attributes and methods of that class. The ACLineTool class has the following addtional methods: {set/get}Angle(): Set/Get the angle of the ACLine. """ def __init__(self): super(ACLineTool, self).__init__() self.__angle = None def setLocation(self, x, y): """Set the location of the Tool. setLocation(x, y) This method extends the Tool::setLocation() method. """ super(ACLineTool, self).setLocation(x, y) _loc = self.getLocation() if _loc is None: return _x, _y = _loc _x1, _y1 = self.getPoint() if abs(_y - _y1) < 1e-10: # horizontal self.__angle = 0.0 elif abs(_x - _x1) < 1e-10: # vertical self.__angle = 90.0 else: _slope = 180.0/math.pi * math.atan2((_y - _y1), (_x - _x1)) self.__angle = util.make_angle(_slope) def setAngle(self, angle): """Set the angle for the ACLine. setAngle(angle) The argument 'angle' should be a float where -90.0 < angle < 90.0 """ _angle = util.make_angle(angle) self.__angle = _angle def getAngle(self): """Get the angle for the ACLine. getAngle() This method returns a float. """ return self.__angle def reset(self): """Restore the tool to its initial state. reset() This method extends PointTool::reset(). """ super(ACLineTool, self).reset() self.__angle = None def create(self, image): """Create a new ACLine and add it to the image. create(image) This method overrides the Tool::create() method. """ _p = self.getPoint() if (_p is not None and self.__angle is not None): _active_layer = image.getActiveLayer() _x, _y = _p _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _pt = Point(_x, _y) _active_layer.addObject(_pt) else: _pt = _pts[0] _acl = ACLine(_pt, self.__angle) _active_layer.addObject(_acl) self.reset() class CLineTool(SegmentTool): """A specialized tool for drawing CLine objects. The CLineTool class is derived from the SegmentTool class, so it shares all the attributes and methods of that class. There are no extra methods for the CLineTool class. """ def __init__(self): super(CLineTool, self).__init__() def create(self, image): """Create a new CLine and add it to the image. create(image) This method overrides the Tool::create() method. """ _p1 = self.getFirstPoint() _p2 = self.getSecondPoint() if _p1 is not None and _p2 is not None: _active_layer = image.getActiveLayer() _x1, _y1 = _p1 _x2, _y2 = _p2 _pts = _active_layer.find('point', _x1, _y1) if len(_pts) == 0: _p1 = Point(_x1, _y1) _active_layer.addObject(_p1) else: _p1 = _pts[0] _pts = _active_layer.find('point', _x2, _y2) if len(_pts) == 0: _p2 = Point(_x2, _y2) _active_layer.addObject(_p2) else: _p2 = _pts[0] _cline = CLine(_p1, _p2) _active_layer.addObject(_cline) self.reset() class CCircleTool(CircleTool): """A specialized tool for drawing CCircle objects. The CCircleTool class is derived from the CircleTool class, so it shares all the attributes and methods of that class. There are no additional methods for the CCircleTool class. """ def __init__(self): super(CCircleTool, self).__init__() def create(self, image): """Create a new CCircle and add it to the image. create(image) This method overrides the Tool::create() method. """ _active_layer = image.getActiveLayer() _x, _y = self.getCenter() _radius = self.getRadius() _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _cp = Point(_x, _y) _active_layer.addObject(_cp) else: _cp = _pts[0] _ccircle = CCircle(_cp, _radius) _active_layer.addObject(_ccircle) self.reset() class TwoPointCCircleTool(TwoPointCircleTool): """A specialized tool for drawing CCircle objects between two points. The TwoPointCCircleTool class is derived from the TwoPointCircleTool class, so it shares all the attributes and methods of that class. There are no additional methods for the TwoPointCCircleTool class. """ def __init__(self): super(TwoPointCCircleTool, self).__init__() def create(self, image): """Create a new CCircle and add it to the image. create(image) This method overrides the Tool::create() method. """ _center = self.getCenter() _radius = self.getRadius() if _center is not None and _radius is not None: _active_layer = image.getActiveLayer() _x, _y = _center _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _cp = Point(_x, _y) _active_layer.addObject(_cp) else: _cp = _pts[0] _ccircle = CCircle(_cp, _radius) _active_layer.addObject(_ccircle) self.reset() # # The PerpendicularCLineTool and TangentCLineTool classes are # subclasses of Tool without any additional functionality (yet) # class PerpendicularCLineTool(Tool): pass class TangentCLineTool(Tool): pass class ParallelOffsetTool(Tool): """A specialized tool for creating parallel construction lines. The ParallelOffsetTool will create a construction line parallel to another construction line a fixed distance from the original construction line. The type of the new construction line will match that of the original. The ParallelOffsetTool is derived from the Tool class, so it shares all the attributes and methods of that class. The ParallelOffsetTool has the following addtional methods: {set/get}Offset(): Set/Get the distance between the construction lines. {set/get}ConstructionLine(): Set/Get the original construction line {set/get}ReferencePoint(): Set/Get the point to define where the new construction line will go. """ def __init__(self): super(ParallelOffsetTool, self).__init__() self.__refpt = None self.__offset = None self.__conline = None def setOffset(self, offset): """Store the displacement in the tool. setOffset(offset) Argument 'offset' must be a float. """ _offset = util.get_float(offset) self.__offset = _offset def getOffset(self): """Return the stored offset from the tool. getOffset() This method will raise a ValueError exception if the offset has not been set with setOffset() """ _offset = self.__offset if _offset is None: raise ValueError, "Offset is not defined." return _offset def setConstructionLine(self, conline): """Store the reference construction line in the tool. setConstructionLine(conline) Argument 'conline' must be a VCLine, HCLine, ACLine, or CLine object. """ if not isinstance(conline, (HCLine, VCLine, ACLine, CLine)): raise TypeError, "Invalid Construction line: " + `type(conline)` self.__conline = conline def getConstructionLine(self): """Retrieve the stored construction line from the tool. getConstructionLine() A ValueError exception is raised if the construction line has not been set with the setConstructionLine() method. """ _conline = self.__conline if _conline is None: raise ValueError, "Construction line is not defined." return _conline def setReferencePoint(self, x, y): """Store the reference point for positioning the new construction line. setReferencePoint(x, y) Arguments 'x' and 'y' give the coordinates of a reference point used to determine where the new construction line will be placed. Both arguments should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__refpt = (_x, _y) def getReferencePoint(self): """Retreive the reference point from the tool. getReferencePoint() This method returns a tuple containing the values stored from the setReferencePoint() call. This method will raise a ValueError exception if the reference point has not been set. """ _refpt = self.__refpt if _refpt is None: raise ValueError, "No reference point defined." return _refpt def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(ParallelOffsetTool, self).reset() self.__refpt = None self.__offset = None self.__conline = None def create(self, image): """Create a parallel construction line in an image. create(image) This method overrides the Tool::create() method. """ _offset = self.__offset _conline = self.__conline _refpt = self.__refpt if (_offset is not None and _conline is not None and _refpt is not None): _active_layer = image.getActiveLayer() _rx, _ry = _refpt _lp1 = _lp2 = _ncl = None if isinstance(_conline, HCLine): _x, _y = _conline.getLocation().getCoords() if _ry > _y: _yn = _y + _offset else: _yn = _y - _offset if len(_active_layer.find('hcline', _yn)) == 0: _pts = _active_layer.find('point', _x, _yn) if len(_pts) == 0: _lp1 = Point(_x, _yn) else: _lp1 = _pts[0] _ncl = HCLine(_lp1) elif isinstance(_conline, VCLine): _x, _y = _conline.getLocation().getCoords() if _rx > _x: _xn = _x + _offset else: _xn = _x - _offset if len(_active_layer.find('vcline', _xn)) == 0: _pts = _active_layer.find('point', _xn, _y) if len(_pts) == 0: _lp1 = Point(_xn, _y) else: _lp1 = _pts[0] _ncl = VCLine(_lp1) elif isinstance(_conline, ACLine): _x, _y = _conline.getLocation().getCoords() _angle = _conline.getAngle() if abs(_angle) < 1e-10: # horizontal _dx = 0.0 _dy = _offset elif abs(abs(_angle) - 90.0) < 1e-10: # vertical _dx = _offset _dy = 0.0 else: _slope = math.tan(_angle * (math.pi/180.0)) _yint = _y - (_slope * _x) # # p1 => (_x, _y) # p2 => (0, _yint) # # redefine angle from p1 to p2 ... _angle = math.atan2((_yint - _y), -_x) if _angle < 0.0: _angle = _angle + (2.0 * math.pi) _sqlen = math.hypot(_x, (_y - _yint)) _sn = ((_y - _ry) * (0.0 - _x)) - ((_x - _rx) * (_yint - _y)) _s = _sn/_sqlen if _s < 0.0: _perp = _angle + (math.pi/2.0) else: _perp = _angle - (math.pi/2.0) _dx = _offset * math.cos(_perp) _dy = _offset * math.sin(_perp) _angle = _conline.getAngle() # reset variable _xn = _x + _dx _yn = _y + _dy if len(_active_layer.find('acline', _xn, _yn, _angle)) == 0: _pts = _active_layer.find('point', _xn, _yn) if len(_pts) == 0: _lp1 = Point(_xn, _yn) else: _lp1 = _pts[0] _ncl = ACLine(_lp1, _angle) elif isinstance(_conline, CLine): _p1, _p2 = _conline.getKeypoints() _x1, _y1 = _p1.getCoords() _x2, _y2 = _p2.getCoords() if abs(_x2 - _x1) < 1e-10: # vertical _dx = _offset _dy = 0.0 elif abs(_y2 - _y1) < 1e-10: # horizontal _dx = 0.0 _dy = _offset else: _angle = math.atan2((_y2 - _y1), (_x2 - _x1)) if _angle < 0.0: _angle = _angle + (2.0 * math.pi) _sqlen = math.hypot((_x2 - _x1), (_y2 - _y1)) _sn = ((_y1 - _ry) * (_x2 - _x1)) - ((_x1 - _rx) * (_y2 - _y1)) _s = _sn/_sqlen if _s < 0.0: _perp = _angle + (math.pi/2.0) else: _perp = _angle - (math.pi/2.0) _dx = math.cos(_perp) * _offset _dy = math.sin(_perp) * _offset _x1n = _x1 + _dx _y1n = _y1 + _dy _x2n = _x2 + _dx _y2n = _y2 + _dy if len(_active_layer.find('cline', _x1n, _y1n, _x2n, _y2n)) == 0: _pts = _active_layer.find('point', _x1n, _y1n) if len(_pts) == 0: _lp1 = Point(_x1n, _y1n) else: _lp1 = _pts[0] _pts = _active_layer.find('point', _x2n, _y2n) if len(_pts) == 0: _lp2 = Point(_x2n, _y2n) else: _lp2 = _pts[0] _ncl = CLine(_lp1, _lp2) else: raise TypeError, "Invalid Construction line type: " + `type(_conline)` if _ncl is not None: if _lp1 is not None and _lp1.getParent() is None: _active_layer.addObject(_lp1) if _lp2 is not None and _lp2.getParent() is None: _active_layer.addObject(_lp2) _active_layer.addObject(_ncl) self.reset() class TangentCircleTool(Tool): """A specialized class for creating tangent construction circles. This class is meant to be a base class for tools that create tangent construction circles. It is derived from the tool class so it shares all the attributes and methods of that class. This class has the following additional methods: {set/get}Center(): Set/Get the center of the tangent circle. {set/get}Radius(): Set/Get the radius of the tangent circle. {set/get}PixelRect(): Set/Get the screen rectangle for drawing the circle. """ def __init__(self): super(TangentCircleTool, self).__init__() self.__center = None self.__radius = None self.__rect = None def setCenter(self, x, y): """Store the tangent circle center point in the tool. setCenter(x, y) Arguments 'x' and 'y' should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__center = (_x, _y) def getCenter(self): """Return the center of the tangent circle. getCenter() This method returns a tuple holding two floats, the first is the 'x' coordinate of the center, the second is the 'y' coordinate. If the tool has not yet been invoked with a setLocation() call, this method returns None. """ return self.__center def setRadius(self, radius): """Store the radius in the tool. setRadius(radius) Argument 'radius' should be a float. """ _radius = util.get_float(radius) self.__radius = _radius def getRadius(self): """Return the center of the tangent circle. getRadius() This method returns a float giving the radius of the tangent circle, or None if the radius is not set. """ return self.__radius def setPixelRect(self, xmin, ymin, width, height): """Store the screen coordinates used to draw the circle. setPixelRect(xmin, ymin, width, height) All the arguments should be integer values. """ _xmin = xmin if not isinstance(_xmin, int): _xmin = int(xmin) _ymin = ymin if not isinstance(_ymin, int): _ymin = int(ymin) _width = width if not isinstance(_width, int): _width = int(_width) _height = height if not isinstance(_height, int): _height = int(height) self.__rect = (_xmin, _ymin, _width, _height) def getPixelRect(self): """Return the screen boundary of the circle to draw getPixelRect(self) This method will return a tuple holding four integer values: xmin, ymin, width, height If the rectangle has not been set by calling setPixelRect(), then this method will return None. """ return self.__rect def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(TangentCircleTool, self).reset() self.__center = None self.__radius = None self.__rect = None def create(self, image): """Create a new CCircle and add it to the image. create(image) This method overrides the Tool::create() method. """ _active_layer = image.getActiveLayer() _x, _y = self.__center _radius = self.__radius _pts = _active_layer.find('point', _x, _y) if len(_pts) == 0: _cp = Point(_x, _y) _active_layer.addObject(_cp) else: _cp = _pts[0] _ccircle = CCircle(_cp, _radius) _active_layer.addObject(_ccircle) self.reset() class TangentCCircleTool(TangentCircleTool): """A specialized tool for creating tangent construction circles. The TangentCCircleTool will create a construction circle tangent to a construction line or a construction circle. The TangentCCircleTool is derived from the TangentCircleTool class, so it shares all the attributes and methods of that class. The TangentCCircleTool has the following addtional methods: {set/get}ConstructionObject(): Set/Get the reference construction object. """ def __init__(self): super(TangentCCircleTool, self).__init__() self.__conobj = None def setConstructionLine(self, conobj): """Store the reference construction object in the tool. setConstructionLine(conobj) Argument 'conobj' must be a VCLine, HCLine, ACLine, CLine, or CCircle object. """ if not isinstance(conobj, (HCLine, VCLine, ACLine, CLine, CCircle)): raise TypeError, "Invalid Construction entity type: " + `type(conobj)` self.__conobj = conobj def getConstructionLine(self): """Retrieve the stored construction line from the tool. getConstructionLine() A ValueError exception is raised if the construction line has not been set with the setConstructionLine() method. """ _conobj = self.__conobj if _conobj is None: raise ValueError, "Construction object is not defined." return _conobj def setLocation(self, x, y): """Store an x/y coordinate pair in the tool. setLocation(x, y) Arguments 'x' and 'y' should be floats. This method extends the TangentCircleTool::setLocation() methods. """ super(TangentCCircleTool, self).setLocation(x, y) _tx, _ty = self.getLocation() _conobj = self.__conobj _cx = _cy = _radius = None if isinstance(_conobj, HCLine): _x, _y = _conobj.getLocation().getCoords() _cx = _tx _cy = (_ty + _y)/2.0 _radius = abs(_ty - _y)/2.0 elif isinstance(_conobj, VCLine): _x, _y = _conobj.getLocation().getCoords() _cx = (_tx + _x)/2.0 _cy = _ty _radius = abs(_tx - _x)/2.0 elif isinstance(_conobj, (ACLine, CLine)): _px, _py = _conobj.getProjection(_tx, _ty) _cx = (_tx + _px)/2.0 _cy = (_ty + _py)/2.0 _radius = math.hypot((_tx - _px), (_ty - _py))/2.0 elif isinstance(_conobj, CCircle): _ccx, _ccy = _conobj.getCenter().getCoords() _rad = _conobj.getRadius() _sep = math.hypot((_tx - _ccx), (_ty - _ccy)) if _sep < 1e-10: return _angle = math.atan2((_ty - _ccy), (_tx - _ccx)) _px = _ccx + (_rad * math.cos(_angle)) _py = _ccy + (_rad * math.sin(_angle)) _cx = (_tx + _px)/2.0 _cy = (_ty + _py)/2.0 _radius = math.hypot((_tx - _px), (_ty - _py))/2.0 else: raise TypeError, "Invalid construction entity type: " + `type(_conobj)` self.setCenter(_cx, _cy) self.setRadius(_radius) class TwoPointTangentCCircleTool(TangentCircleTool): """A specialized tool for creating tangent construction circles. The TwoPointTangentCCircleTool will create a construction circle tangent to two construction lines or a construction line and a construction circle if such a tangent circle can be created. The TwoPointTangentCCircleTool is derived from the TangentCircleTool class, so it shares all the attributes and methods of that class. This class also has the following addtional methods: {set/get}FirstConObject(): Set/Get the first construction object. {set/get}SecondConObject(): Set/Get the second constuction object. """ def __init__(self): super(TwoPointTangentCCircleTool, self).__init__() self.__cobj1 = None self.__cobj2 = None def setFirstConObject(self, conobj): """Store the first reference construction object in the tool. setFirstConObject(conobj) Argument 'conobj' must be a VCLine, HCLine, ACLine, CLine, or CCircle object. """ if not isinstance(conobj, (HCLine, VCLine, ACLine, CLine, CCircle)): raise TypeError, "Invalid Construction entity type: " + `type(conobj)` self.__cobj1 = conobj def getFirstConObject(self): """Retreive the first construction object from the tool. getFirstConObject() """ return self.__cobj1 def setSecondConObject(self, conobj): """Store the second reference construction object in the tool. setSecondConObject(conobj) Argument 'conobj' must be a VCLine, HCLine, ACLine, or a CLine object. Drawing a tangent circle against two CCircle objects is not yet supported. A ValueError exception will be raised if this method is called before the first construction object has been set. """ if self.__cobj1 is None: raise ValueError, "First construction object not set." if not isinstance(conobj, (HCLine, VCLine, ACLine, CLine)): raise TypeError, "Invalid Construction line type: " + `type(conobj)` self.__cobj2 = conobj def getSecondConObject(self): """Retreive the second construction object from the tool. getSecondConObject() """ return self.__cobj2 def reset(self): """Restore the tool to its initial state. reset() This method extends the TangentCircleTool::reset() method. """ super(TwoPointTangentCCircleTool, self).reset() self.__cobj1 = None self.__cobj2 = None def setLocation(self, x, y): """Store an x/y coordinate pair in the tool. setLocation(x, y) Arguments 'x' and 'y' should be floats. This method extends the TangentCircleTool::setLocation() methods. """ super(TwoPointTangentCCircleTool, self).setLocation(x, y) _tx, _ty = self.getLocation() _obja = self.__cobj1 _objb = self.__cobj2 _tandata = tangent.calc_tangent_circle(_obja, _objb, _tx, _ty) if _tandata is not None: _cx, _cy, _radius = _tandata self.setCenter(_cx, _cy) self.setRadius(_radius) class CCircleTangentLineTool(Tool): """A specialized class for creating tangent lines to construction circles. This class is a specialized class that handles creating construction lines around circles. There can be at most four possible tangent lines. There are two tangent lines if the circles overlap, and no tangent lines if one circle is inside another. As this tool is derived from the Tool class it shares all the attributes and methods of that class. The CCircleTangentLineTool class has the following additional methods: {get/set}FirstCCircle(): Get/Set the first CCircle in the tool. {get/set}SecondCCircle(): Get/Set the second CCircle in the tool. """ def __init__(self): super(CCircleTangentLineTool, self).__init__() self.__circ1 = None self.__circ2 = None self.__tanpts = [] def setFirstCCircle(self, ccircle): """Store the first construction circle in the tool. setFirstCCircle(ccircle) Argument 'ccircle' must be a CCircle object. """ if not isinstance(ccircle, CCircle): raise TypeError, "Invalid CCircle type: " + `type(ccircle)` self.__circ1 = ccircle def getFirstCCircle(self): """Retreive the first construction circle from the tool. getFirstCCircle() """ return self.__circ1 def setSecondCCircle(self, ccircle): """Store the second construction circle in the tool. setSecondCCircle(ccircle) Argument 'ccircle' must be a CCircle object. A ValueError exception will be raised if this method is called before the first construction circle has been set. """ if self.__circ1 is None: raise ValueError, "First construction circle not set." if not isinstance(ccircle, CCircle): raise TypeError, "Invalid CCircle type: " + `type(ccircle)` self.__circ2 = ccircle # # calculate the tangent points if they exist # _cc1 = self.__circ1 _cc2 = self.__circ2 _cx1, _cy1 = _cc1.getCenter().getCoords() _r1 = _cc1.getRadius() _cx2, _cy2 = _cc2.getCenter().getCoords() _r2 = _cc2.getRadius() _sep = math.hypot((_cx2 - _cx1), (_cy2 - _cy1)) _angle = math.atan2((_cy2 - _cy1), (_cx2 - _cx1)) _sine = math.sin(_angle) _cosine = math.cos(_angle) # # tangent points are calculated as if the first circle # center is (0, 0) and both circle centers on the x-axis, # so the points need to be transformed to the correct coordinates # _tanpts = self.__tanpts del _tanpts[:] # make sure it is empty ... _tansets = tangent.calc_two_circle_tangents(_r1, _r2, _sep) for _set in _tansets: _x1, _y1, _x2, _y2 = _set _tx1 = ((_x1 * _cosine) - (_y1 * _sine)) + _cx1 _ty1 = ((_x1 * _sine) + (_y1 * _cosine)) + _cy1 _tx2 = ((_x2 * _cosine) - (_y2 * _sine)) + _cx1 _ty2 = ((_x2 * _sine) + (_y2 * _cosine)) + _cy1 _tanpts.append((_tx1, _ty1, _tx2, _ty2)) def getSecondCCircle(self): """Retreive the second construction circle from the tool. getSecondCCircle() """ return self.__circ2 def hasTangentPoints(self): """Test if tangent points were found for the two circles. hasTangentPoints() """ _val = False if len(self.__tanpts): _val = True return _val def getTangentPoints(self): """Return the tangent points calculated for two-circle tangency. getTangentPoints() This method returns a list of tuples holding four float values: (x1, y1, x2, y2) A tangent line can be drawn between the two circles from (x1, y1) to (x2, y2). """ return self.__tanpts[:] def create(self, image): """Create the tangent line for two circles. create(image) """ _x, _y = self.getLocation() _tanpts = self.__tanpts if not len(_tanpts): raise ValueError, "No tangent points defined." _minsep = _px1 = _py1 = _px2 = _py2 = None for _set in _tanpts: _x1, _y1, _x2, _y2 = _set _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) _sep = math.hypot((_px - _x), (_py - _y)) if _minsep is None or _sep < _minsep: _minsep = _sep _px1 = _x1 _py1 = _y1 _px2 = _x2 _py2 = _y2 _active_layer = image.getActiveLayer() _pts = _active_layer.find('point', _px1, _py1) if len(_pts) == 0: _p1 = Point(_px1, _py1) _active_layer.addObject(_p1) else: _p1 = _pts[0] _pts = _active_layer.find('point', _px2, _py2) if len(_pts) == 0: _p2 = Point(_px2, _py2) _active_layer.addObject(_p2) else: _p2 = _pts[0] _active_layer.addObject(CLine(_p1, _p2)) def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(CCircleTangentLineTool, self).reset() self.__circ1 = None self.__circ2 = None del self.__tanpts[:] class DimensionTool(Tool): """A base class for tools creating Dimension objects. The DimensionTool class is meant to be a base class for classes that will create Dimension objects. The DimensionTool class is derived from the Tool class, so it shares all the attributes and methods of that class. The DimensionTool class has the following additional methods: setDimension(): Store a dimension object in the tool getDimension(): Retrieve a stored dimension object. setDimPrefs(): Apply the current dimension preferences on a stored dimension. """ def __init__(self): super(DimensionTool, self).__init__() self.__dim = None def _setDimension(self, dim): """Store a dimension in the tool. setDimension(dim) The argument 'dim' must be a Dimension object. """ if not isinstance(dim, dimension.Dimension): raise TypeError, "Invalid Dimension type: " + `type(dim)` self.__dim = dim def getDimension(self): """Retrieve the stored dimension object from the tool. getDimension() This method returns the stored Dimension or None. """ return self.__dim def setDimPrefs(self, image): """Apply the current dimension options to the stored dimension. setDimPrefs(image) The argument 'image' is an image option in which the current dimension preferences are retrieved. """ _dim = self.__dim if _dim is None: raise ValueError, "No dimension stored in tool." _pds = _dim.getPrimaryDimstring() _pds.mute() _pds.setFamily(image.getOption('DIM_PRIMARY_FONT_FAMILY')) _pds.setWeight(image.getOption('DIM_PRIMARY_FONT_WEIGHT')) _pds.setStyle(image.getOption('DIM_PRIMARY_FONT_STYLE')) _pds.setColor(image.getOption('DIM_PRIMARY_FONT_COLOR')) _pds.setSize(image.getOption('DIM_PRIMARY_TEXT_SIZE')) _pds.setAlignment(image.getOption('DIM_PRIMARY_TEXT_ALIGNMENT')) _pds.setPrecision(image.getOption('DIM_PRIMARY_PRECISION')) _pds.setUnits(image.getOption('DIM_PRIMARY_UNITS')) _pds.setPrintZero(image.getOption('DIM_PRIMARY_LEADING_ZERO')) _pds.setPrintDecimal(image.getOption('DIM_PRIMARY_TRAILING_DECIMAL')) _pds.unmute() _sds = _dim.getSecondaryDimstring() _sds.mute() _sds.setFamily(image.getOption('DIM_SECONDARY_FONT_FAMILY')) _sds.setWeight(image.getOption('DIM_SECONDARY_FONT_WEIGHT')) _sds.setStyle(image.getOption('DIM_SECONDARY_FONT_STYLE')) _sds.setColor(image.getOption('DIM_SECONDARY_FONT_COLOR')) _sds.setSize(image.getOption('DIM_SECONDARY_TEXT_SIZE')) _sds.setAlignment(image.getOption('DIM_SECONDARY_TEXT_ALIGNMENT')) _sds.setPrecision(image.getOption('DIM_SECONDARY_PRECISION')) _sds.setUnits(image.getOption('DIM_SECONDARY_UNITS')) _sds.setPrintZero(image.getOption('DIM_SECONDARY_LEADING_ZERO')) _sds.setPrintDecimal(image.getOption('DIM_SECONDARY_TRAILING_DECIMAL')) _sds.unmute() _dim.setOffset(image.getOption('DIM_OFFSET')) _dim.setExtension(image.getOption('DIM_EXTENSION')) _dim.setColor(image.getOption('DIM_COLOR')) _dim.setPosition(image.getOption('DIM_POSITION')) _dim.setEndpointType(image.getOption('DIM_ENDPOINT')) _dim.setEndpointSize(image.getOption('DIM_ENDPOINT_SIZE')) _dim.setDualDimMode(image.getOption('DIM_DUAL_MODE')) def reset(self): """Restore the tool to its initial state. reset() This method sets the DimensionTool dimension to None. """ super(DimensionTool, self).reset() self.__dim = None class LinearDimensionTool(DimensionTool): """A specialized tool for drawing LinearDimension objects. The LinearDimensionTool is derived from the DimensionTool and Tool, class, so it shares all the attributes and methods of those classes. The LinearDimensionTool class has the following addtional methods: {set/get}FirstPoint(): Set/Get the first point in the LinearDimension {set/get}SecondPoint(): Set/Get the second point in the LinearDimension. {set/get}DimPosition(): Set/Get the location of the dimension text. """ def __init__(self): super(LinearDimensionTool, self).__init__() self.__p1 = None self.__p2 = None self.__position = None def setFirstPoint(self, p): """Store the first point for the LinearDimension. setFirstPoint(p): The argument 'p' must be a Point instance. """ if not isinstance (p, Point): raise TypeError, "Invalid Point: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in a Layer." self.__p1 = p def getFirstPoint(self): """Return the first point for the LinearDimension. getFirstPoint() This method returns a tuple of two objects: the first object is a Layer, the second object is a Point. """ return self.__p1 def setSecondPoint(self, p): """Store the second point for the LinearDimension. setSecondPoint(p): The argument 'p' must be a Point instance. """ if self.__p1 is None: raise ValueError, "First point not set for LinearDimension." if not isinstance (p, Point): raise TypeError, "Invalid Point: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in a Layer." self.__p2 = p def getSecondPoint(self): """Return the second point for the LinearDimension. getSecondPoint() This method returns a tuple of two objects: the first object is a Layer, the second object is a Point. """ return self.__p2 def setDimPosition(self, x, y): """Store the point where the dimension text will be located. setDimPosition(x, y) Arguments 'x' and 'y' should be float values. """ _x = util.get_float(x) _y = util.get_float(y) self.__position = (_x, _y) def getDimPosition(self): """Retrieve where the dimension text should be placed. getDimPosition() This method returns a tuple containing the x/y coodindates defined by the setDimPosition() call, or None if that method has not been invoked. """ return self.__position def reset(self): """Restore the tool to its initial state. reset() This method extends the reset() methods of its base classes. """ super(LinearDimensionTool, self).reset() self.__p1 = None self.__p2 = None self.__position = None def makeDimension(self, image): """Create a LinearDimension based on the stored tool values. makeDimension(image) The argument 'image' is an image object where the dimension will be used. """ _p1 = self.__p1 _p2 = self.__p2 _x, _y = self.__position if (_p1 is not None and _p2 is not None and _x is not None and _y is not None): _ds = image.getOption('DIM_STYLE') _ldim = dimension.LinearDimension(_p1, _p2, _x, _y, _ds) self._setDimension(_ldim) self.setDimPrefs(image) def create(self, image): """Create a new LinearDimension and add it to the image. create(image) This method overrides the Tool::create() method. """ _ldim = self.getDimension() if _ldim is not None: _pds = _ldim.getPrimaryDimstring() _pds.mute() try: _pds.setPrefix(image.getOption('DIM_PRIMARY_PREFIX')) _pds.setSuffix(image.getOption('DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds = _ldim.getSecondaryDimstring() _sds.mute() try: _sds.setPrefix(image.getOption('DIM_SECONDARY_PREFIX')) _sds.setSuffix(image.getOption('DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() _ldim.calcDimValues() image.addObject(_ldim) self.reset() class HorizontalDimensionTool(LinearDimensionTool): """A specialized tool for drawing HorizontalDimension objects. The HorizontalDimensionTool is derived from the LinearDimensionTool and the Tool classes, so it shares all the attributes and methods of those class. There are no additional methods for the HorizontalDimension class. """ def __init__(self): super(HorizontalDimensionTool, self).__init__() def makeDimension(self, image): """Create a HorizontalDimension based on the stored tool values. makeDimension(image) The argument 'image' is an image object where the dimension willbe used. """ _p1 = self.getFirstPoint() _p2 = self.getSecondPoint() _x, _y = self.getDimPosition() if (_p1 is not None and _p2 is not None and _x is not None and _y is not None): _ds = image.getOption('DIM_STYLE') _hdim = dimension.HorizontalDimension(_p1, _p2, _x, _y, _ds) self._setDimension(_hdim) self.setDimPrefs(image) def create(self, image): """Create a new HorizontalDimension and add it to the image. create(image) This method overrides the LinearDimensionTool::create() method. """ _hdim = self.getDimension() if _hdim is not None: _pds = _hdim.getPrimaryDimstring() _pds.mute() try: _pds.setPrefix(image.getOption('DIM_PRIMARY_PREFIX')) _pds.setSuffix(image.getOption('DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds = _hdim.getSecondaryDimstring() _sds.mute() try: _sds.setPrefix(image.getOption('DIM_SECONDARY_PREFIX')) _sds.setSuffix(image.getOption('DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() _hdim.calcDimValues() image.addObject(_hdim) self.reset() class VerticalDimensionTool(LinearDimensionTool): """A specialized tool for drawing VerticalDimension objects. The VerticalalDimensionTool is derived from the LinearDimensionTool and the Tool classes, so it shares all the attributes and methods of those class. There are no additional methods for the VerticalalDimension class. """ def __init__(self): super(VerticalDimensionTool, self).__init__() def makeDimension(self, image): """Create a VerticalDimension based on the stored tool values. makeDimension(image) The argument 'image' is an image object where the dimension will be used. """ _p1 = self.getFirstPoint() _p2 = self.getSecondPoint() _x, _y = self.getDimPosition() if (_p1 is not None and _p2 is not None and _x is not None and _y is not None): _ds = image.getOption('DIM_STYLE') _vdim = dimension.VerticalDimension(_p1, _p2, _x, _y, _ds) self._setDimension(_vdim) self.setDimPrefs(image) def create(self, image): """Create a new VerticalDimension and add it to the image. create(image) This method overrides the LinearDimensionTool::create() method. """ _vdim = self.getDimension() if _vdim is not None: _pds = _vdim.getPrimaryDimstring() _pds.mute() try: _pds.setPrefix(image.getOption('DIM_PRIMARY_PREFIX')) _pds.setSuffix(image.getOption('DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds = _vdim.getSecondaryDimstring() _sds.mute() try: _sds.setPrefix(image.getOption('DIM_SECONDARY_PREFIX')) _sds.setSuffix(image.getOption('DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() _vdim.calcDimValues() image.addObject(_vdim) self.reset() class RadialDimensionTool(DimensionTool): """A specialized tool for drawing RadialDimension objects. The RadialDimensionTool class is derived from the DimensionTool class and Tool class, so it shares all the attributes and methods of those classes. The RadialDimensionTool class has the following additional methods: {set/get}DimObject(): Set/Get the circular object being dimensioned. {set/get}DimPosition(): Set/Get the location of the dimension text. """ def __init__(self): super(RadialDimensionTool, self).__init__() self.__cobj = None self.__position = None def setDimObject(self, cobj): """Store the Circle or Arc that the RadialDimension will describe. setDimObject(cobj): The argument 'cobj' must be either a Circle or Arc instance. """ if not isinstance (cobj, (Circle, Arc)): raise TypeError, "Invalid Circle or Arc: " + `type(cobj)` if cobj.getParent() is None: raise ValueError, "Circle/Arc not found in a Layer." self.__cobj = cobj def getDimObject(self): """Return the object the RadialDimension will define. getDimObject() This method returns a tuple of two objects: the first object is a Layer, the second object is either a Circle or an Arc """ return self.__cobj.getParent(), self.__cobj def setDimPosition(self, x, y): """Store the point where the dimension text will be located. setDimPosition(x, y) Arguments 'x' and 'y' should be float values. """ _x = util.get_float(x) _y = util.get_float(y) self.__position = (_x, _y) def getDimPosition(self): """Retrieve where the dimension text should be placed. getDimPosition() This method returns a tuple containing the x/y coodindates defined by the setDimPosition() call, or None if that method has not been invoked. """ return self.__position def reset(self): """Restore the tool to its initial state. reset() This method extends the reset() methods of its base classes. """ super(RadialDimensionTool, self).reset() self.__cobj = None self.__position = None def makeDimension(self, image): """Create a RadialDimension based on the stored tool values. makeDimension(image) The argument 'image' is an image object where the dimension will be used. """ _cobj = self.__cobj _x, _y = self.__position if (_cobj is not None and _x is not None and _y is not None): _ds = image.getOption('DIM_STYLE') _rdim = dimension.RadialDimension(_cobj, _x, _y, _ds) self._setDimension(_rdim) self.setDimPrefs(image) def create(self, image): """Create a new RadialDimension and add it to the image. create(image) This method overrides the Tool::create() method. """ _rdim = self.getDimension() if _rdim is not None: _pds = _rdim.getPrimaryDimstring() _pds.mute() try: _pds.setPrefix(image.getOption('RADIAL_DIM_PRIMARY_PREFIX')) _pds.setSuffix(image.getOption('RADIAL_DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds = _rdim.getSecondaryDimstring() _sds.mute() try: _sds.setPrefix(image.getOption('RADIAL_DIM_SECONDARY_PREFIX')) _sds.setSuffix(image.getOption('RADIAL_DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() _rdim.setDiaMode(image.getOption('RADIAL_DIM_DIA_MODE')) _rdim.calcDimValues() image.addObject(_rdim) self.reset() class AngularDimensionTool(LinearDimensionTool): """A specialized tool for drawing AngularDimension objects. The AngularDimensionTool class is derived from the LinearDimensionTool class, so it shares all the attributes and methods of that class. The AngularDimensionTool class has the following additional methods: {set/get}VertexPoint(): Set/Get the vertex point used by the dimension """ def __init__(self): super(AngularDimensionTool, self).__init__() self.__vp = None def setVertexPoint(self, p): """Store the vertex point for the AngularDimension. setVertexPoint(p): The argument 'p' must be a Point instance. """ if not isinstance (p, Point): raise TypeError, "Invalid Point: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in layer." self.__vp = p def getVertexPoint(self): """Return the vertex point for the AngularDimension. getVertexPoint() This method returns a tuple of two objects: the first object is a Layer, the second object is a Point. """ return self.__vp def reset(self): """Restore the tool to its initial state. reset() This method extends LinearDimensionTool::reset(). """ super(AngularDimensionTool, self).reset() self.__vp = None def makeDimension(self, image): """Create an AngularDimension based on the stored tool values. makeDimension(image) The argument 'image' is an image object where the dimension will be used. """ _vp = self.__vp _p1 = self.getFirstPoint() _p2 = self.getSecondPoint() _x, _y = self.getDimPosition() if (_vp is not None and _p1 is not None and _p2 is not None and _x is not None and _y is not None): _ds = image.getOption('DIM_STYLE') _adim = dimension.AngularDimension(_vp, _p1, _p2, _x, _y, _ds) self._setDimension(_adim) self.setDimPrefs(image) def create(self, image): """Create a new AngularDimension and add it to the image. create(image) This method overrides the Tool::create() method. """ _adim = self.getDimension() if _adim is not None: _pds = _adim.getPrimaryDimstring() _pds.mute() try: _pds.setPrefix(image.getOption('ANGULAR_DIM_PRIMARY_PREFIX')) _pds.setSuffix(image.getOption('ANGULAR_DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds = _adim.getSecondaryDimstring() _sds.mute() try: _sds.setPrefix(image.getOption('ANGULAR_DIM_SECONDARY_PREFIX')) _sds.setSuffix(image.getOption('ANGULAR_DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() _adim.calcDimValues() image.addObject(_adim) self.reset() # # printing/plotting tools # class PlotTool(Tool): """A tool for defining plot regions """ def __init__(self): super(PlotTool, self).__init__() self.__c1 = None self.__c2 = None def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(PlotTool, self).reset() self.__c1 = None self.__c2 = None def setFirstCorner(self, x, y): """Store the first corner of the plot region. setFirstCorner(x, y) Arguments 'x' and 'y' should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__c1 = (_x, _y) def getFirstCorner(self): """Return the first corner of the plot region. getFirstCorner() """ if self.__c1 is None: raise ValueError, "First corner not defined" return self.__c1 def setSecondCorner(self, x, y): """Store the second corner of the plot region. setSecondCorner(x, y) Arguments 'x' and 'y' should be floats. """ _x = util.get_float(x) _y = util.get_float(y) self.__c2 = (_x, _y) def getSecondCorner(self): """Return the second corner of the plot region. getSecondCorner() """ if self.__c2 is None: raise ValueError, "Second corner not defined" return self.__c2 # # entity modification tools # class RegionTool(Tool): """A base class for a tool that can store a defined region. The RegionTool class is designed to be a base class for tools that need to store a rectangular region. The RegionTool class is derived from the Tool class, so it shares all the attributes and methods of that classs. The RegionTool class has the following additional methods: {set/get}Region(): Set/Get a stored rectangular region """ def __init__(self): super(RegionTool, self).__init__() self.__region = None def setRegion(self, xmin, ymin, xmax, ymax): """Store a rectangular region in the RegionTool. setRegion(xmin, ymin, xmax, ymax) xmin: The minimum x-coordinate value ymin: The minimum y-coordinate value xmax: The maximum x-coordinate value ymax: The maximum y-coordinate value All the arguments should be floats. If xmax < xmin or ymax < ymin a ValueError exception is raised. """ _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Invalid values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Invalid values: ymax < ymin" self.__region = (_xmin, _ymin, _xmax, _ymax) def getRegion(self): """Retrieve the stored rectangular region in the tool. getRegion() This method returns a tuple containing four float values: (xmin, ymin, xmax, ymax) """ return self.__region def reset(self): """Restore the tool to its initial state. reset() This method resets the RegionTool region to None. """ super(RegionTool, self).reset() self.__region = None class MoveTool(RegionTool): """A specialized class for moving objects. The MoveTool class is derived from the Tool and RegionTool classes, so it shares all the attributes and methods of those classes. The MoveTool class has the following additional methods: {set/get}Distance(): Set/Get the values to move objects. """ def __init__(self): super(MoveTool, self).__init__() self.__distance = None def setDistance(self, x, y): """Store the distance to move objects in the tool. setDistance(x, y) Arguments 'x' and 'y' should be floats, and represent the amount to move objects in the x-coordinate and y-coordinate axes. """ _x = util.get_float(x) _y = util.get_float(y) self.__distance = (_x, _y) def getDistance(self): """Get the displacement stored in the tool. getDistance() This method returns a tuple containing two float values. (xdisp, ydisp) If this method is called before the setDistance() method is used, a ValueError exception is raised. """ if self.__distance is None: raise ValueError, "No distance stored in tool." return self.__distance def reset(self): """Restore the tool to its initial state. reset() This method extends the reset() method of its base classes """ super(MoveTool, self).reset() self.__distance = None class HorizontalMoveTool(MoveTool): """A specialized class for moving objects horizontally. The HorizontalMoveTool is derived from the MoveTool class, so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(HorizontalMoveTool, self).__init__() def setDistance(self, x, y): """Store the distance to move objects in the tool. setDistance(x, y) This method extends the MoveTool::setDistance() method by enforcing a y-axis displacement of 0.0 """ _x = util.get_float(x) super(HorizontalMoveTool, self).setDistance(_x, 0.0) class VerticalMoveTool(MoveTool): """A specialized class for moving objects vertically. The VerticalMoveTool is derived from the MoveTool class, so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(VerticalMoveTool, self).__init__() def setDistance(self, x, y): """Store the distance to move objects in the tool. setDistance(x, y) This method extends the MoveTool::setDistance() method by enforcing an x-axis displacement of 0.0 """ _y = util.get_float(y) super(VerticalMoveTool, self).setDistance(0.0, _y) class StretchTool(MoveTool): """A specialized class for stretching objects The StretchTool class is derived from the MoveTool class, so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(StretchTool, self).__init__() class HorizontalStretchTool(HorizontalMoveTool): """A specialized class for stretching objects horizontally. The HorizontalStretchTool class is derived from the HorizontalMoveTool class, so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(HorizontalStretchTool, self).__init__() class VerticalStretchTool(VerticalMoveTool): """A specialized class for stretching objects horizontally. The VerticalStretchTool class is derived from the VerticalMoveTool class, so it shares all the attributes and methods of that class. There are no additional methods for this class. """ def __init__(self): super(VerticalStretchTool, self).__init__() class DeleteTool(RegionTool): """A specialized class for deleting objects. The DeleteTool class is derived from the Tool and RegionTool classes, so it shares all the attributes and methods of those classes. There are no additional methods for this class. """ def __init__(self): super(DeleteTool, self).__init__() class SplitTool(RegionTool): """A specialized class for splitting objects. The SplitTool class is derived from the Tool and RegionTool classes, so it shares all the attributes and methods of those classes. There are no additional methods for this class. """ def __init__(self): super(SplitTool, self).__init__() class MirrorTool(RegionTool): """A specialized class for mirroring objects. The MirrorTool class is derived from the Tool and RegionTool classes, so it shares all the attributes and methods of those classes. The MirrorTool class has the following additional methods: {set/get}MirrorLine(): Set/Get the construction line used for mirroring """ def __init__(self): super(MirrorTool, self).__init__() self.__mirrorline = None def setMirrorLine(self, mline): """Store the mirroring construction line in the tool. setMirrorLine(mline) The argument 'mline' must be a construction line, otherwise a TypeError exception is raised. """ if not isinstance(mline, (HCLine, VCLine, ACLine, CLine)): raise TypeError, "Invalid mirroring line type: " + `type(mline)` self.__mirrorline = mline def getMirrorLine(self): """Retrieve the mirroring construction line from the tool. getMirrorLine() If the mirroring construction line has not been set, a ValueError exception is raised. """ if self.__mirrorline is None: raise ValueError, "No mirror line set." return self.__mirrorline def reset(self): """Restore the tool to its initial state. reset() This method extends the RegionTool::reset() method. """ super(MirrorTool, self).reset() self.__mirrorline = None # # The TransferTool class is a subclass of the RegionTool # with no additional functionality (yet) # class TransferTool(RegionTool): pass class RotateTool(RegionTool): """A specialized class for rotating objects. The RotateTool class is derived from the Tool and RegionTool classes, so it shares all the attributes and methods of those classes. The Rotateool class has the following additional methods: {set/get}RotationPoint(): Set/Get the point objects are rotated around {set/get}Angle(): Set/Get the angle of rotation """ def __init__(self): super(RotateTool, self).__init__() self.__rp = None self.__angle = None def setRotationPoint(self, x, y): """Set the coordinates the entities will rotate around setRotationPoint(x, y) Arguments 'x' and 'y' should be floats """ _x = util.get_float(x) _y = util.get_float(y) self.__rp = (_x, _y) def getRotationPoint(self): """Get the point objects will rotate around getRotationPoint() This method returns a tuple of two floats or None if the rotation point has not be defined with setRotationPoint() """ return self.__rp def setAngle(self, angle): """Set the angle of rotation. setAngle(angle) Argument 'angle' should be a float value. The value is normalized so that abs(angle) < 360.0. """ _a = util.get_float(angle) self.__angle = math.fmod(_a, 360.0) def getAngle(self): """Get the angle of rotation. getAngle() This method returns a float or None if the angle has not been set with setAngle() """ return self.__angle def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(RotateTool, self).reset() self.__rp = None self.__angle = None class GraphicObjectTool(RegionTool): """A specialized class for changing attributes of GraphicObject instances. The GraphicObjectTool class is derived from the RegionTool class, so it shares all the attributes and methods of that class. The GraphicObjectTool class has the following additional methods: {set/get}Attribute(): Set/Get the desired attribute {set/get}Value(): Set/Get the new entity color. """ def __init__(self): super(RegionTool, self).__init__() self.__attr = None self.__value = None def setAttribute(self, attr): """Define which attribute the tool is modifying. setAttribute(attr) Argument 'attr' should be either 'setStyle', 'setLinetype', 'setColor', or 'setThickness' """ if not isinstance(attr, str): raise TypeError, "Invalid attribute type: " + `type(attr)` if attr not in ('setStyle', 'setLinetype', 'setColor', 'setThickness'): raise ValueError, "Invalid attribute: " + attr self.__attr = attr def getAttribute(self): """Return the specified attribute. getAttribute() If called before invoking setAttribute(), this method raises a ValueError. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." return self.__attr def setValue(self, val): """Store the new value of the entity attribute. setValue(val) Argument 'val' depends on the type of attribute defined for the tool. If no attribute is defined this method raises a ValueError. Invoking this method with 'None' as an argument sets the tool to restore the default attribute value. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." _a = self.__attr _val = None if val is not None: if _a == 'setStyle': if not isinstance(val, style.Style): raise TypeError, "Invalid Style: " + `type(val)` _val = val elif _a == 'setLinetype': if not isinstance(val, linetype.Linetype): raise TypeError, "Invalid Linetype: " + `type(val)` _val = val elif _a == 'setColor': if not isinstance(val, color.Color): raise TypeError, "Invalid Color: " + `type(val)` _val = val elif _a == 'setThickness': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid thickness: %g" % _val else: raise ValueError, "Unexpected attribute: " + _a self.__value = _val def getValue(self): """Get the stored attribute value. getValue() This method returns the value stored in setValue() or None. """ return self.__value # # # class ChangeStyleTool(GraphicObjectTool): pass class ChangeLinetypeTool(GraphicObjectTool): pass class ChangeColorTool(GraphicObjectTool): pass class ChangeThicknessTool(GraphicObjectTool): pass class TextTool(RegionTool): """A specialized class for entering text. The TextTool class is derived from the Tool class, so it shares the attributes and methods of that class. The TextTool class also has the following additional methods: {set/get}Text(): Set/Get the text string in the tool. hasText(): Test if the tool has stored a text string {set/get}TextLocation(): Set/Get where the text is to be placed. {set/get}TextBlock(): Set/Get a TextBlock instance in the Tool {set/get}Bounds(): Set/Get the width and height of the text {set/get}PixelSize(): Set/Get the a rectangular region bounding the text. {set/get}Layout(): Set/Get the formatted text string display. {set/get/test}Attribute(): Set/Get/Test a TextBlock attribute {set/get}Value(): Set/Get the attribute value. """ def __init__(self): super(TextTool, self).__init__() self.__text = None self.__location = None self.__tblock = None self.__attr = None self.__value = None self.__bounds = None self.__pixel_size = None self.__layout = None def setText(self, text): """Store some text in the tool. setText(text) The argument 'text' should be a unicode object. """ _text = text if not isinstance(_text, unicode): _text = unicode(text) self.__text = _text def getText(self): """Retrieve the stored text from the TextTool. getText() If no text has been stored, this method raises a ValueError exception. """ if self.__text is None: raise ValueError, "No text stored in TextTool." return self.__text def hasText(self): """Test if the tool has stored a text string. hasText() """ return self.__text is not None def setTextLocation(self, x, y): """Store the location where the text will be placed. setTextLocation(x, y) The arguments 'x' and 'y' should be float values. """ _x, _y = util.make_coords(x, y) self.__location = (_x, _y) def getTextLocation(self): """Retrieve the location where the text will be placed. getTextLocation() This method returns a tuple holding two floats: (x, y) A ValueError exception is raised if this method is called prior to setting the text location with setTextLocation(). """ if self.__location is None: raise ValueError, "No text location defined." return self.__location def testAttribute(self, attr): """Test that the given attribute is valid. testAttribute(attr) Argument 'attr' should be one of the following: 'setAngle', 'setAlignment', 'setFamily', 'setStyle', 'setWeight', 'setColor', or 'setSize' """ if not isinstance(attr, str): raise TypeError, "Invalid attribute type: " + `type(attr)` return attr in ('setAngle', 'setAlignment', 'setFamily', 'setStyle', 'setWeight', 'setColor', 'setSize') def setAttribute(self, attr): """Define which attribute the tool is modifying. setAttribute(attr) Argument 'attr' should be one of the following: 'setAngle', 'setAlignment', 'setFamily', 'setStyle', 'setWeight', 'setColor', or 'setSize' """ if not self.testAttribute(attr): raise ValueError, "Invalid attribute: " + attr self.__attr = attr def getAttribute(self): """Return the specified attribute. getAttribute() If called before invoking setAttribute(), this method raises a ValueError. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." return self.__attr def testValue(self, val): """Test that the given value is valid for the preset attribute. testValue(val) Argument 'val' depends on what attribute has been set with via setAttribute(). """ _a = self.__attr if _a == 'setAngle': _val = util.getFloat(val) elif _a == 'setAlignment': if not isinstance(val, int): raise TypeError, "Invalid alignment type: " + `type(val)` if (val != TextStyle.ALIGN_LEFT and val != TextStyle.ALIGN_CENTER and val != TextStyle.ALIGN_RIGHT): raise ValueError, "Invalid alignment value: %d" % val _val = val elif _a == 'setColor': if not isinstance(val, color.Color): raise TypeError, "Invalid Color: " + `type(val)` _val = val elif _a == 'setFamily': if not isinstance(val, types.StringTypes): raise TypeError, "Invalid family type: " + `type(val)` _val = val elif _a == 'setStyle': if not isinstance(val, int): raise TypeError, "Invalid style type: " + `type(val)` if (val != TextStyle.FONT_NORMAL and val != TextStyle.FONT_OBLIQUE and val != TextStyle.FONT_ITALIC): raise ValueError, "Invalid style value: %d" % val _val = val elif _a == 'setWeight': if not isinstance(val, int): raise TypeError, "Invalid weight type: " + `type(val)` if (val != TextStyle.WEIGHT_NORMAL and val != TextStyle.WEIGHT_LIGHT and val != TextStyle.WEIGHT_BOLD and val != TextStyle.WEIGHT_HEAVY): raise ValueError, "Invalid weight value: %d" % val _val = val elif _a == 'setSize': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid size: %g" % _val else: raise ValueError, "Unexpected attribute: " + _a return _val def setValue(self, val): """Store the new value of the entity attribute. setValue(val) Argument 'val' depends on the type of attribute defined for the tool. If no attribute is defined this method raises a ValueError. Invoking this method with 'None' as an argument sets the tool to restore the default attribute value. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." _val = None if val is not None: _val = self.testValue(val) self.__value = _val def getValue(self): """Get the stored attribute value. getValue() This method returns the value stored in setValue() or None. """ return self.__value def getBounds(self): """Return the width and height of the TextBlock. getBounds() """ if self.__bounds is None: raise ValueError, "TextBlock bounds not defined." return self.__bounds def setBounds(self, width, height): """Set the width and height of the TextBlock. setBounds(width, height): Arguments 'width' and 'height' should be positive float values. """ _w = util.get_float(width) if _w < 0.0: raise ValueError, "Invalid width: %g" % _w _h = util.get_float(height) if _h < 0.0: raise ValueError, "Invalid height: %g" % _h self.__bounds = (_w, _h) def setPixelSize(self, width, height): """Store a screen-size rectangular boundary for the text. setPixelSize(width, height) Arguments 'width' and 'height' should be positive integer values. This method is somewhat GTK specific ... """ _width = width if not isinstance(_width, int): _width = int(width) if _width < 0: raise ValueError, "Invalid width: %d" % _width _height = height if not isinstance(_height, int): _height = int(height) if _height < 0: raise ValueError, "Invalid height: %d" % _height self.__pixel_size = (_width, _height) def getPixelSize(self): """Retrieve the stored rectangular region of text. getPixelSize() A ValueError exception is raised if this method is called before the size has been set by setPixelSize() """ if self.__pixel_size is None: raise ValueError, "Pixel size is not defined." return self.__pixel_size def setLayout(self, layout): """Store a formatted layout string for the text. setLayout() This method is very GTK/Pango specific ... """ self.__layout = layout def getLayout(self): """Retrieve the formatted layout for the text string. getLayout() This method is very GTK/Pango specific ... """ return self.__layout def setTextBlock(self, tblock): """Store a TextBlock instance within the Tool. setTextBlock(tblock) Argument 'tblock' must be a TextBlock. """ if not isinstance(tblock, TextBlock): raise TypeError, "Invalid TextBlock: " + `type(tblock)` self.__tblock = tblock def getTextBlock(self): """Retrieve a stored TextBlock within the Tool. getTextBlock() This method may return None if no TextBlock has been stored via setTextBlock(). """ return self.__tblock def create(self, image): """Create a new TextBlock and add it to the image. create(image) This method overrides the Tool::create() method. """ _tb = self.__tblock if _tb is None: _text = self.getText() _x, _y = self.getTextLocation() _ts = image.getOption('TEXT_STYLE') _tb = TextBlock(_x, _y, _text, _ts) _f = image.getOption('FONT_FAMILY') if _f != _ts.getFamily(): _tb.setFamily(_f) _s = image.getOption('FONT_STYLE') if _s != _ts.getStyle(): _tb.setStyle(_s) _w = image.getOption('FONT_WEIGHT') if _w != _ts.getWeight(): _tb.setWeight(_w) _c = image.getOption('FONT_COLOR') if _c != _ts.getColor(): _tb.setColor(_c) _sz = image.getOption('TEXT_SIZE') if abs(_sz - _ts.getSize()) > 1e-10: _tb.setSize(_sz) _a = image.getOption('TEXT_ANGLE') if abs(_a - _ts.getAngle()) > 1e-10: _tb.setAngle(_a) _al = image.getOption('TEXT_ALIGNMENT') if _al != _ts.getAlignment(): _tb.setAlignment(_al) image.addObject(_tb) self.reset() def reset(self): """Restore the tool to its initial state. reset() This method extends Tool::reset(). """ super(TextTool, self).reset() self.__text = None self.__location = None self.__tblock = None self.__bounds = None self.__pixel_size = None self.__layout = None class EditDimensionTool(RegionTool): """A specialized class for changing attributes of Dimension instances. The EditDimensionTool class is derived from the RegionTool class, so it shares all the attributes and methods of that class. The EditDimensionTool class has the following additional methods: {set/get}Attribute(): Set/Get the desired attribute {set/get}Value(): Set/Get the new entity color. """ def __init__(self): super(RegionTool, self).__init__() self.__attr = None self.__value = None def setAttribute(self, attr): """Define which attribute the tool is modifying. setAttribute(attr) Argument 'attr' should be one of the following: 'setEndpointType', 'setEndpointSize', 'setDualDimMode', 'setDualModeOffset', 'setOffset', 'setExtension', 'setColor', 'setThickness', 'setScale' """ if not isinstance(attr, str): raise TypeError, "Invalid attribute type: " + `type(attr)` if attr not in ('setEndpointType', 'setEndpointSize', 'setDualDimMode', 'setDualModeOffset', 'setOffset', 'setExtension', 'setColor', 'setThickness', 'setScale'): raise ValueError, "Invalid attribute: " + attr self.__attr = attr def getAttribute(self): """Return the specified attribute. getAttribute() If called before invoking setAttribute(), this method raises a ValueError. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." return self.__attr def setValue(self, val): """Store the new value of the entity attribute. setValue(val) Argument 'val' depends on the type of attribute defined for the tool. If no attribute is defined this method raises a ValueError. Invoking this method with 'None' as an argument sets the tool to restore the default attribute value. """ if self.__attr is None: raise ValueError, "Tool attribute not defined." _a = self.__attr _val = None if val is not None: if _a == 'setEndpointType': if (val != dimension.Dimension.DIM_ENDPT_NONE and val != dimension.Dimension.DIM_ENDPT_ARROW and val != dimension.Dimension.DIM_ENDPT_FILLED_ARROW and val != dimension.Dimension.DIM_ENDPT_SLASH and val != dimension.Dimension.DIM_ENDPT_CIRCLE): raise ValueError, "Invalid endpoint value: " + str(val) _val = val elif _a == 'setEndpointSize': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid endpoint size: %g" % _val _val = val elif _a == 'setDualDimMode': util.test_boolean(val) _val = val elif _a == 'setDualModeOffset': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid offset length: %g" % _val _val = val elif _a == 'setOffset': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid offset length: %g" % _val _val = val elif _a == 'setExtension': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid extension length: %g" % _val _val = val elif _a == 'setColor': if not isinstance(val, color.Color): raise TypeError, "Invalid Color: " + `type(val)` _val = val elif _a == 'setThickness': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid thickness: %g" % _val elif _a == 'setScale': _val = util.get_float(val) if _val < 0.0: raise ValueError, "Invalid scale: %g" % _val else: raise ValueError, "Unexpected attribute: " + _a self.__value = _val def getValue(self): """Get the stored attribute value. getValue() This method returns the value stored in setValue() or None. """ return self.__value class EditDimStringTool(TextTool): """A specialized class for modifying DimString instances in Dimensions. The EditDimStringTool class is derived from the TextTool class, so it shares the attributes and methods of that class. The TextTool class has the following additional methods: {set/get}Primary(): Set/Get the DimString on which the tool operates. """ def __init__(self): super(EditDimStringTool, self).__init__() self.__pds = True def setPrimary(self, flag=True): """Set the tool to operate on the primary DimString setPrimary([flag]) Optional argument 'flag' should be a boolean. By default the tool will operate on the primary DimString of a Dimension. If argument 'flag' is False, the tool will operate on the secondary DimString. """ util.test_boolean(flag) self.__pds = flag def getPrimary(self): """Test if the tool operates on the primary DimString getPrimary() This method returns a boolean """ return self.__pds def testAttribute(self, attr): """Test that the attribute is valid for the DimString entity. testAttribute(attr) Argument 'attr' must be one of the following: 'setPrefix', 'setSuffix', 'setUnits', 'setPrecision', 'setPrintZero', 'setPringDecimal', 'setFamily', 'setStyle', 'setWeight', 'setSize', 'setAlignment', 'setColor', or 'setAngle'. """ if not isinstance(attr, str): raise TypeError, "Invalid attribute type: " + `type(attr)` _res = attr in ('setPrefix', 'setSuffix', 'setUnits', 'setPrecision', 'setPrintZero', 'setPrintDecimal') if _res: return _res return super(EditDimStringTool, self).testAttribute(attr) def testValue(self, val): """Test that the value is valid for a given DimString attribute. testValue(val) Argument 'val' depends on the attribute set for the EditDimString instance. """ _a = self.getAttribute() if _a == 'setPrefix' or _a == 'setSuffix': if not isinstance(val, types.StringTypes): raise TypeError, "Invalid %s type: %s " % (_a, `type(val)`) _val = val if not isinstance(_val, unicode): _val = unicode(val) elif _a == 'setPrecision': if not isinstance(val, int): raise TypeError, "Invalid precision type: " + `type(val)` _val = val elif _a == 'setPrintZero' or _a == 'setPrintDecimal': try: util.test_boolean(val) except TypeError: raise TypeError, "Invalid %s type: %s " % (_a, `type(val)`) _val = val elif _a == 'setUnits': _val = val # FIXME: needs error checking ... else: _val = super(EditDimStringTool, self).testValue(val) return _val def setText(self, txt): pass def getText(self): pass def hasText(self): pass def setTextLocation(self, x, y): pass def getTextLocation(self): pass def setBounds(self, width, height): pass def getBounds(self): pass def setPixelSize(self, width, height): pass def getPixelSize(self): pass def setLayout(self, layout): pass def getLayout(self): pass