# # Copyright (c) 2002, 2003, 2004, 2005, 2006 Art Haas # # This file is part of PythonCAD. # # PythonCAD is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # PythonCAD is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PythonCAD; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # generic dimension classes # import math import sys import types from PythonCAD.Generic import text from PythonCAD.Generic import point from PythonCAD.Generic import circle from PythonCAD.Generic import arc from PythonCAD.Generic import color from PythonCAD.Generic import units from PythonCAD.Generic import util from PythonCAD.Generic import tolerance from PythonCAD.Generic import baseobject from PythonCAD.Generic import entity _dtr = math.pi/180.0 _rtd = 180.0/math.pi class DimString(text.TextBlock): """A class for the visual presentation of the dimensional value. The DimString class is used to present the numerical display for the dimension. A DimString object is derived from the text.TextBlock class, so it shares all of that classes methods and attributes. The DimString class has the following additional properties: prefix: A prefix prepended to the dimension text suffix: A suffix appended to the dimension text units: The units the dimension text will display precision: The displayed dimension precision print_zero: Displayed dimensions will have a leading 0 if needed print_decimal: Displayed dimensions will have a trailing decimal point The DimString class has the following additional methods: {get/set}Prefix(): Get/Set the text preceding the dimension value. {get/set}Suffix(): Get/Set the text following the dimension value. {get/set}Units(): Define what units the dimension will be in. {get/set}Precision(): Get/Set how many fractional digits are displayed. {get/set}PrintZero(): Get/Set whether or not a dimension less than one unit long should have a leading 0 printed {get/set}PrintDecimal(): Get/Set whether or not a dimension with 0 fractional digits prints out the decimal point. {get/set}Dimension(): Get/Set the dimension using the DimString The DimString class has the following classmethods: {get/set}DefaultTextStyle(): Get/Set the default TextStyle for the class. """ __defstyle = None __messages = { 'prefix_changed' : True, 'suffix_changed' : True, 'units_changed' : True, 'precision_changed' : True, 'print_zero_changed' : True, 'print_decimal_changed' : True, 'dimension_changed' : True, } def __init__(self, x, y, **kw): """Initialize a DimString object ds = DimString(x, y, **kw) Arguments 'x' and 'y' should be floats. Various keyword arguments can also be used: text: Displayed text textstyle: The default textstyle family: Font family style: Font style weight: Font weight color: Text color size: Text size angle: Text angle align: Text alignment prefix: Default prefix suffix: Default suffix units: Displayed units precision: Displayed precision of units print_zero: Boolean to print a leading '0' print_decimal: Boolean to print a leading '.' By default, a DimString object has the following values ... prefix: Empty string suffix: Empty string unit: Millimeters precision: 3 decimal places print zero: True print decimal: True """ _text = u'' if 'text' in kw: _text = kw['text'] _tstyle = self.getDefaultTextStyle() if 'textstyle' in kw: _tstyle = kw['textstyle'] del kw['textstyle'] _prefix = u'' if 'prefix' in kw: _prefix = kw['prefix'] if not isinstance(_prefix, types.StringTypes): raise TypeError, "Invalid prefix type: " + `type(_prefix)` _suffix = u'' if 'suffix' in kw: _suffix = kw['suffix'] if not isinstance(_suffix, types.StringTypes): raise TypeError, "Invalid suffix type: " + `type(_suffix)` _unit = units.MILLIMETERS if 'units' in kw: _unit = kw['units'] _prec = 3 if 'precision' in kw: _prec = kw['precision'] if not isinstance(_prec, int): raise TypeError, "Invalid precision type: " + `type(_prec)` if _prec < 0 or _prec > 15: raise ValueError, "Invalid precision: %d" % _prec _pz = True if 'print_zero' in kw: _pz = kw['print_zero'] util.test_boolean(_pz) _pd = True if 'print_decimal' in kw: _pd = kw['print_decimal'] util.test_boolean(_pd) super(DimString, self).__init__(x, y, _text, textstyle=_tstyle, **kw) self.__prefix = _prefix self.__suffix = _suffix self.__unit = units.Unit(_unit) self.__precision = _prec self.__print_zero = _pz self.__print_decimal = _pd self.__dim = None def getDefaultTextStyle(cls): if cls.__defstyle is None: _s = text.TextStyle(u'Default Dimension Text Style', color=color.Color(0xffffff), align=text.TextStyle.ALIGN_CENTER) cls.__defstyle = _s return cls.__defstyle getDefaultTextStyle = classmethod(getDefaultTextStyle) def setDefaultTextStyle(cls, s): if not isinstance(s, text.TextStyle): raise TypeError, "Invalid TextStyle: " + `type(s)` cls.__defstyle = s setDefaultTextStyle = classmethod(setDefaultTextStyle) def finish(self): """Finalization for DimString instances. finish() """ if self.__dim is not None: self.__dim = None super(DimString, self).finish() def getValues(self): """Return values comprising the DimString. getValues() This method extends the TextBlock::getValues() method. """ _data = super(DimString, self).getValues() _data.setValue('type', 'dimstring') _data.setValue('prefix', self.__prefix) _data.setValue('suffix', self.__suffix) _data.setValue('units', self.__unit.getStringUnit()) _data.setValue('precision', self.__precision) _data.setValue('print_zero', self.__print_zero) _data.setValue('print_decimal', self.__print_decimal) return _data def getParent(self): """Get the entity containing the DimString. getParent() This method overrides the Entity::getParent() call. """ _parent = None if self.__dim is not None: _parent = self.__dim.getParent() return _parent def setLocation(self, x, y): """Set the location of the DimString. setLocation(x, y) Arguments 'x' and 'y' should be floats. This method extends the TextBlock::setLocation() method. """ # # the DimString location is defined in relation to # the position defined by the Dimension::setLocation() # call, so don't bother sending out 'moved' or 'modified' # messages # self.mute() try: super(DimString, self).setLocation(x, y) finally: self.unmute() def getPrefix(self): """Return the prefix for the DimString object. getPrefix() """ return self.__prefix def setPrefix(self, prefix=None): """Set the prefix for the DimString object. setPrefix([prefix]) Invoking this method without arguments sets the prefix to an empty string, or to the DimStyle value in the associated Dimension if one is set for the DimString. When an argument is passed, the argument should be a Unicode string. """ if self.isLocked(): raise RuntimeError, "Setting prefix not allowed - object locked." _p = prefix if _p is None: _p = u'' if self.__dim is not None: _p = self.__dim.getStyleValue(self, 'prefix') if not isinstance(_p, unicode): _p = unicode(prefix) _op = self.__prefix if _op != _p: self.startChange('prefix_changed') self.__prefix = _p self.endChange('prefix_changed') self.setBounds() self.sendMessage('prefix_changed', _op) self.modified() prefix = property(getPrefix, setPrefix, None, 'Dimension string prefix') def getSuffix(self): """Return the suffix for the DimString object. getSuffix() """ return self.__suffix def setSuffix(self, suffix=None): """Set the suffix for the DimString object. setSuffix([suffix]) Invoking this method without arguments sets the suffix to an empty string, or to the DimStyle value in the associated Dimension if one is set for the DimString.. When an argument is passed, the argument should be a Unicode string. """ if self.isLocked(): raise RuntimeError, "Setting suffix not allowed - object locked." _s = suffix if _s is None: _s = u'' if self.__dim is not None: _s = self.__dim.getStyleValue(self, 'suffix') if not isinstance(_s, unicode): _s = unicode(suffix) _os = self.__suffix if _os != _s: self.startChange('suffix_changed') self.__suffix = _s self.endChange('suffix_changed') self.setBounds() self.sendMessage('suffix_changed', _os) self.modified() suffix = property(getSuffix, setSuffix, None, 'Dimension string suffix') def getPrecision(self): """Return the number of decimal points used for the DimString. getPrecision() """ return self.__precision def setPrecision(self, precision=None): """Set the number of decimal points used for the DimString. setPrecision([p]) The valid range of p is 0 <= p <= 15. Invoking this method without arguments sets the precision to 3, or to the DimStyle value in the associated Dimension if one is set for the DimString.. """ if self.isLocked(): raise RuntimeError, "Setting precision not allowed - object locked." _p = precision if _p is None: _p = 3 if self.__dim is not None: _p = self.__dim.getStyleValue(self, 'precision') if not isinstance(_p, int): raise TypeError, "Invalid precision type: " + `type(_p)` if _p < 0 or _p > 15: raise ValueError, "Invalid precision: %d" % _p _op = self.__precision if _op != _p: self.startChange('precision_changed') self.__precision = _p self.endChange('precision_changed') self.setBounds() self.sendMessage('precision_changed', _op) self.modified() precision = property(getPrecision, setPrecision, None, 'Dimension precision') def getUnits(self): """Return the current units used in the DimString(). getUnits() """ return self.__unit.getUnit() def setUnits(self, unit=None): """The the units for the DimString. setUnits([unit]) The value units are given in the units module. Invoking this method without arguments sets the units to millimeters, or to the DimStyle value of the associated Dimension if one is set for the DimString. """ _u = unit if _u is None: _u = units.MILLIMETERS if self.__dim is not None: _u = self.__dim.getStyleValue(self, 'units') _ou = self.__unit.getUnit() if _ou != _u: self.startChange('units_changed') self.__unit.setUnit(_u) self.endChange('units_changed') self.setBounds() self.sendMessage('units_changed', _ou) self.modified() units = property(getUnits, setUnits, None, 'Dimensional units.') def getPrintZero(self): """Return whether or not a leading 0 is printed for the DimString. getPrintZero() """ return self.__print_zero def setPrintZero(self, print_zero=None): """Set whether or not a leading 0 is printed for the DimString. setPrintZero([pz]) Invoking this method without arguments sets the value to True, or to the DimStyle value of the associated Dimension if one is set for the DimString. If called with an argument, the argument should be either True or False. """ _pz = print_zero if _pz is None: _pz = True if self.__dim is not None: _pz = self.__dim.getStyleValue(self, 'print_zero') util.test_boolean(_pz) _flag = self.__print_zero if _flag is not _pz: self.startChange('print_zero_changed') self.__print_zero = _pz self.endChange('print_zero_changed') self.setBounds() self.sendMessage('print_zero_changed', _flag) self.modified() print_zero = property(getPrintZero, setPrintZero, None, 'Print a leading 0 for decimal dimensions') def getPrintDecimal(self): """Return whether or not the DimString will print a trailing decimal. getPrintDecimal() """ return self.__print_decimal def setPrintDecimal(self, print_decimal=None): """Set whether or not the DimString will print a trailing decimal. setPrintDecimal([pd]) Invoking this method without arguments sets the value to True, or to the DimStyle value of the associated Dimension if one is set for the DimString. If called with an argument, the argument should be either True or False. """ _pd = print_decimal if _pd is None: _pd = True if self.__dim is not None: _pd = self.__dim.getStyleValue(self, 'print_decimal') util.test_boolean(_pd) _flag = self.__print_decimal if _flag is not _pd: self.startChange('print_decimal_changed') self.__print_decimal = _pd self.endChange('print_decimal_changed') self.setBounds() self.sendMessage('print_decimal_changed', _flag) self.modified() print_decimal = property(getPrintDecimal, setPrintDecimal, None, 'Print a decimal point after the dimension value') def getDimension(self): """Return the dimension using the Dimstring. getDimension() This method can return None if there is not Dimension association set for the DimString. """ return self.__dim def setDimension(self, dim, adjust): """Set the dimension using this DimString. setDimension(dim, adjust) Argument 'dim' must be a Dimension or None, and argument 'adjust' must be a Boolean. Argument 'adjust' is only used if a Dimension is passed for the first argument. """ _dim = dim if _dim is not None and not isinstance(_dim, Dimension): raise TypeError, "Invalid dimension: " + `type(_dim)` util.test_boolean(adjust) _d = self.__dim if _d is not _dim: self.startChange('dimension_changed') self.__dim = _dim self.endChange('dimension_changed') if _dim is not None and adjust: self.setPrefix() self.setSuffix() self.setPrecision() self.setUnits() self.setPrintZero() self.setPrintDecimal() self.setFamily() self.setStyle() self.setWeight() self.setColor() self.setSize() self.setAngle() self.setAlignment() self.sendMessage('dimension_changed', _d) self.modified() if self.__dim is not None: self.setParent(self.__dim.getParent()) # # extend the TextBlock set methods to use the values # found in a DimStyle if one is available # def setFamily(self, family=None): """Set the font family for the DimString. setFamily([family]) Calling this method without an argument will set the family to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _family = family if _family is None and self.__dim is not None: _family = self.__dim.getStyleValue(self, 'font_family') super(DimString, self).setFamily(_family) def setStyle(self, style=None): """Set the font style for the DimString. setStyle([style]) Calling this method without an argument will set the font style to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _style = style if _style is None and self.__dim is not None: _style = self.__dim.getStyleValue(self, 'font_style') super(DimString, self).setStyle(_style) def setWeight(self, weight=None): """Set the font weight for the DimString. setWeight([weight]) Calling this method without an argument will set the font weight to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _weight = weight if _weight is None and self.__dim is not None: _weight = self.__dim.getStyleValue(self, 'font_weight') super(DimString, self).setWeight(_weight) def setColor(self, color=None): """Set the font color for the DimString. setColor([color]) Calling this method without an argument will set the font color to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _color = color if _color is None and self.__dim is not None: _color = self.__dim.getStyleValue(self, 'color') super(DimString, self).setColor(_color) def setSize(self, size=None): """Set the text size for the DimString. setSize([size]) Calling this method without an argument will set the text size to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _size = size if _size is None and self.__dim is not None: _size = self.__dim.getStyleValue(self, 'size') super(DimString, self).setSize(_size) def setAngle(self, angle=None): """Set the text angle for the DimString. setAngle([angle]) Calling this method without an argument will set the text angle to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _angle = angle if _angle is None and self.__dim is not None: _angle = self.__dim.getStyleValue(self, 'angle') super(DimString, self).setAngle(_angle) def setAlignment(self, align=None): """Set the text alignment for the DimString. setAlignment([align]) Calling this method without an argument will set the text alignment to that given in the DimStyle of the associated Dimension if one is set for this DimString. """ _align = align if _align is None and self.__dim is not None: _align = self.__dim.getStyleValue(self, 'alignment') super(DimString, self).setAlignment(_align) def setText(self, text): """Set the text in the DimString. This method overrides the setText method in the TextBlock. """ pass def formatDimension(self, dist): """Return a formatted numerical value for a dimension. formatDimension(dist) The argument 'dist' should be a float value representing the distance in millimeters. The returned value will have the prefix prepended and suffix appended to the numerical value that has been formatted with the precision. """ _d = abs(util.get_float(dist)) _fmtstr = u"%%#.%df" % self.__precision _dstr = _fmtstr % self.__unit.fromMillimeters(_d) if _d < 1.0 and self.__print_zero is False: _dstr = _dstr[1:] if _dstr.endswith('.') and self.__print_decimal is False: _dstr = _dstr[:-1] _text = self.__prefix + _dstr + self.__suffix # # don't send out 'text_changed' or 'modified' messages # self.mute() try: super(DimString, self).setText(_text) finally: self.unmute() return _text def sendsMessage(self, m): if m in DimString.__messages: return True return super(DimString, self).sendsMessage(m) class DimBar(entity.Entity): """The class for the dimension bar. A dimension bar leads from the point the dimension references out to, and possibly beyond, the point where the dimension text bar the DimBar to another DimBar. Linear, horizontal, vertical, and angular dimension will have two dimension bars; radial dimensions have none. The DimBar class has the following methods: getEndpoints(): Get the x/y position of the DimBar start and end {get/set}FirstEndpoint(): Get/Set the starting x/y position of the DimBar. {get/set}SecondEndpoint(): Get/Set the ending x/y position of the DimBar. getAngle(): Get the angle at which the DimBar slopes getSinCosValues(): Get trig values used for transformation calculations. """ __messages = { 'attribute_changed' : True, } def __init__(self, x1=0.0, y1=0.0, x2=0.0, y2=0.0, **kw): """Initialize a DimBar. db = DimBar([x1, y1, x2, y2]) By default all the arguments are 0.0. Any arguments passed to this method should be float values. """ _x1 = util.get_float(x1) _y1 = util.get_float(y1) _x2 = util.get_float(x2) _y2 = util.get_float(y2) super(DimBar, self).__init__(**kw) self.__ex1 = _x1 self.__ey1 = _y1 self.__ex2 = _x2 self.__ey2 = _y2 def getEndpoints(self): """Return the coordinates of the DimBar endpoints. getEndpoints() This method returns two tuples, each containing two float values. The first tuple gives the x/y coordinates of the DimBar start, the second gives the coordinates of the DimBar end. """ _ep1 = (self.__ex1, self.__ey1) _ep2 = (self.__ex2, self.__ey2) return _ep1, _ep2 def setFirstEndpoint(self, x, y): """Set the starting coordinates for the DimBar setFirstEndpoint(x, y) Arguments x and y should be float values. """ if self.isLocked(): raise RuntimeError, "Setting endpoint not allowed - object locked." _x = util.get_float(x) _y = util.get_float(y) _sx = self.__ex1 _sy = self.__ey1 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10: self.__ex1 = _x self.__ey1 = _y self.sendMessage('attribute_changed', 'endpoint', _sx, _sy, self.__ex2, self.__ey2) self.modified() def getFirstEndpoint(self): """Return the starting coordinates of the DimBar. getFirstEndpoint() This method returns a tuple giving the x/y coordinates. """ return self.__ex1, self.__ey1 def setSecondEndpoint(self, x, y): """Set the ending coordinates for the DimBar setSecondEndpoint(x, y) Arguments x and y should be float values. """ if self.isLocked(): raise RuntimeError, "Setting endpoint not allowed - object locked." _x = util.get_float(x) _y = util.get_float(y) _sx = self.__ex2 _sy = self.__ey2 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10: self.__ex2 = _x self.__ey2 = _y self.sendMessage('attribute_changed', 'endpoint', self.__ex1, self.__ey1, _sx, _sy) self.modified() def getSecondEndpoint(self): """Return the ending coordinates of the DimBar. getSecondEndpoint() This method returns a tuple giving the x/y coordinates. """ return self.__ex2, self.__ey2 def getAngle(self): """Return the angle at which the DimBar lies. getAngle() This method returns a float value giving the angle of inclination of the DimBar. The value returned will be a positive value less than 360.0. """ _x1 = self.__ex1 _y1 = self.__ey1 _x2 = self.__ex2 _y2 = self.__ey2 if abs(_x2 - _x1) < 1e-10 and abs(_y2 - _y1) < 1e-10: raise ValueError, "Endpoints are equal" if abs(_x2 - _x1) < 1e-10: # vertical if _y2 > _y1: _angle = 90.0 else: _angle = 270.0 elif abs(_y2 - _y1) < 1e-10: # horizontal if _x2 > _x1: _angle = 0.0 else: _angle = 180.0 else: _angle = _rtd * math.atan2((_y2 - _y1), (_x2 - _x1)) if _angle < 0.0: _angle = _angle + 360.0 return _angle def getSinCosValues(self): """Return sin()/cos() values based on the DimBar slope getSinCosValues() This method returns a tuple of two floats. The first value is the sin() value, the second is the cos() value. """ _x1 = self.__ex1 _y1 = self.__ey1 _x2 = self.__ex2 _y2 = self.__ey2 if abs(_x2 - _x1) < 1e-10: # vertical _cosine = 0.0 if _y2 > _y1: _sine = 1.0 else: _sine = -1.0 elif abs(_y2 - _y1) < 1e-10: # horizontal _sine = 0.0 if _x2 > _x1: _cosine = 1.0 else: _cosine = -1.0 else: _angle = math.atan2((_y2 - _y1), (_x2 - _x1)) _sine = math.sin(_angle) _cosine = math.cos(_angle) return _sine, _cosine def sendsMessage(self, m): if m in DimBar.__messages: return True return super(DimBar, self).sendsMessage(m) class DimCrossbar(DimBar): """The class for the Dimension crossbar. The DimCrossbar class is drawn between two DimBar objects for horizontal, vertical, and generic linear dimensions. The dimension text is place over the DimCrossbar object. Arrow heads, circles, or slashes can be drawn at the intersection of the DimCrossbar and the DimBar if desired. These objects are called markers. The DimCrossbar class is derived from the DimBar class so it shares all the methods of that class. In addition the DimCrossbar class has the following methods: {set/get}FirstCrossbarPoint(): Set/Get the initial location of the crossbar. {set/get}SecondCrossbarPoint(): Set/Get the ending location of the crossbar. getCrossbarPoints(): Get the location of the crossbar endpoints. clearMarkerPoints(): Delete the stored coordintes of the dimension markers. storeMarkerPoint(): Save a coordinate pair of the dimension marker. getMarkerPoints(): Return the coordinates of the dimension marker. """ __messages = {} def __init__(self, **kw): """Initialize a DimCrossbar object. dcb = DimCrossbar() This method takes no arguments. """ super(DimCrossbar, self).__init__(**kw) self.__mx1 = 0.0 self.__my1 = 0.0 self.__mx2 = 0.0 self.__my2 = 0.0 self.__mpts = [] def setFirstCrossbarPoint(self, x, y): """Store the initial endpoint of the DimCrossbar. setFirstCrossbarPoint(x, y) Arguments x and y should be floats. """ if self.isLocked(): raise RuntimeError, "Setting crossbar point not allowed - object locked." _x = util.get_float(x) _y = util.get_float(y) _sx = self.__mx1 _sy = self.__my1 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10: self.__mx1 = _x self.__my1 = _y self.sendMessage('attribute_changed', 'barpoint', _sx, _sy, self.__mx2, self.__my2) self.modified() def getFirstCrossbarPoint(self): """Return the initial coordinates of the DimCrossbar. getFirstCrossbarPoint() This method returns a tuple of two floats giving the x/y coordinates. """ return self.__mx1, self.__my1 def setSecondCrossbarPoint(self, x, y): """Store the terminal endpoint of the DimCrossbar. setSecondCrossbarPoint(x, y) Arguments 'x' and 'y' should be floats. """ if self.isLocked(): raise RuntimeError, "Setting crossbar point not allowed - object locked" _x = util.get_float(x) _y = util.get_float(y) _sx = self.__mx2 _sy = self.__my2 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10: self.__mx2 = _x self.__my2 = _y self.sendMessage('attribute_changed', 'barpoint', self.__mx1, self.__my1, _sx, _sy) self.modified() def getSecondCrossbarPoint(self): """Return the terminal coordinates of the DimCrossbar. getSecondCrossbarPoint() This method returns a tuple of two floats giving the x/y coordinates. """ return self.__mx2, self.__my2 def getCrossbarPoints(self): """Return the endpoints of the DimCrossbar. getCrossbarPoints() This method returns two tuples, each tuple containing two float values giving the x/y coordinates. """ _mp1 = (self.__mx1, self.__my1) _mp2 = (self.__mx2, self.__my2) return _mp1, _mp2 def clearMarkerPoints(self): """Delete the stored location of any dimension markers. clearMarkerPoints() """ del self.__mpts[:] def storeMarkerPoint(self, x, y): """Save a coordinate pair of the current dimension marker. storeMarkerPoint(x, y) Arguments 'x' and 'y' should be floats. Each time this method is invoked the list of stored coordinates is appended with the values given as arguments. """ _x = util.get_float(x) _y = util.get_float(y) self.__mpts.append((_x, _y)) def getMarkerPoints(self): """Return the stored marker coordinates. getMarkerPoints() This method returns a list of coordinates stored with storeMarkerPoint(). Each item in the list is a tuple holding two float values - the x/y coordinate of the point. """ return self.__mpts[:] def sendsMessage(self, m): if m in DimCrossbar.__messages: return True return super(DimCrossbar, self).sendsMessage(m) class DimCrossarc(DimCrossbar): """The class for specialized crossbars for angular dimensions. The DimCrossarc class is meant to be used only with angular dimensions. As an angular dimension has two DimBar objects that are connected with an arc. The DimCrossarc class is derived from the DimCrossbar class so it shares all the methods of that class. The DimCrossarc class has the following additional methods: {get/set}Radius(): Get/Set the radius of the arc. {get/set}StartAngle(): Get/Set the arc starting angle. {get/set}EndAngle(): Get/Set the arc finishing angle. """ __messages = { 'arcpoint_changed' : True, 'radius_changed' : True, 'start_angle_changed' : True, 'end_angle_changed' : True, } def __init__(self, radius=0.0, start=0.0, end=0.0, **kw): """Initialize a DimCrossarc object. dca = DimCrossarc([radius, start, end]) By default the arguments are all 0.0. Any arguments passed to this method should be floats. """ super(DimCrossarc, self).__init__(**kw) _r = util.get_float(radius) if _r < 0.0: raise ValueError, "Invalid radius: %g" % _r _start = util.make_c_angle(start) _end = util.make_c_angle(end) self.__radius = _r self.__start = _start self.__end = _end def getRadius(self): """Return the radius of the arc. getRadius() This method returns a float value. """ return self.__radius def setRadius(self, radius): """Set the radius of the arc. setRadius(radius) Argument 'radius' should be a float value greater than 0.0. """ if self.isLocked(): raise RuntimeError, "Setting radius not allowed - object locked." _r = util.get_float(radius) if _r < 0.0: raise ValueError, "Invalid radius: %g" % _r _sr = self.__radius if abs(_sr - _r) > 1e-10: self.startChange('radius_changed') self.__radius = _r self.endChange('radius_changed') self.sendMessage('radius_changed', _sr) self.modified() def getStartAngle(self): """Return the arc starting angle. getStartAngle() This method returns a float. """ return self.__start def setStartAngle(self, angle): """Set the starting angle of the arc. setStartAngle(angle) Argument angle should be a float value. """ if self.isLocked(): raise RuntimeError, "Setting start angle not allowed - object locked." _sa = self.__start _angle = util.make_c_angle(angle) if abs(_sa - _angle) > 1e-10: self.startChange('start_angle_changed') self.__start = _angle self.endChange('start_angle_changed') self.sendMessage('start_angle_changed', _sa) self.modified() def getEndAngle(self): """Return the arc ending angle. getEndAngle() This method returns a float. """ return self.__end def setEndAngle(self, angle): """Set the ending angle of the arc. setEndAngle(angle) Argument angle should be a float value. """ if self.isLocked(): raise RuntimeError, "Setting end angle not allowed - object locked." _ea = self.__end _angle = util.make_c_angle(angle) if abs(_ea - _angle) > 1e-10: self.startChange('end_angle_changed') self.__end = _angle self.endChange('end_angle_changed') self.sendMessage('end_angle_changed', _ea) self.modified() def getAngle(self): pass # override the DimBar::getAngle() method def getSinCosValues(self): pass # override the DimBar::getSinCosValues() method def sendsMessage(self, m): if m in DimCrossarc.__messages: return True return super(DimCrossarc, self).sendsMessage(m) class Dimension(entity.Entity): """The base class for Dimensions A Dimension object is meant to be a base class for specialized dimensions. Every Dimension object holds two DimString objects, so any dimension can be displayed with two separate formatting options and units. A Dimension has the following methods {get/set}DimStyle(): Get/Set the DimStyle used for this Dimension. getPrimaryDimstring(): Return the DimString used for formatting the Primary dimension. getSecondaryDimstring(): Return the DimString used for formatting the Secondary dimension. {get/set}EndpointType(): Get/Set the type of endpoints used in the Dimension {get/set}EndpointSize(): Get/Set the size of the dimension endpoints {get/set}DualDimMode(): Get/Set whether or not to display both the Primary and Secondary DimString objects {get/set}Offset(): Get/Set how far from the dimension endpoints to draw dimension lines at the edges of the dimension. {get/set}Extension(): Get/Set how far past the dimension crossbar line to draw. {get/set}Position(): Get/Set where the dimensional values are placed on the dimension cross bar. {get/set}Color(): Get/Set the color used to draw the dimension lines. {get/set}Location(): Get/Set where to draw the dimensional values. {get/set}PositionOffset(): Get/Set the dimension text offset when the text is above or below the crossbar/crossarc {get/set}DualModeOffset(): Get/Set the text offset for spaceing the two dimension strings above and below the bar separating the two dimensions {get/set}Thickness(): Get/Set the Dimension thickness. {get/set}Scale(): Get/Set the Dimension scaling factor. getStyleValue(): Return the DimStyle value for some option getDimensions(): Return the formatted dimensional values in this Dimension. inRegion(): Return if the dimension is visible within some are. calcDimValues(): Calculate the dimension lines endpoints. mapCoords(): Return the coordinates on the dimension within some point. onDimension(): Test if an x/y coordinate pair hit the dimension lines. getBounds(): Return the minma and maximum locations of the dimension. The Dimension class has the following classmethods: {get/set}DefaultDimStyle(): Get/Set the default DimStyle for the class. getEndpointTypeAsString(): Return the endpoint type as a string for a value. getEndpointTypeFromString(): Return the endpoint type value given a string. getEndpointTypeStrings(): Get the endpoint types values as strings. getEndpointTypeValues(): Get the endpoint type values. getPositionAsString(): Return the text position as a string for a value. getPositionFromString(): Return the text postion value given a string. getPositionStrings(): Get the text position values as strings. getPositionValues(): Get the text position values. """ # # Endpoint # DIM_ENDPT_NONE= 0 DIM_ENDPT_ARROW = 1 DIM_ENDPT_FILLED_ARROW = 2 DIM_ENDPT_SLASH = 3 DIM_ENDPT_CIRCLE = 4 # # Dimension position on dimline # DIM_TEXT_POS_SPLIT = 0 DIM_TEXT_POS_ABOVE = 1 DIM_TEXT_POS_BELOW = 2 __defstyle = None __messages = { 'dimstyle_changed' : True, 'endpoint_type_changed' : True, 'endpoint_size_changed' : True, 'dual_mode_changed' : True, 'offset_changed' : True, 'extension_changed' : True, 'position_changed' : True, 'position_offset_changed' : True, 'dual_mode_offset_changed' : True, 'color_changed' : True, 'thickness_changed' : True, 'scale_changed' : True, 'location_changed' : True, 'dimstring_changed' : True, 'moved' : True, } def __init__(self, x, y, dimstyle=None, **kw): """Initialize a Dimension object dim = Dimension(x, y[, ds]) Arguments 'x' and 'y' should be float values. Optional argument 'ds' should be a DimStyle instance. A default DimStyle is used of the optional argument is not used. """ _x = util.get_float(x) _y = util.get_float(y) _ds = dimstyle if _ds is None: _ds = self.getDefaultDimStyle() if not isinstance(_ds, DimStyle): raise TypeError, "Invalid DimStyle type: " + `type(_ds)` _ddm = None if 'dual-mode' in kw: _ddm = kw['dual-mode'] if _ddm is not None: util.test_boolean(_ddm) if _ddm is _ds.getValue('DIM_DUAL_MODE'): _ddm = None _offset = None if 'offset' in kw: _offset = util.get_float(kw['offset']) if _offset is not None: if _offset < 0.0: raise ValueError, "Invalid dimension offset: %g" % _offset if abs(_offset - _ds.getValue('DIM_OFFSET')) < 1e-10: _offset = None _extlen = None if 'extlen' in kw: _extlen = util.get_float(kw['extlen']) if _extlen < 0.0: raise ValueError, "Invalid dimension extension: %g" % _extlen if abs(_extlen - _ds.getValue('DIM_EXTENSION')) < 1e-10: _extlen = None _textpos = None if 'textpos' in kw: _textpos = kw['textpos'] if (_textpos != Dimension.DIM_TEXT_POS_SPLIT and _textpos != Dimension.DIM_TEXT_POS_ABOVE and _textpos != Dimension.DIM_TEXT_POS_BELOW): raise ValueError, "Invalid dimension text position: '%s'" % str(_textpos) if _textpos == _ds.getValue('DIM_POSITION'): _textpos = None _poffset = None if 'poffset' in kw: _poffset = util.get_float(kw['poffset']) if _poffset < 0.0: raise ValueError, "Invalid text offset length %g" % _poffset if abs(_poffset - _ds.getValue('DIM_POSITION_OFFSET')) < 1e-10: _poffset = None _dmoffset = None if 'dmoffset' in kw: _dmoffset = util.get_float(kw['dmoffset']) if _dmoffset < 0.0: raise ValueError, "Invalid dual mode offset length %g" % _dmoffset if abs(_dmoffset - _ds.getValue('DIM_DUAL_MODE_OFFSET')) < 1e-10: _dmoffset = None _eptype = None if 'eptype' in kw: _eptype = kw['eptype'] if (_eptype != Dimension.DIM_ENDPT_NONE and _eptype != Dimension.DIM_ENDPT_ARROW and _eptype != Dimension.DIM_ENDPT_FILLED_ARROW and _eptype != Dimension.DIM_ENDPT_SLASH and _eptype != Dimension.DIM_ENDPT_CIRCLE): raise ValueError, "Invalid endpoint: '%s'" % str(_eptype) if _eptype == _ds.getValue('DIM_ENDPOINT'): _eptype = None _epsize = None if 'epsize' in kw: _epsize = util.get_float(kw['epsize']) if _epsize < 0.0: raise ValueError, "Invalid endpoint size %g" % _epsize if abs(_epsize - _ds.getValue('DIM_ENDPOINT_SIZE')) < 1e-10: _epsize = None _color = None if 'color' in kw: _color = kw['color'] if not isinstance(_color, color.Color): raise TypeError, "Invalid color type: " + `type(_color)` if _color == _ds.getValue('DIM_COLOR'): _color = None _thickness = None if 'thickness' in kw: _thickness = util.get_float(kw['thickness']) if _thickness < 0.0: raise ValueError, "Invalid thickness: %g" % _thickness if abs(_thickness - _ds.getValue('DIM_THICKNESS')) < 1e-10: _thickness = None _scale = 1.0 if 'scale' in kw: _scale = util.get_float(kw['scale']) if not _scale > 0.0: raise ValueError, "Invalid scale: %g" % _scale # # dimstrings # # the setDimension() call will adjust the values in the # new DimString instances if they get created # _ds1 = _ds2 = None _ds1adj = _ds2adj = True if 'ds1' in kw: _ds1 = kw['ds1'] if not isinstance(_ds1, DimString): raise TypeError, "Invalid DimString type: " + `type(_ds1)` _ds1adj = False if _ds1 is None: _ds1 = DimString(_x, _y) # if 'ds2' in kw: _ds2 = kw['ds2'] if not isinstance(_ds2, DimString): raise TypeError, "Invalid DimString type: " + `type(_ds2)` _ds2adj = False if _ds2 is None: _ds2 = DimString(_x, _y) # # finally ... # super(Dimension, self).__init__(**kw) self.__dimstyle = _ds self.__ddm = _ddm self.__offset = _offset self.__extlen = _extlen self.__textpos = _textpos self.__poffset = _poffset self.__dmoffset = _dmoffset self.__eptype = _eptype self.__epsize = _epsize self.__color = _color self.__thickness = _thickness self.__scale = _scale self.__dimloc = (_x, _y) self.__ds1 = _ds1 self.__ds2 = _ds2 self.__ds1.setDimension(self, _ds1adj) self.__ds2.setDimension(self, _ds2adj) _ds1.connect('change_pending', self.__dimstringChangePending) _ds1.connect('change_complete', self.__dimstringChangeComplete) _ds2.connect('change_pending', self.__dimstringChangePending) _ds2.connect('change_complete', self.__dimstringChangeComplete) def getDefaultDimStyle(cls): if cls.__defstyle is None: cls.__defstyle = DimStyle(u'Default DimStyle') return cls.__defstyle getDefaultDimStyle = classmethod(getDefaultDimStyle) def setDefaultDimStyle(cls, s): if not isinstance(s, DimStyle): raise TypeError, "Invalid DimStyle: " + `type(s)` cls.__defstyle = s setDefaultDimStyle = classmethod(setDefaultDimStyle) def finish(self): self.__ds1.disconnect(self) self.__ds2.disconnect(self) self.__ds1.finish() self.__ds2.finish() self.__ds1 = self.__ds2 = None super(Dimension, self).finish() def getValues(self): """Return values comprising the Dimension. getValues() This method extends the Entity::getValues() method. """ _data = super(Dimension, self).getValues() _data.setValue('location', self.__dimloc) if self.__offset is not None: _data.setValue('offset', self.__offset) if self.__extlen is not None: _data.setValue('extension', self.__extlen) if self.__textpos is not None: _data.setValue('position', self.__textpos) if self.__eptype is not None: _data.setValue('eptype', self.__eptype) if self.__epsize is not None: _data.setValue('epsize', self.__epsize) if self.__color is not None: _data.setValue('color', self.__color.getColors()) if self.__ddm is not None: _data.setValue('dualmode', self.__ddm) if self.__poffset is not None: _data.setValue('poffset', self.__poffset) if self.__dmoffset is not None: _data.setValue('dmoffset', self.__dmoffset) if self.__thickness is not None: _data.setValue('thickness', self.__thickness) _data.setValue('ds1', self.__ds1.getValues()) _data.setValue('ds2', self.__ds2.getValues()) _data.setValue('dimstyle', self.__dimstyle.getValues()) return _data def getDimStyle(self): """Return the DimStyle used in this Dimension. getDimStyle() """ return self.__dimstyle def setDimStyle(self, ds): """Set the DimStyle used for this Dimension. setDimStyle(ds) After setting the DimStyle, the values stored in it are applied to the DimensionObject. """ if self.isLocked(): raise RuntimeError, "Changing dimstyle not allowed - object locked." if not isinstance(ds, DimStyle): raise TypeError, "Invalid DimStyle type: " + `type(ds)` _sds = self.__dimstyle if ds is not _sds: _opts = self.getValues() self.startChange('dimstyle_changed') self.__dimstyle = ds self.endChange('dimstyle_changed') # # call the various methods without arguments # so the values given in the new DimStyle are used # self.setOffset() self.setExtension() self.setPosition() self.setEndpointType() self.setEndpointSize() self.setColor() self.setThickness() self.setDualDimMode() self.setPositionOffset() self.setDualModeOffset() # # set the values in the two DimString instances # _d = self.__ds1 _d.setPrefix(ds.getValue('DIM_PRIMARY_PREFIX')) _d.setSuffix(ds.getValue('DIM_PRIMARY_SUFFIX')) _d.setPrecision(ds.getValue('DIM_PRIMARY_PRECISION')) _d.setUnits(ds.getValue('DIM_PRIMARY_UNITS')) _d.setPrintZero(ds.getValue('DIM_PRIMARY_LEADING_ZERO')) _d.setPrintDecimal(ds.getValue('DIM_PRIMARY_TRAILING_DECIMAL')) _d.setFamily(ds.getValue('DIM_PRIMARY_FONT_FAMILY')) _d.setWeight(ds.getValue('DIM_PRIMARY_FONT_WEIGHT')) _d.setStyle(ds.getValue('DIM_PRIMARY_FONT_STYLE')) _d.setColor(ds.getValue('DIM_PRIMARY_FONT_COLOR')) _d.setSize(ds.getValue('DIM_PRIMARY_TEXT_SIZE')) _d.setAngle(ds.getValue('DIM_PRIMARY_TEXT_ANGLE')) _d.setAlignment(ds.getVaue('DIM_PRIMARY_TEXT_ALIGNMENT')) _d = self.__ds2 _d.setPrefix(ds.getValue('DIM_SECONDARY_PREFIX')) _d.setSuffix(ds.getValue('DIM_SECONDARY_SUFFIX')) _d.setPrecision(ds.getValue('DIM_SECONDARY_PRECISION')) _d.setUnits(ds.getValue('DIM_SECONDARY_UNITS')) _d.setPrintZero(ds.getValue('DIM_SECONDARY_LEADING_ZERO')) _d.setPrintDecimal(ds.getValue('DIM_SECONDARY_TRAILING_DECIMAL')) _d.setFamily(ds.getValue('DIM_SECONDARY_FONT_FAMILY')) _d.setWeight(ds.getValue('DIM_SECONDARY_FONT_WEIGHT')) _d.setStyle(ds.getValue('DIM_SECONDARY_FONT_STYLE')) _d.setColor(ds.getValue('DIM_SECONDARY_FONT_COLOR')) _d.setSize(ds.getValue('DIM_SECONDARY_TEXT_SIZE')) _d.setAngle(ds.getValue('DIM_SECONDARY_TEXT_ANGLE')) _d.setAlignment(ds.getVaue('DIM_SECONDARY_TEXT_ALIGNMENT')) self.sendMessage('dimstyle_changed', _sds, _opts) self.modified() dimstyle = property(getDimStyle, setDimStyle, None, "Dimension DimStyle object.") def getEndpointTypeAsString(cls, ep): """Return a text string for the dimension endpoint type. getEndpointTypeAsString(ep) This classmethod returns 'none', 'arrow', or 'filled-arrow', 'slash', or 'circle'. """ if not isinstance(ep, int): raise TypeError, "Invalid argument type: " + `type(ep)` if ep == Dimension.DIM_ENDPT_NONE: _str = 'none' elif ep == Dimension.DIM_ENDPT_ARROW: _str = 'arrow' elif ep == Dimension.DIM_ENDPT_FILLED_ARROW: _str = 'filled-arrow' elif ep == Dimension.DIM_ENDPT_SLASH: _str = 'slash' elif ep == Dimension.DIM_ENDPT_CIRCLE: _str = 'circle' else: raise ValueError, "Unexpected endpoint type value: %d" % ep return _str getEndpointTypeAsString = classmethod(getEndpointTypeAsString) def getEndpointTypeFromString(cls, s): """Return the dimension endpoint type given a string argument. getEndpointTypeFromString(ep) This classmethod returns a value based on the string argument: 'none' -> Dimension.DIM_ENDPT_NONE 'arrow' -> Dimension.DIM_ENDPT_ARROW 'filled-arrow' -> Dimension.DIM_ENDPT_FILLED_ARROW 'slash' -> Dimension.DIM_ENDPT_SLASH 'circle' -> Dimension.DIM_ENDPT_CIRCLE If the string is not listed above a ValueError execption is raised. """ if not isinstance(s, str): raise TypeError, "Invalid argument type: " + `type(s)` _ls = s.lower() if _ls == 'none': _v = Dimension.DIM_ENDPT_NONE elif _ls == 'arrow': _v = Dimension.DIM_ENDPT_ARROW elif (_ls == 'filled-arrow' or _ls == 'filled_arrow'): _v = Dimension.DIM_ENDPT_FILLED_ARROW elif _ls == 'slash': _v = Dimension.DIM_ENDPT_SLASH elif _ls == 'circle': _v = Dimension.DIM_ENDPT_CIRCLE else: raise ValueError, "Unexpected endpoint type string: " + s return _v getEndpointTypeFromString = classmethod(getEndpointTypeFromString) def getEndpointTypeStrings(cls): """Return the endpoint types as strings. getEndpointTypeStrings() This classmethod returns a list of strings. """ return [_('None'), _('Arrow'), _('Filled-Arrow'), _('Slash'), _('Circle') ] getEndpointTypeStrings = classmethod(getEndpointTypeStrings) def getEndpointTypeValues(cls): """Return the endpoint type values. getEndpointTypeValues() This classmethod returns a list of values. """ return [Dimension.DIM_ENDPT_NONE, Dimension.DIM_ENDPT_ARROW, Dimension.DIM_ENDPT_FILLED_ARROW, Dimension.DIM_ENDPT_SLASH, Dimension.DIM_ENDPT_CIRCLE ] getEndpointTypeValues = classmethod(getEndpointTypeValues) def getEndpointType(self): """Return what type of endpoints the Dimension uses. getEndpointType() """ _et = self.__eptype if _et is None: _et = self.__dimstyle.getValue('DIM_ENDPOINT') return _et def setEndpointType(self, eptype=None): """Set what type of endpoints the Dimension will use. setEndpointType([e]) The argument 'e' should be one of the following dimension.NO_ENDPOINT => no special marking at the dimension crossbar ends dimension.ARROW => an arrowhead at the dimension crossbar ends dimension.FILLED_ARROW => a filled arrohead at the dimension crossbar ends dimension.SLASH => a slash mark at the dimension crossbar ends dimension.CIRCLE => a filled circle at the dimension crossbar ends If this method is called without an argument, the endpoint type is set to that given in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Changing endpoint type allowed - object locked." _ep = eptype if _ep is not None: if (_ep != Dimension.DIM_ENDPT_NONE and _ep != Dimension.DIM_ENDPT_ARROW and _ep != Dimension.DIM_ENDPT_FILLED_ARROW and _ep != Dimension.DIM_ENDPT_SLASH and _ep != Dimension.DIM_ENDPT_CIRCLE): raise ValueError, "Invalid endpoint value: '%s'" % str(_ep) _et = self.getEndpointType() if ((_ep is None and self.__eptype is not None) or (_ep is not None and _ep != _et)): self.startChange('endpoint_type_changed') self.__eptype = _ep self.endChange('endpoint_type_changed') self.calcDimValues() self.sendMessage('endpoint_type_changed', _et) self.modified() endpoint = property(getEndpointType, setEndpointType, None, "Dimension endpoint type.") def getEndpointSize(self): """Return the size of the Dimension endpoints. getEndpointSize() """ _es = self.__epsize if _es is None: _es = self.__dimstyle.getValue('DIM_ENDPOINT_SIZE') return _es def setEndpointSize(self, size=None): """Set the size of the Dimension endpoints. setEndpointSize([size]) Optional argument 'size' should be a float greater than or equal to 0.0. Calling this method without an argument sets the endpoint size to that given in the DimStle. """ if self.isLocked(): raise RuntimeError, "Changing endpoint type allowed - object locked." _size = size if _size is not None: _size = util.get_float(_size) if _size < 0.0: raise ValueError, "Invalid endpoint size: %g" % _size _es = self.getEndpointSize() if ((_size is None and self.__epsize is not None) or (_size is not None and abs(_size - _es) > 1e-10)): self.startChange('endpoint_size_changed') self.__epsize = _size self.endChange('endpoint_size_changed') self.calcDimValues() self.sendMessage('endpoint_size_changed', _es) self.modified() def getDimstrings(self): """Return both primary and secondry dimstrings. getDimstrings() """ return self.__ds1, self.__ds2 def getPrimaryDimstring(self): """ Return the DimString used for formatting the primary dimension. getPrimaryDimstring() """ return self.__ds1 def getSecondaryDimstring(self): """Return the DimString used for formatting the secondary dimension. getSecondaryDimstring() """ return self.__ds2 def getDualDimMode(self): """Return if the Dimension is displaying primary and secondary values. getDualDimMode(self) """ _mode = self.__ddm if _mode is None: _mode = self.__dimstyle.getValue('DIM_DUAL_MODE') return _mode def setDualDimMode(self, mode=None): """Set the Dimension to display both primary and secondary values. setDualDimMode([mode]) Optional argument 'mode' should be either True or False. Invoking this method without arguments will set the dual dimension value display mode to that given from the DimStyle """ if self.isLocked(): raise RuntimeError, "Changing dual mode not allowed - object locked." _mode = mode if _mode is not None: util.test_boolean(_mode) _ddm = self.getDualDimMode() if ((_mode is None and self.__ddm is not None) or (_mode is not None and _mode is not _ddm)): self.startChange('dual_mode_changed') self.__ddm = _mode self.endChange('dual_mode_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.calcDimValues() self.sendMessage('dual_mode_changed', _ddm) self.modified() dual_mode = property(getDualDimMode, setDualDimMode, None, "Display both primary and secondary dimensions") def getOffset(self): """Return the current offset value for the Dimension. getOffset() """ _offset = self.__offset if _offset is None: _offset = self.__dimstyle.getValue('DIM_OFFSET') return _offset def setOffset(self, offset=None): """Set the offset value for the Dimension. setOffset([offset]) Optional argument 'offset' should be a positive float. Calling this method without arguments sets the value to that given in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Setting offset not allowed - object locked." _o = offset if _o is not None: _o = util.get_float(_o) if _o < 0.0: raise ValueError, "Invalid dimension offset length: %g" % _o _off = self.getOffset() if ((_o is None and self.__offset is not None) or (_o is not None and abs(_o - _off) > 1e-10)): _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.startChange('offset_changed') self.__offset = _o self.endChange('offset_changed') self.calcDimValues() self.sendMessage('offset_changed', _off) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() offset = property(getOffset, setOffset, None, "Dimension offset.") def getExtension(self): """Get the extension length of the Dimension. getExtension() """ _ext = self.__extlen if _ext is None: _ext = self.__dimstyle.getValue('DIM_EXTENSION') return _ext def setExtension(self, ext=None): """Set the extension length of the Dimension. setExtension([ext]) Optional argument 'ext' should be a positive float value. Calling this method without arguments set the extension length to that given in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Setting extension not allowed - object locked." _e = ext if _e is not None: _e = util.get_float(_e) if _e < 0.0: raise ValueError, "Invalid dimension extension length: %g" % _e _ext = self.getExtension() if ((_e is None and self.__extlen is not None) or (_e is not None and abs(_e - _ext) > 1e-10)): _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.startChange('extension_changed') self.__extlen = _e self.endChange('extension_changed') self.calcDimValues() self.sendMessage('extension_changed', _ext) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() extension = property(getExtension, setExtension, None, "Dimension extension length.") def getPositionAsString(cls, p): """Return a text string for the dimension text position. getPositionAsString(p) This classmethod returns 'split', 'above', or 'below' """ if not isinstance(p, int): raise TypeError, "Invalid argument type: " + `type(p)` if p == Dimension.DIM_TEXT_POS_SPLIT: _str = 'split' elif p == Dimension.DIM_TEXT_POS_ABOVE: _str = 'above' elif p == Dimension.DIM_TEXT_POS_BELOW: _str = 'below' else: raise ValueError, "Unexpected position value: %d" % p return _str getPositionAsString = classmethod(getPositionAsString) def getPositionFromString(cls, s): """Return the dimension text position given a string argument. getPositionFromString(s) This classmethod returns a value based on the string argument: 'split' -> Dimension.DIM_TEXT_POS_SPLIT 'above' -> Dimension.DIM_TEXT_POS_ABOVE 'below' -> Dimension.DIM_TEXT_POS_BELOW If the string is not listed above a ValueError execption is raised. """ if not isinstance(s, str): raise TypeError, "Invalid argument type: " + `type(s)` _ls = s.lower() if _ls == 'split': _v = Dimension.DIM_TEXT_POS_SPLIT elif _ls == 'above': _v = Dimension.DIM_TEXT_POS_ABOVE elif _ls == 'below': _v = Dimension.DIM_TEXT_POS_BELOW else: raise ValueError, "Unexpected position string: " + s return _v getPositionFromString = classmethod(getPositionFromString) def getPositionStrings(cls): """Return the position values as strings. getPositionStrings() This classmethod returns a list of strings. """ return [_('Split'), _('Above'), _('Below') ] getPositionStrings = classmethod(getPositionStrings) def getPositionValues(cls): """Return the position values. getPositionValues() This classmethod reutrns a list of values. """ return [Dimension.DIM_TEXT_POS_SPLIT, Dimension.DIM_TEXT_POS_ABOVE, Dimension.DIM_TEXT_POS_BELOW ] getPositionValues = classmethod(getPositionValues) def getPosition(self): """Return how the dimension text intersects the crossbar. getPosition() """ _pos = self.__textpos if _pos is None: _pos = self.__dimstyle.getValue('DIM_POSITION') return _pos def setPosition(self, pos=None): """Set where the dimension text should be placed at the crossbar. setPosition([pos]) Choices for optional argument 'pos' are: dimension.SPLIT => In the middle of the crossbar. dimension.ABOVE => Beyond the crossbar from the dimensioned objects. dimension.BELOW => Between the crossbar and the dimensioned objects. Calling this method without arguments sets the position to that given in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Setting position not allowed - object locked." _pos = pos if (_pos != Dimension.DIM_TEXT_POS_SPLIT and _pos != Dimension.DIM_TEXT_POS_ABOVE and _pos != Dimension.DIM_TEXT_POS_BELOW): raise ValueError, "Invalid dimension text position: '%s'" % str(_pos) _dp = self.getPosition() if ((_pos is None and self.__textpos is not None) or (_pos is not None and _pos != _dp)): self.startChange('position_changed') self.__textpos = _pos self.endChange('position_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.sendMessage('position_changed', _dp) self.modified() position = property(getPosition, setPosition, None, "Dimension text position") def getPositionOffset(self): """Get the offset for the dimension text and the crossbar/crossarc. getPositionOffset() """ _po = self.__poffset if _po is None: _po = self.__dimstyle.getValue('DIM_POSITION_OFFSET') return _po def setPositionOffset(self, offset=None): """Set the separation between the dimension text and the crossbar. setPositionOffset([offset]) If this method is called without arguments, the text offset length is set to the value given in the DimStyle. If the argument 'offset' is supplied, it should be a positive float value. """ if self.isLocked(): raise RuntimeError, "Setting text offset length not allowed - object locked." _o = offset if _o is not None: _o = util.get_float(_o) if _o < 0.0: raise ValueError, "Invalid text offset length: %g" % _o _to = self.getPositionOffset() if ((_o is None and self.__poffset is not None) or (_o is not None and abs(_o - _to) > 1e-10)): _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.startChange('position_offset_changed') self.__poffset = _o self.endChange('position_offset_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.calcDimValues() self.sendMessage('position_offset_changed', _to) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() position_offset = property(getPositionOffset, setPositionOffset, None, "Text offset from crossbar/crossarc distance.") def getDualModeOffset(self): """Get the offset for the dimension text when displaying two dimensions. getDualModeOffset() """ _dmo = self.__dmoffset if _dmo is None: _dmo = self.__dimstyle.getValue('DIM_DUAL_MODE_OFFSET') return _dmo def setDualModeOffset(self, offset=None): """Set the separation between the dimensions and the dual mode dimension divider. setDualModeOffset([offset]) If this method is called without arguments, the dual mode offset length is set to the value given in the DimStyle. If the argument 'offset' is supplied, it should be a positive float value. """ if self.isLocked(): raise RuntimeError, "Setting dual mode offset length not allowed - object locked." _o = offset if _o is not None: _o = util.get_float(_o) if _o < 0.0: raise ValueError, "Invalid dual mode offset length: %g" % _o _dmo = self.getDualModeOffset() if ((_o is None and self.__dmoffset is not None) or (_o is not None and abs(_o - _dmo) > 1e-10)): _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.startChange('dual_mode_offset_changed') self.__dmoffset = _o self.endChange('dual_mode_offset_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.calcDimValues() self.sendMessage('dual_mode_offset_changed', _dmo) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() dual_mode_offset = property(getDualModeOffset, setDualModeOffset, None, "Text offset from dimension splitting bar when displaying two dimensions.") def getColor(self): """Return the color of the dimension lines. getColor() """ _col = self.__color if _col is None: _col = self.__dimstyle.getValue('DIM_COLOR') return _col def setColor(self, c=None): """Set the color of the dimension lines. setColor([c]) Optional argument 'c' should be a Color instance. Calling this method without an argument sets the color to the value given in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Setting object color not allowed - object locked." _c = c if _c is not None: if not isinstance(_c, color.Color): raise TypeError, "Invalid color type: " + `type(_c)` _oc = self.getColor() if ((_c is None and self.__color is not None) or (_c is not None and _c != _oc)): self.startChange('color_changed') self.__color = _c self.endChange('color_changed') self.sendMessage('color_changed', _oc) self.modified() color = property(getColor, setColor, None, "Dimension Color") def getThickness(self): """Return the thickness of the dimension bars. getThickness() This method returns a float. """ _t = self.__thickness if _t is None: _t = self.__dimstyle.getValue('DIM_THICKNESS') return _t def setThickness(self, thickness=None): """Set the thickness of the dimension bars. setThickness([thickness]) Optional argument 'thickness' should be a float value. Setting the thickness to 0 will display and print the lines with the thinnest value possible. Calling this method without arguments resets the thickness to the value defined in the DimStyle. """ if self.isLocked(): raise RuntimeError, "Setting thickness not allowed - object locked." _t = thickness if _t is not None: _t = util.get_float(_t) if _t < 0.0: raise ValueError, "Invalid thickness: %g" % _t _ot = self.getThickness() if ((_t is None and self.__thickness is not None) or (_t is not None and abs(_t - _ot) > 1e-10)): self.startChange('thickness_changed') self.__thickness = _t self.endChange('thickness_changed') self.sendMessage('thickness_changed', _ot) self.modified() thickness = property(getThickness, setThickness, None, "Dimension bar thickness.") def getScale(self): """Return the Dimension scale factor. getScale() """ return self.__scale def setScale(self, scale=None): """Set the Dimension scale factor. setScale([scale]) Optional argument 's' should be a float value greater than 0. If no argument is supplied the default scale factor of 1 is set. """ if self.isLocked(): raise RuntimeError, "Setting scale not allowed - object locked." _s = scale if _s is None: _s = 1.0 _s = util.get_float(_s) if not _s > 0.0: raise ValueError, "Invalid scale factor: %g" % _s _os = self.__scale if abs(_os - _s) > 1e-10: self.startChange('scale_changed') self.__scale = _s self.endChange('scale_changed') self.sendMessage('scale_changed', _os) self.modified() scale = property(getScale, setScale, None, "Dimension scale factor.") def getLocation(self): """Return the location of the dimensional text values. getLocation() """ return self.__dimloc def setLocation(self, x, y): """Set the location of the dimensional text values. setLocation(x, y) The 'x' and 'y' arguments should be float values. The text is centered around that point. """ if self.isLocked(): raise RuntimeError, "Setting location not allowed - object locked." _x = util.get_float(x) _y = util.get_float(y) _ox, _oy = self.__dimloc if abs(_ox - _x) > 1e-10 or abs(_oy - _y) > 1e-10: _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.startChange('location_changed') self.__dimloc = (_x, _y) self.endChange('location_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.calcDimValues() self.sendMessage('location_changed', _ox, _oy) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() location = property(getLocation, setLocation, None, "Dimension location") def move(self, dx, dy): """Move a Dimension. move(dx, dy) The first argument gives the x-coordinate displacement, and the second gives the y-coordinate displacement. Both values should be floats. """ if self.isLocked(): raise RuntimeError, "Moving not allowed - object locked." _dx = util.get_float(dx) _dy = util.get_float(dy) if abs(_dx) > 1e-10 or abs(_dy) > 1e-10: _dxmin, _dymin, _dxmax, _dymax = self.getBounds() _x, _y = self.__dimloc self.startChange('location_changed') self.__dimloc = ((_x + _dx), (_y + _dy)) self.endChange('location_changed') self.__ds1.setBounds() self.__ds2.setBounds() self.calcDimValues() self.sendMessage('location_changed', _x, _y) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() def getStyleValue(self, ds, opt): """Get the value in the DimStyle for some option getStyleValue(ds, opt) Argument 'ds' should be one of the DimString objects in the Dimension, and argument 'opt' should be a string. Valid choices for 'opt' are 'prefix', 'suffix', 'precision', 'units', 'print_zero', 'print_decimal', 'font_family', 'font_style', 'font_weight', 'size', 'color', 'angle', and 'alignment'. """ if not isinstance(ds, DimString): raise TypeError, "Invalid DimString type: " + `type(ds)` if not isinstance(opt, str): raise TypeError, "Invalid DimStyle option type: " + `type(opt)` _key = None if ds is self.__ds1: if opt == 'prefix': _key = 'DIM_PRIMARY_PREFIX' elif opt == 'suffix': _key = 'DIM_PRIMARY_SUFFIX' elif opt == 'precision': _key = 'DIM_PRIMARY_PRECISION' elif opt == 'units': _key = 'DIM_PRIMARY_UNITS' elif opt == 'print_zero': _key = 'DIM_PRIMARY_LEADING_ZERO' elif opt == 'print_decimal': _key = 'DIM_PRIMARY_TRAILING_DECIMAL' elif opt == 'font_family': _key = 'DIM_PRIMARY_FONT_FAMILY' elif opt == 'font_weight': _key = 'DIM_PRIMARY_FONT_WEIGHT' elif opt == 'font_style': _key = 'DIM_PRIMARY_FONT_STYLE' elif opt == 'size': _key = 'DIM_PRIMARY_TEXT_SIZE' elif opt == 'color': _key = 'DIM_PRIMARY_FONT_COLOR' elif opt == 'angle': _key = 'DIM_PRIMARY_TEXT_ANGLE' elif opt == 'alignment': _key = 'DIM_PRIMARY_TEXT_ALIGNMENT' else: raise ValueError, "Unexpected option: %s" % opt elif ds is self.__ds2: if opt == 'prefix': _key = 'DIM_SECONDARY_PREFIX' elif opt == 'suffix': _key = 'DIM_SECONDARY_SUFFIX' elif opt == 'precision': _key = 'DIM_SECONDARY_PRECISION' elif opt == 'units': _key = 'DIM_SECONDARY_UNITS' elif opt == 'print_zero': _key = 'DIM_SECONDARY_LEADING_ZERO' elif opt == 'print_decimal': _key = 'DIM_SECONDARY_TRAILING_DECIMAL' elif opt == 'font_family': _key = 'DIM_SECONDARY_FONT_FAMILY' elif opt == 'font_weight': _key = 'DIM_SECONDARY_FONT_WEIGHT' elif opt == 'font_style': _key = 'DIM_SECONDARY_FONT_STYLE' elif opt == 'size': _key = 'DIM_SECONDARY_TEXT_SIZE' elif opt == 'color': _key = 'DIM_SECONDARY_FONT_COLOR' elif opt == 'angle': _key = 'DIM_SECONDARY_TEXT_ANGLE' elif opt == 'alignment': _key = 'DIM_SECONDARY_TEXT_ALIGNMENT' else: raise ValueError, "Unexpected option: %s" % opt else: raise ValueError, "DimString not used in this Dimension: " + `ds` if _key is None: raise ValueError, "Unexpected option: %s" % opt return self.__dimstyle.getValue(_key) def getDimensions(self, dimlen): """Return the formatted dimensional values. getDimensions(dimlen) The argument 'dimlen' should be the length in millimeters. This method returns a list of the primary and secondary dimensional values. """ _dl = util.get_float(dimlen) dims = [] dims.append(self.__ds1.formatDimension(_dl)) dims.append(self.__ds2.formatDimension(_dl)) return dims def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display calcDimValues([allpts]) This method is meant to be overriden by subclasses. """ pass def inRegion(self, xmin, ymin, xmax, ymax, fully=False): """Return whether or not a Dimension exists with a region. isRegion(xmin, ymin, xmax, ymax[, fully]) The first four arguments define the boundary. The optional fifth argument 'fully' indicates whether or not the Dimension must be completely contained within the region or just pass through it. This method should be overriden in classes derived from Dimension. """ return False def getBounds(self): """Return the minimal and maximal locations of the dimension getBounds() This method returns a tuple of four values - xmin, ymin, xmax, ymax. These values give the mimimum and maximum coordinates of the dimension object. This method should be overriden in classes derived from Dimension. """ _xmin = _ymin = -float(sys.maxint) _xmax = _ymax = float(sys.maxint) return _xmin, _ymin, _xmax, _ymax def copyDimValues(self, dim): """This method adjusts one Dimension to match another Dimension copyDimValues(dim) Argument 'dim' must be a Dimension instance """ if not isinstance(dim, Dimension): raise TypeError, "Invalid Dimension type: " + `type(dim)` self.setDimStyle(dim.getDimStyle()) self.setOffset(dim.getOffset()) self.setExtension(dim.getExtension()) self.setEndpointType(dim.getEndpointType()) self.setEndpointSize(dim.getEndpointSize()) self.setColor(dim.getColor()) self.setThickness(dim.getThickness()) self.setDualDimMode(dim.getDualDimMode()) self.setPositionOffset(dim.getPositionOffset()) self.setDualModeOffset(dim.getDualModeOffset()) # _ds1, _ds2 = dim.getDimstrings() # _ds = self.__ds1 _ds.setTextStyle(_ds1.getTextStyle()) _ds.setPrefix(_ds1.getPrefix()) _ds.setSuffix(_ds1.getSuffix()) _ds.setPrecision(_ds1.getPrecision()) _ds.setUnits(_ds1.getUnits()) _ds.setPrintZero(_ds1.getPrintZero()) _ds.setPrintDecimal(_ds1.getPrintDecimal()) _ds.setFamily(_ds1.getFamily()) _ds.setWeight(_ds1.getWeight()) _ds.setStyle(_ds1.getStyle()) _ds.setColor(_ds1.getColor()) _ds.setSize(_ds1.getSize()) _ds.setAngle(_ds1.getAngle()) _ds.setAlignment(_ds1.getAlignment()) # _ds = self.__ds2 _ds.setTextStyle(_ds2.getTextStyle()) _ds.setPrefix(_ds2.getPrefix()) _ds.setSuffix(_ds2.getSuffix()) _ds.setPrecision(_ds2.getPrecision()) _ds.setUnits(_ds2.getUnits()) _ds.setPrintZero(_ds2.getPrintZero()) _ds.setPrintDecimal(_ds2.getPrintDecimal()) _ds.setFamily(_ds2.getFamily()) _ds.setWeight(_ds2.getWeight()) _ds.setStyle(_ds2.getStyle()) _ds.setColor(_ds2.getColor()) _ds.setSize(_ds2.getSize()) _ds.setAngle(_ds2.getAngle()) _ds.setAlignment(_ds2.getAlignment()) def __dimstringChangePending(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _arg = args[0] if _arg == 'moved': self.startChange(_arg) elif (_arg == 'textstyle_changed' or _arg == 'font_family_changed' or _arg == 'font_style_changed' or _arg == 'font_weight_changed' or _arg == 'font_color_changed' or _arg == 'text_size_changed' or _arg == 'text_angle_changed' or _arg == 'text_alignment_changed' or _arg == 'prefix_changed' or _arg == 'suffix_changed' or _arg == 'units_changed' or _arg == 'precision_changed' or _arg == 'print_zero_changed' or _arg == 'print_decimal_changed'): self.startChange('dimstring_changed') else: pass def __dimstringChangeComplete(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _arg = args[0] if _arg == 'moved': self.endChanged(_arg) elif (_arg == 'textstyle_changed' or _arg == 'font_family_changed' or _arg == 'font_style_changed' or _arg == 'font_weight_changed' or _arg == 'font_color_changed' or _arg == 'text_size_changed' or _arg == 'text_angle_changed' or _arg == 'text_alignment_changed' or _arg == 'prefix_changed' or _arg == 'suffix_changed' or _arg == 'units_changed' or _arg == 'precision_changed' or _arg == 'print_zero_changed' or _arg == 'print_decimal_changed'): self.endChange('dimstring_changed') else: pass def sendsMessage(self, m): if m in Dimension.__messages: return True return super(Dimension, self).sendsMessage(m) # # class stuff for dimension styles # class DimStyle(object): """A class storing preferences for Dimensions The DimStyle class stores a set of dimension parameters that will be used when creating dimensions when the particular style is active. A DimStyle object has the following methods: getName(): Return the name of the DimStyle. getOption(): Return a single value in the DimStyle. getOptions(): Return all the options in the DimStyle. getValue(): Return the value of one of the DimStyle options. getOption() and getValue() are synonymous. The DimStyle class has the following classmethods: getDimStyleOptions(): Return the options defining a DimStyle. getDimStyleDefaultValue(): Return the default value for a DimStyle option. """ # # the default values for the DimStyle class # __deftextcolor = color.Color('#ffffff') __defdimcolor = color.Color(255,165,0) __defaults = { 'DIM_PRIMARY_FONT_FAMILY' : 'Sans', 'DIM_PRIMARY_TEXT_SIZE' : 1.0, 'DIM_PRIMARY_FONT_WEIGHT' : text.TextStyle.FONT_NORMAL, 'DIM_PRIMARY_FONT_STYLE' : text.TextStyle.FONT_NORMAL, 'DIM_PRIMARY_FONT_COLOR' : __deftextcolor, 'DIM_PRIMARY_TEXT_ANGLE' : 0.0, 'DIM_PRIMARY_TEXT_ALIGNMENT' : text.TextStyle.ALIGN_CENTER, 'DIM_PRIMARY_PREFIX' : u'', 'DIM_PRIMARY_SUFFIX' : u'', 'DIM_PRIMARY_PRECISION' : 3, 'DIM_PRIMARY_UNITS' : units.MILLIMETERS, 'DIM_PRIMARY_LEADING_ZERO' : True, 'DIM_PRIMARY_TRAILING_DECIMAL' : True, 'DIM_SECONDARY_FONT_FAMILY' : 'Sans', 'DIM_SECONDARY_TEXT_SIZE' : 1.0, 'DIM_SECONDARY_FONT_WEIGHT' : text.TextStyle.FONT_NORMAL, 'DIM_SECONDARY_FONT_STYLE' : text.TextStyle.FONT_NORMAL, 'DIM_SECONDARY_FONT_COLOR' : __deftextcolor, 'DIM_SECONDARY_TEXT_ANGLE' : 0.0, 'DIM_SECONDARY_TEXT_ALIGNMENT' : text.TextStyle.ALIGN_CENTER, 'DIM_SECONDARY_PREFIX' : u'', 'DIM_SECONDARY_SUFFIX' : u'', 'DIM_SECONDARY_PRECISION' : 3, 'DIM_SECONDARY_UNITS' : units.MILLIMETERS, 'DIM_SECONDARY_LEADING_ZERO' : True, 'DIM_SECONDARY_TRAILING_DECIMAL' : True, 'DIM_OFFSET' : 1.0, 'DIM_EXTENSION' : 1.0, 'DIM_COLOR' : __defdimcolor, 'DIM_THICKNESS' : 0.0, 'DIM_POSITION' : Dimension.DIM_TEXT_POS_SPLIT, 'DIM_ENDPOINT' : Dimension.DIM_ENDPT_NONE, 'DIM_ENDPOINT_SIZE' : 1.0, 'DIM_DUAL_MODE' : False, 'DIM_POSITION_OFFSET' : 0.0, 'DIM_DUAL_MODE_OFFSET' : 1.0, 'RADIAL_DIM_PRIMARY_PREFIX' : u'', 'RADIAL_DIM_PRIMARY_SUFFIX' : u'', 'RADIAL_DIM_SECONDARY_PREFIX' : u'', 'RADIAL_DIM_SECONDARY_SUFFIX' : u'', 'RADIAL_DIM_DIA_MODE' : False, 'ANGULAR_DIM_PRIMARY_PREFIX' : u'', 'ANGULAR_DIM_PRIMARY_SUFFIX' : u'', 'ANGULAR_DIM_SECONDARY_PREFIX' : u'', 'ANGULAR_DIM_SECONDARY_SUFFIX' : u'', } def __init__(self, name, keywords={}): """Instantiate a DimStyle object. ds = DimStyle(name, keywords) The argument 'name' should be a unicode name, and the 'keyword' argument should be a dict. The keys should be the same keywords used to set option values, such as DIM_OFFSET, DIM_EXTENSION, etc, and the value corresponding to each key should be set appropriately. """ super(DimStyle, self).__init__() _n = name if not isinstance(_n, types.StringTypes): raise TypeError, "Invalid DimStyle name type: "+ `type(_n)` if isinstance(_n, str): _n = unicode(_n) if not isinstance(keywords, dict): raise TypeError, "Invalid keywords argument type: " + `type(keywords)` from PythonCAD.Generic.options import test_option self.__opts = baseobject.ConstDict(str) self.__name = _n for _kw in keywords: if _kw not in DimStyle.__defaults: raise KeyError, "Unknown DimStyle keyword: " + _kw _val = keywords[_kw] _valid = test_option(_kw, _val) self.__opts[_kw] = _val def __eq__(self, obj): """Test a DimStyle object for equality with another DimStyle. """ if not isinstance(obj, DimStyle): return False if obj is self: return True if self.__name != obj.getName(): return False _val = True for _key in DimStyle.__defaults.keys(): _sv = self.getOption(_key) _ov = obj.getOption(_key) if ((_key == 'DIM_PRIMARY_TEXT_SIZE') or (_key == 'DIM_PRIMARY_TEXT_ANGLE') or (_key == 'DIM_SECONDARY_TEXT_SIZE') or (_key == 'DIM_SECONDARY_TEXT_ANGLE') or (_key == 'DIM_OFFSET') or (_key == 'DIM_EXTENSION') or (_key == 'DIM_THICKNESS') or (_key == 'DIM_ENDPOINT_SIZE') or (_key == 'DIM_POSITION_OFFSET') or (_key == 'DIM_DUAL_MODE_OFFSET')): if abs(_sv - _ov) > 1e-10: _val = False else: if _sv != _ov: _val = False if _val is False: break return _val def __ne__(self, obj): """Test a DimStyle object for inequality with another DimStyle. """ return not self == obj def getDimStyleOptions(cls): """Return the options used to define a DimStyle instance. getDimStyleOptions() This classmethod returns a list of strings. """ return cls.__defaults.keys() getDimStyleOptions = classmethod(getDimStyleOptions) def getDimStyleDefaultValue(cls, key): """Return the default value for a DimStyle option. getDimStyleValue(key) Argument 'key' must be one of the options given in getDimStyleOptions(). """ return cls.__defaults[key] getDimStyleDefaultValue = classmethod(getDimStyleDefaultValue) def getName(self): """Return the name of the DimStyle. getName() """ return self.__name name = property(getName, None, None, "DimStyle name.") def getKeys(self): """Return the non-default options within the DimStyle. getKeys() """ return self.__opts.keys() def getOptions(self): """Return all the options stored within the DimStyle. getOptions() """ _keys = self.__opts.keys() for _key in DimStyle.__defaults: if _key not in self.__opts: _keys.append(_key) return _keys def getOption(self, key): """Return the value of a particular option in the DimStyle. getOption(key) The key should be one of the strings returned from getOptions. If there is no value found in the DimStyle for the key, the value None is returned. """ if key in self.__opts: _val = self.__opts[key] elif key in DimStyle.__defaults: _val = DimStyle.__defaults[key] else: raise KeyError, "Unexpected DimStyle keyword: '%s'" % key return _val def getValue(self, key): """Return the value of a particular option in the DimStyle. getValue(key) The key should be one of the strings returned from getOptions. This method raises a KeyError exception if the key is not found. """ if key in self.__opts: _val = self.__opts[key] elif key in DimStyle.__defaults: _val = DimStyle.__defaults[key] else: raise KeyError, "Unexpected DimStyle keyword: '%s'" % key return _val def getValues(self): """Return values comprising the DimStyle. getValues() """ _vals = {} _vals['name'] = self.__name for _opt in self.__opts: _val = self.__opts[_opt] if ((_opt == 'DIM_PRIMARY_FONT_COLOR') or (_opt == 'DIM_SECONDARY_FONT_COLOR') or (_opt == 'DIM_COLOR')): _vals[_opt] = _val.getColors() else: _vals[_opt] = _val return _vals class LinearDimension(Dimension): """A class for Linear dimensions. The LinearDimension class is derived from the Dimension class, so it shares all of those methods and attributes. A LinearDimension should be used to display the absolute distance between two Point objects. A LinearDimension object has the following methods: {get/set}P1(): Get/Set the first Point for the LinearDimension. {get/set}P2(): Get/Set the second Point for the LinearDimension. getDimPoints(): Return the two Points used in this dimension. getDimLayers(): Return the two Layers holding the Points. getDimXPoints(): Get the x-coordinates of the dimension bar positions. getDimYPoints(): Get the y-coordinates of the dimension bar positions. getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers. calcMarkerPoints(): Calculate the coordinates of any dimension marker objects. """ __messages = { 'point_changed' : True, } def __init__(self, p1, p2, x, y, ds=None, **kw): """Instantiate a LinearDimension object. ldim = LinearDimension(p1, p2, x, y, ds) p1: A Point contained in a Layer p2: A Point contained in a Layer x: The x-coordinate of the dimensional text y: The y-coordinate of the dimensional text ds: The DimStyle used for this Dimension. """ if not isinstance(p1, point.Point): raise TypeError, "Invalid point type: " + `type(p1)` if p1.getParent() is None: raise ValueError, "Point P1 not stored in a Layer!" if not isinstance(p2, point.Point): raise TypeError, "Invalid point type: " + `type(p2)` if p2.getParent() is None: raise ValueError, "Point P2 not stored in a Layer!" super(LinearDimension, self).__init__(x, y, ds, **kw) self.__p1 = p1 self.__p2 = p2 self.__bar1 = DimBar() self.__bar2 = DimBar() self.__crossbar = DimCrossbar() p1.storeUser(self) p1.connect('moved', self.__movePoint) p1.connect('change_pending', self.__pointChangePending) p1.connect('change_complete', self.__pointChangeComplete) p2.storeUser(self) p2.connect('moved', self.__movePoint) p2.connect('change_pending', self.__pointChangePending) p2.connect('change_complete', self.__pointChangeComplete) self.calcDimValues() def __eq__(self, ldim): """Test two LinearDimension objects for equality. """ if not isinstance(ldim, LinearDimension): return False _lp1 = self.__p1.getParent() _lp2 = self.__p2.getParent() _p1, _p2 = ldim.getDimPoints() _l1 = _p1.getParent() _l2 = _p2.getParent() if (_lp1 is _l1 and _lp2 is _l2 and self.__p1 == _p1 and self.__p2 == _p2): return True if (_lp1 is _l2 and _lp2 is _l1 and self.__p1 == _p2 and self.__p2 == _p1): return True return False def __ne__(self, ldim): """Test two LinearDimension objects for equality. """ if not isinstance(ldim, LinearDimension): return True _lp1 = self.__p1.getParent() _lp2 = self.__p2.getParent() _p1, _p2 = ldim.getDimPoints() _l1 = _p1.getParent() _p2 = self.__p2 _l2 = _p2.getParent() if (_lp1 is _l1 and _lp2 is _l2 and self.__p1 == _p1 and self.__p2 == _p2): return False if (_lp1 is _l2 and _lp2 is _l1 and self.__p1 == _p2 and self.__p2 == _p1): return False return True def finish(self): self.__p1.disconnect(self) self.__p1.freeUser(self) self.__p2.disconnect(self) self.__p2.freeUser(self) self.__bar1 = self.__bar2 = self.__crossbar = None self.__p1 = self.__p2 = None super(LinearDimension, self).finish() def getValues(self): """Return values comprising the LinearDimension. getValues() This method extends the Dimension::getValues() method. """ _data = super(LinearDimension, self).getValues() _data.setValue('type', 'ldim') _data.setValue('p1', self.__p1.getID()) _layer = self.__p1.getParent() _data.setValue('l1', _layer.getID()) _data.setValue('p2', self.__p2.getID()) _layer = self.__p2.getParent() _data.setValue('l2', _layer.getID()) return _data def getP1(self): """Return the first Point of a LinearDimension. getP1() """ return self.__p1 def setP1(self, p): """Set the first Point of a LinearDimension. setP1(p) There is one required argument for this method: p: A Point contained in a Layer """ if self.isLocked(): raise RuntimeError, "Setting point not allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid point type: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not stored in a Layer!" _pt = self.__p1 if _pt is not p: _pt.disconnect(self) _pt.freeUser(self) self.startChange('point_changed') self.__p1 = p self.endChange('point_changed') self.sendMessage('point_changed', _pt, p) p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10: _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.calcDimValues() self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() p1 = property(getP1, None, None, "Dimension first point.") def getP2(self): """Return the second point of a LinearDimension. getP2() """ return self.__p2 def setP2(self, p): """Set the second Point of a LinearDimension. setP2(p) There is one required argument for this method: p: A Point contained in a Layer """ if self.isLocked(): raise RuntimeError, "Setting point not allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid point type: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not stored in a Layer!" _pt = self.__p2 if _pt is not p: _pt.disconnect(self) _pt.freeUser(self) self.startChange('point_changed') self.__p2 = p self.endChange('point_changed') self.sendMessage('point_changed', _pt, p) p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10: _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.calcDimValues() self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) self.modified() p2 = property(getP2, None, None, "Dimension second point.") def getDimPoints(self): """Return both points used in the LinearDimension. getDimPoints() The two points are returned in a tuple. """ return self.__p1, self.__p2 def getDimBars(self): """Return the dimension boundary bars. getDimBars() """ return self.__bar1, self.__bar2 def getDimCrossbar(self): """Return the dimension crossbar. getDimCrossbar() """ return self.__crossbar def getDimLayers(self): """Return both layers used in the LinearDimension. getDimLayers() The two layers are returned in a tuple. """ _l1 = self.__p1.getParent() _l2 = self.__p2.getParent() return _l1, _l2 def calculate(self): """Determine the length of this LinearDimension. calculate() """ return self.__p1 - self.__p2 def inRegion(self, xmin, ymin, xmax, ymax, fully=False): """Return whether or not a LinearDimension exists within a region. isRegion(xmin, ymin, xmax, ymax[, fully]) The four arguments define the boundary of an area, and the function returns True if the LinearDimension lies within that area. If the optional argument fully is used and is True, then the dimension points and the location of the dimension text must lie within the boundary. Otherwise, the function returns False. """ _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Illegal values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Illegal values: ymax < ymin" util.test_boolean(fully) _dxmin, _dymin, _dxmax, _dymax = self.getBounds() if ((_dxmin > _xmax) or (_dymin > _ymax) or (_dxmax < _xmin) or (_dymax < _ymin)): return False if fully: if ((_dxmin > _xmin) and (_dymin > _ymin) and (_dxmax < _xmax) and (_dymax < _ymax)): return True return False _dx, _dy = self.getLocation() if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text return True # # bar at p1 # _ep1, _ep2 = self.__bar1.getEndpoints() _x1, _y1 = _ep1 _x2, _y2 = _ep2 if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax): return True # # bar at p2 # _ep1, _ep2 = self.__bar2.getEndpoints() _x1, _y1 = _ep1 _x2, _y2 = _ep2 if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax): return True # # crossbar # _ep1, _ep2 = self.__crossbar.getEndpoints() _x1, _y1 = _ep1 _x2, _y2 = _ep2 return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax) def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display. calcDimValues([allpts]) This method calculates where the points for the dimension bars and crossbar are located. The argument 'allpts' is optional. By default it is True. If the argument is set to False, then the coordinates of the dimension marker points will not be calculated. """ _allpts = allpts util.test_boolean(_allpts) _p1, _p2 = self.getDimPoints() _bar1 = self.__bar1 _bar2 = self.__bar2 _crossbar = self.__crossbar _p1x, _p1y = _p1.getCoords() _p2x, _p2y = _p2.getCoords() _dx, _dy = self.getLocation() _offset = self.getOffset() _ext = self.getExtension() # # see comp.graphics.algorithms.faq section on calcuating # the distance between a point and line for info about # the following equations ... # _dpx = _p2x - _p1x _dpy = _p2y - _p1y _rnum = ((_dx - _p1x) * _dpx) + ((_dy - _p1y) * _dpy) _snum = ((_p1y - _dy) * _dpx) - ((_p1x - _dx) * _dpy) _den = pow(_dpx, 2) + pow(_dpy, 2) _r = _rnum/_den _s = _snum/_den _sep = abs(_s) * math.sqrt(_den) if abs(_dpx) < 1e-10: # vertical if _p2y > _p1y: _slope = math.pi/2.0 else: _slope = -math.pi/2.0 elif abs(_dpy) < 1e-10: # horizontal if _p2x > _p1x: _slope = 0.0 else: _slope = -math.pi else: _slope = math.atan2(_dpy, _dpx) if _s < 0.0: # dim point left of p1-p2 line _angle = _slope + (math.pi/2.0) else: # dim point right of p1-p2 line (or on it) _angle = _slope - (math.pi/2.0) _sin_angle = math.sin(_angle) _cos_angle = math.cos(_angle) _x = _p1x + (_offset * _cos_angle) _y = _p1y + (_offset * _sin_angle) _bar1.setFirstEndpoint(_x, _y) if _r < 0.0: _px = _p1x + (_r * _dpx) _py = _p1y + (_r * _dpy) _x = _px + (_sep * _cos_angle) _y = _py + (_sep * _sin_angle) else: _x = _p1x + (_sep * _cos_angle) _y = _p1y + (_sep * _sin_angle) _crossbar.setFirstEndpoint(_x, _y) _x = _p1x + (_sep * _cos_angle) _y = _p1y + (_sep * _sin_angle) _crossbar.setFirstCrossbarPoint(_x, _y) _x = _p1x + ((_sep + _ext) * _cos_angle) _y = _p1y + ((_sep + _ext) * _sin_angle) _bar1.setSecondEndpoint(_x, _y) _x = _p2x + (_offset * _cos_angle) _y = _p2y + (_offset * _sin_angle) _bar2.setFirstEndpoint(_x, _y) if _r > 1.0: _px = _p1x + (_r * _dpx) _py = _p1y + (_r * _dpy) _x = _px + (_sep * _cos_angle) _y = _py + (_sep * _sin_angle) else: _x = _p2x + (_sep * _cos_angle) _y = _p2y + (_sep * _sin_angle) _crossbar.setSecondEndpoint(_x, _y) _x = _p2x + (_sep * _cos_angle) _y = _p2y + (_sep * _sin_angle) _crossbar.setSecondCrossbarPoint(_x, _y) _x = _p2x + ((_sep + _ext) * _cos_angle) _y = _p2y + ((_sep + _ext) * _sin_angle) _bar2.setSecondEndpoint(_x, _y) if _allpts: self.calcMarkerPoints() def calcMarkerPoints(self): """Calculate and store the dimension endpoint markers coordinates. calcMarkerPoints() """ _type = self.getEndpointType() _crossbar = self.__crossbar _crossbar.clearMarkerPoints() if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE: return _size = self.getEndpointSize() _p1, _p2 = _crossbar.getCrossbarPoints() _x1, _y1 = _p1 _x2, _y2 = _p2 # print "x1: %g" % _x1 # print "y1: %g" % _y1 # print "x2: %g" % _x2 # print "y2: %g" % _y2 _sine, _cosine = _crossbar.getSinCosValues() if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW: _height = _size/5.0 # p1 -> (x,y) = (size, _height) _mx = (_cosine * _size - _sine * _height) + _x1 _my = (_sine * _size + _cosine * _height) + _y1 _crossbar.storeMarkerPoint(_mx, _my) # p2 -> (x,y) = (size, -_height) _mx = (_cosine * _size - _sine *(-_height)) + _x1 _my = (_sine * _size + _cosine *(-_height)) + _y1 _crossbar.storeMarkerPoint(_mx, _my) # p3 -> (x,y) = (-size, _height) _mx = (_cosine * (-_size) - _sine * _height) + _x2 _my = (_sine * (-_size) + _cosine * _height) + _y2 _crossbar.storeMarkerPoint(_mx, _my) # p4 -> (x,y) = (-size, -_height) _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2 _my = (_sine * (-_size) + _cosine *(-_height)) + _y2 _crossbar.storeMarkerPoint(_mx, _my) elif _type == Dimension.DIM_ENDPT_SLASH: _angle = 30.0 * _dtr # slope of slash _height = 0.5 * _size * math.sin(_angle) _length = 0.5 * _size * math.cos(_angle) # p1 -> (x,y) = (-_length, -_height) _sx1 = (_cosine * (-_length) - _sine * (-_height)) _sy1 = (_sine * (-_length) + _cosine * (-_height)) # p2 -> (x,y) = (_length, _height) _sx2 = (_cosine * _length - _sine * _height) _sy2 = (_sine * _length + _cosine * _height) # # shift the calculate based on the location of the # marker point # _mx = _sx1 + _x2 _my = _sy1 + _y2 _crossbar.storeMarkerPoint(_mx, _my) _mx = _sx2 + _x2 _my = _sy2 + _y2 _crossbar.storeMarkerPoint(_mx, _my) _mx = _sx1 + _x1 _my = _sy1 + _y1 _crossbar.storeMarkerPoint(_mx, _my) _mx = _sx2 + _x1 _my = _sy2 + _y1 _crossbar.storeMarkerPoint(_mx, _my) else: raise ValueError, "Unexpected endpoint type: '%s'" % str(_type) def mapCoords(self, x, y, tol=tolerance.TOL): """Test an x/y coordinate pair if it could lay on the dimension. mapCoords(x, y[, tol]) This method has two required parameters: x: The x-coordinate y: The y-coordinate These should both be float values. There is an optional third parameter tol giving the maximum distance from the dimension bars that the x/y coordinates may lie. """ _x = util.get_float(x) _y = util.get_float(y) _t = tolerance.toltest(tol) _ep1, _ep2 = self.__bar1.getEndpoints() # # test p1 bar # _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t) if _mp is not None: return _mp # # test p2 bar # _ep1, _ep2 = self.__bar2.getEndpoints() _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t) if _mp is not None: return _mp # # test crossbar # _ep1, _ep2 = self.__crossbar.getEndpoints() return util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t) def onDimension(self, x, y, tol=tolerance.TOL): return self.mapCoords(x, y, tol) is not None def getBounds(self): """Return the minimal and maximal locations of the dimension getBounds() This method overrides the Dimension::getBounds() method """ _dx, _dy = self.getLocation() _dxpts = [] _dypts = [] _ep1, _ep2 = self.__bar1.getEndpoints() _dxpts.append(_ep1[0]) _dypts.append(_ep1[1]) _dxpts.append(_ep2[0]) _dypts.append(_ep2[1]) _ep1, _ep2 = self.__bar2.getEndpoints() _dxpts.append(_ep1[0]) _dypts.append(_ep1[1]) _dxpts.append(_ep2[0]) _dypts.append(_ep2[1]) _ep1, _ep2 = self.__crossbar.getEndpoints() _dxpts.append(_ep1[0]) _dypts.append(_ep1[1]) _dxpts.append(_ep2[0]) _dypts.append(_ep2[1]) _xmin = min(_dx, min(_dxpts)) _ymin = min(_dy, min(_dypts)) _xmax = max(_dx, max(_dxpts)) _ymax = max(_dy, max(_dypts)) return _xmin, _ymin, _xmax, _ymax def clone(self): _p1 = self.__p1 _p2 = self.__p2 _x, _y = self.getLocation() _ds = self.getDimStyle() _ldim = LinearDimension(_p1, _p2, _x, _y, _ds) _ldim.copyDimValues(self) return _ldim def __pointChangePending(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved': self.startChange('moved') def __pointChangeComplete(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved': self.endChange('moved') def __movePoint(self, p, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen if p is not self.__p1 and p is not self.__p2: raise ValueError, "Unexpected dimension point: " + `p` _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.calcDimValues(True) self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) def sendsMessage(self, m): if m in LinearDimension.__messages: return True return super(LinearDimension, self).sendsMessage(m) class HorizontalDimension(LinearDimension): """A class representing Horizontal dimensions. This class is derived from the LinearDimension class, so it shares all those attributes and methods of its parent. """ def __init__(self, p1, p2, x, y, ds=None, **kw): """Initialize a Horizontal Dimension. hdim = HorizontalDimension(p1, p2, x, y, ds) p1: A Point contained in a Layer p2: A Point contained in a Layer x: The x-coordinate of the dimensional text y: The y-coordinate of the dimensional text ds: The DimStyle used for this Dimension. """ super(HorizontalDimension, self).__init__(p1, p2, x, y, ds, **kw) def getValues(self): """Return values comprising the HorizontalDimension. getValues() This method extends the LinearDimension::getValues() method. """ _data = super(HorizontalDimension, self).getValues() _data.setValue('type', 'hdim') return _data def calculate(self): """Determine the length of this HorizontalDimension. calculate() """ _p1, _p2 = self.getDimPoints() return abs(_p1.x - _p2.x) def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display. calcDimValues([allpts]) This method overrides the LinearDimension::calcDimValues() method. """ _allpts = allpts util.test_boolean(_allpts) _p1, _p2 = self.getDimPoints() _bar1, _bar2 = self.getDimBars() _crossbar = self.getDimCrossbar() _p1x, _p1y = _p1.getCoords() _p2x, _p2y = _p2.getCoords() _dx, _dy = self.getLocation() _offset = self.getOffset() _ext = self.getExtension() _crossbar.setFirstEndpoint(_p1x, _dy) _crossbar.setSecondEndpoint(_p2x, _dy) if _dx < min(_p1x, _p2x) or _dx > max(_p1x, _p2x): if _p1x < _p2x: if _dx < _p1x: _crossbar.setFirstEndpoint(_dx, _dy) if _dx > _p2x: _crossbar.setSecondEndpoint(_dx, _dy) else: if _dx < _p2x: _crossbar.setSecondEndpoint(_dx, _dy) if _dx > _p1x: _crossbar.setFirstEndpoint(_dx, _dy) _crossbar.setFirstCrossbarPoint(_p1x, _dy) _crossbar.setSecondCrossbarPoint(_p2x, _dy) if _dy < min(_p1y, _p2y): _bar1.setFirstEndpoint(_p1x, (_p1y - _offset)) _bar1.setSecondEndpoint(_p1x, (_dy - _ext)) _bar2.setFirstEndpoint(_p2x, (_p2y - _offset)) _bar2.setSecondEndpoint(_p2x, (_dy - _ext)) elif _dy > max(_p1y, _p2y): _bar1.setFirstEndpoint(_p1x, (_p1y + _offset)) _bar1.setSecondEndpoint(_p1x, (_dy + _ext)) _bar2.setFirstEndpoint(_p2x, (_p2y + _offset)) _bar2.setSecondEndpoint(_p2x, (_dy + _ext)) else: if _dy > _p1y: _bar1.setFirstEndpoint(_p1x, (_p1y + _offset)) _bar1.setSecondEndpoint(_p1x, (_dy + _ext)) else: _bar1.setFirstEndpoint(_p1x, (_p1y - _offset)) _bar1.setSecondEndpoint(_p1x, (_dy - _ext)) if _dy > _p2y: _bar2.setFirstEndpoint(_p2x, (_p2y + _offset)) _bar2.setSecondEndpoint(_p2x, (_dy + _ext)) else: _bar2.setFirstEndpoint(_p2x, (_p2y - _offset)) _bar2.setSecondEndpoint(_p2x, (_dy - _ext)) if _allpts: self.calcMarkerPoints() def clone(self): _p1, _p2 = self.getDimPoints() _x, _y = self.getLocation() _ds = self.getDimStyle() _hdim = HorizontalDimension(_p1, _p2, _x, _y, _ds) _hdim.copyDimValues(self) return _hdim class VerticalDimension(LinearDimension): """A class representing Vertical dimensions. This class is derived from the LinearDimension class, so it shares all those attributes and methods of its parent. """ def __init__(self, p1, p2, x, y, ds=None, **kw): """Initialize a Vertical Dimension. vdim = VerticalDimension(p1, p2, x, y, ds) p1: A Point contained in a Layer p2: A Point contained in a Layer x: The x-coordinate of the dimensional text y: The y-coordinate of the dimensional text ds: The DimStyle used for this Dimension. """ super(VerticalDimension, self).__init__(p1, p2, x, y, ds, **kw) def getValues(self): """Return values comprising the VerticalDimension. getValues() This method extends the LinearDimension::getValues() method. """ _data = super(VerticalDimension, self).getValues() _data.setValue('type', 'vdim') return _data def calculate(self): """Determine the length of this VerticalDimension. calculate() """ _p1, _p2 = self.getDimPoints() return abs(_p1.y - _p2.y) def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display. calcDimValues([allpts]) This method overrides the LinearDimension::calcDimValues() method. """ _allpts = allpts util.test_boolean(_allpts) _p1, _p2 = self.getDimPoints() _bar1, _bar2 = self.getDimBars() _crossbar = self.getDimCrossbar() _p1x, _p1y = _p1.getCoords() _p2x, _p2y = _p2.getCoords() _dx, _dy = self.getLocation() _offset = self.getOffset() _ext = self.getExtension() _crossbar.setFirstEndpoint(_dx, _p1y) _crossbar.setSecondEndpoint(_dx, _p2y) if _dy < min(_p1y, _p2y) or _dy > max(_p1y, _p2y): if _p1y < _p2y: if _dy < _p1y: _crossbar.setFirstEndpoint(_dx, _dy) if _dy > _p2y: _crossbar.setSecondEndpoint(_dx, _dy) if _p2y < _p1y: if _dy < _p2y: _crossbar.setSecondEndpoint(_dx, _dy) if _dy > _p1y: _crossbar.setFirstEndpoint(_dx, _dy) _crossbar.setFirstCrossbarPoint(_dx, _p1y) _crossbar.setSecondCrossbarPoint(_dx, _p2y) if _dx < min(_p1x, _p2x): _bar1.setFirstEndpoint((_p1x - _offset), _p1y) _bar1.setSecondEndpoint((_dx - _ext), _p1y) _bar2.setFirstEndpoint((_p2x - _offset), _p2y) _bar2.setSecondEndpoint((_dx - _ext), _p2y) elif _dx > max(_p1x, _p2x): _bar1.setFirstEndpoint((_p1x + _offset), _p1y) _bar1.setSecondEndpoint((_dx + _ext), _p1y) _bar2.setFirstEndpoint((_p2x + _offset), _p2y) _bar2.setSecondEndpoint((_dx + _ext), _p2y) else: if _dx > _p1x: _bar1.setFirstEndpoint((_p1x + _offset), _p1y) _bar1.setSecondEndpoint((_dx + _ext), _p1y) else: _bar1.setFirstEndpoint((_p1x - _offset), _p1y) _bar1.setSecondEndpoint((_dx - _ext), _p1y) if _dx > _p2x: _bar2.setFirstEndpoint((_p2x + _offset), _p2y) _bar2.setSecondEndpoint((_dx + _ext), _p2y) else: _bar2.setFirstEndpoint((_p2x - _offset), _p2y) _bar2.setSecondEndpoint((_dx - _ext), _p2y) if _allpts: self.calcMarkerPoints() def clone(self): _p1, _p2 = self.getDimPoints() _x, _y = self.getLocation() _ds = self.getDimStyle() _vdim = VerticalDimension(_p1, _p2, _x, _y, _ds) _vdim.copyDimValues(self) return _vdim class RadialDimension(Dimension): """A class for Radial dimensions. The RadialDimension class is derived from the Dimension class, so it shares all of those methods and attributes. A RadialDimension should be used to display either the radius or diamter of a Circle object. A RadialDimension object has the following methods: {get/set}DimCircle(): Get/Set the measured circle object. getDimLayer(): Return the layer containing the measured circle. {get/set}DiaMode(): Get/Set if the RadialDimension should return diameters. getDimXPoints(): Get the x-coordinates of the dimension bar positions. getDimYPoints(): Get the y-coordinates of the dimension bar positions. getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers. getDimCrossbar(): Get the DimCrossbar object of the RadialDimension. calcDimValues(): Calculate the endpoint of the dimension line. mapCoords(): Return coordinates on the dimension near some point. onDimension(): Test if an x/y coordinate pair fall on the dimension line. """ __messages = { 'dimobj_changed' : True, 'dia_mode_changed' : True, } def __init__(self, cir, x, y, ds=None, **kw): """Initialize a RadialDimension object. rdim = RadialDimension(cir, x, y, ds) cir: A Circle or Arc object x: The x-coordinate of the dimensional text y: The y-coordinate of the dimensional text ds: The DimStyle used for this Dimension. """ super(RadialDimension, self).__init__(x, y, ds, **kw) if not isinstance(cir, (circle.Circle, arc.Arc)): raise TypeError, "Invalid circle/arc type: " + `type(cir)` if cir.getParent() is None: raise ValueError, "Circle/Arc not found in Layer!" self.__circle = cir self.__crossbar = DimCrossbar() self.__dia_mode = False cir.storeUser(self) _ds = self.getDimStyle() _pds, _sds = self.getDimstrings() _pds.mute() try: _pds.setPrefix(_ds.getValue('RADIAL_DIM_PRIMARY_PREFIX')) _pds.setSuffix(_ds.getValue('RADIAL_DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds.mute() try: _sds.setPrefix(_ds.getValue('RADIAL_DIM_SECONDARY_PREFIX')) _sds.setSuffix(_ds.getValue('RADIAL_DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() self.setDiaMode(_ds.getValue('RADIAL_DIM_DIA_MODE')) cir.connect('moved', self.__moveCircle) cir.connect('radius_changed', self.__radiusChanged) cir.connect('change_pending', self.__circleChangePending) cir.connect('change_complete', self.__circleChangeComplete) self.calcDimValues() def __eq__(self, rdim): """Compare two RadialDimensions for equality. """ if not isinstance(rdim, RadialDimension): return False _val = False _layer = self.__circle.getParent() _rc = rdim.getDimCircle() _rl = _rc.getParent() if _layer is _rl and self.__circle == _rc: _val = True return _val def __ne__(self, rdim): """Compare two RadialDimensions for inequality. """ if not isinstance(rdim, RadialDimension): return True _val = True _layer = self.__circle.getParent() _rc = rdim.getDimCircle() _rl = _rc.getParent() if _layer is _rl and self.__circle == _rc: _val = False return _val def finish(self): self.__circle.disconnect(self) self.__circle.freeUser(self) self.__circle = self.__crossbar = None super(RadialDimension, self).finish() def getValues(self): """Return values comprising the RadialDimension. getValues() This method extends the Dimension::getValues() method. """ _data = super(RadialDimension, self).getValues() _data.setValue('type', 'rdim') _data.setValue('circle', self.__circle.getID()) _layer = self.__circle.getParent() _data.setValue('layer', _layer.getID()) _data.setValue('dia_mode', self.__dia_mode) return _data def getDiaMode(self): """Return if the RadialDimension will return diametrical values. getDiaMode() This method returns True if the diameter value is returned, and False otherwise. """ return self.__dia_mode def setDiaMode(self, mode=False): """Set the RadialDimension to return diametrical values. setDiaMode([mode]) Calling this method without an argument sets the RadialDimension to return radial measurements. If the argument "mode" is supplied, it should be either True or False. If the RadialDimension is measuring an arc, the returned value will always be set to return a radius. """ util.test_boolean(mode) if not isinstance(self.__circle, arc.Arc): _m = self.__dia_mode if _m is not mode: self.startChange('dia_mode_changed') self.__dia_mode = mode self.endChange('dia_mode_changed') self.sendMessage('dia_mode_changed', _m) self.calcDimValues() self.modified() dia_mode = property(getDiaMode, setDiaMode, None, "Draw the Dimension as a diameter") def getDimLayer(self): """Return the Layer object holding the Circle for this RadialDimension. getDimLayer() """ return self.__circle.getParent() def getDimCircle(self): """Return the Circle object this RadialDimension is measuring. getDimCircle() """ return self.__circle def setDimCircle(self, c): """Set the Circle object measured by this RadialDimension. setDimCircle(c) The argument for this method is: c: A Circle/Arc contained in a Layer """ if self.isLocked(): raise RuntimeError, "Setting circle/arc not allowed - object locked." if not isinstance(c, (circle.Circle, arc.Arc)): raise TypeError, "Invalid circle/arc type: " + `type(c)` if c.getParent() is None: raise ValueError, "Circle/Arc not found in a Layer!" _circ = self.__circle if _circ is not c: _circ.disconnect(self) _circ.freeUser(self) self.startChange('dimobj_changed') self.__circle = c self.endChange('dimobj_changed') c.storeUser(self) self.sendMessage('dimobj_changed', _circ, c) c.connect('moved', self.__moveCircle) c.connect('radius_changed', self.__radiusChanged) c.connect('change_pending', self.__circleChangePending) c.connect('change_complete', self.__circleChangeComplete) self.calcDimValues() self.modified() circle = property(getDimCircle, None, None, "Radial dimension circle object.") def getDimCrossbar(self): """Get the DimCrossbar object used by the RadialDimension. getDimCrossbar() """ return self.__crossbar def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display. calcDimValues([allpts]) The optional argument 'allpts' is by default True. Calling this method with the argument set to False will skip the calculation of any dimension endpoint marker points. """ _allpts = allpts util.test_boolean(_allpts) _c = self.__circle _dimbar = self.__crossbar _cx, _cy = _c.getCenter().getCoords() _rad = _c.getRadius() _dx, _dy = self.getLocation() _dia_mode = self.__dia_mode _sep = math.hypot((_dx - _cx), (_dy - _cy)) _angle = math.atan2((_dy - _cy), (_dx - _cx)) _sx = _rad * math.cos(_angle) _sy = _rad * math.sin(_angle) if isinstance(_c, arc.Arc): assert _dia_mode is False, "dia_mode for arc radial dimension" _sa = _c.getStartAngle() _ea = _c.getEndAngle() _angle = _rtd * _angle if _angle < 0.0: _angle = _angle + 360.0 if not _c.throughAngle(_angle): _ep1, _ep2 = _c.getEndpoints() if _angle < _sa: _sa = _dtr * _sa _sx = _rad * math.cos(_sa) _sy = _rad * math.sin(_sa) if _sep > _rad: _dx = _cx + (_sep * math.cos(_sa)) _dy = _cy + (_sep * math.sin(_sa)) if _angle > _ea: _ea = _dtr * _ea _sx = _rad * math.cos(_ea) _sy = _rad * math.sin(_ea) if _sep > _rad: _dx = _cx + (_sep * math.cos(_ea)) _dy = _cy + (_sep * math.sin(_ea)) if _dia_mode: _dimbar.setFirstEndpoint((_cx - _sx), (_cy - _sy)) _dimbar.setFirstCrossbarPoint((_cx - _sx), (_cy - _sy)) else: _dimbar.setFirstEndpoint(_cx, _cy) _dimbar.setFirstCrossbarPoint(_cx, _cy) if _sep > _rad: _dimbar.setSecondEndpoint(_dx, _dy) else: _dimbar.setSecondEndpoint((_cx + _sx), (_cy + _sy)) _dimbar.setSecondCrossbarPoint((_cx + _sx), (_cy + _sy)) if not _allpts: return # # calculate dimension endpoint marker coordinates # _type = self.getEndpointType() _dimbar.clearMarkerPoints() if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE: return _size = self.getEndpointSize() _x1, _y1 = _dimbar.getFirstCrossbarPoint() _x2, _y2 = _dimbar.getSecondCrossbarPoint() _sine, _cosine = _dimbar.getSinCosValues() if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW: _height = _size/5.0 # p1 -> (x,y) = (size, _height) _mx = (_cosine * _size - _sine * _height) + _x1 _my = (_sine * _size + _cosine * _height) + _y1 _dimbar.storeMarkerPoint(_mx, _my) # p2 -> (x,y) = (size, -_height) _mx = (_cosine * _size - _sine *(-_height)) + _x1 _my = (_sine * _size + _cosine *(-_height)) + _y1 _dimbar.storeMarkerPoint(_mx, _my) # p3 -> (x,y) = (-size, _height) _mx = (_cosine * (-_size) - _sine * _height) + _x2 _my = (_sine * (-_size) + _cosine * _height) + _y2 _dimbar.storeMarkerPoint(_mx, _my) # p4 -> (x,y) = (-size, -_height) _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2 _my = (_sine * (-_size) + _cosine *(-_height)) + _y2 _dimbar.storeMarkerPoint(_mx, _my) elif _type == Dimension.DIM_ENDPT_SLASH: _angle = 30.0 * _dtr # slope of slash _height = 0.5 * _size * math.sin(_angle) _length = 0.5 * _size * math.cos(_angle) # p1 -> (x,y) = (-_length, -_height) _sx1 = (_cosine * (-_length) - _sine * (-_height)) _sy1 = (_sine * (-_length) + _cosine * (-_height)) # p2 -> (x,y) = (_length, _height) _sx2 = (_cosine * _length - _sine * _height) _sy2 = (_sine * _length + _cosine * _height) # # shift the calculate based on the location of the # marker point # _mx = _sx1 + _x1 _my = _sy1 + _y1 _dimbar.storeMarkerPoint(_mx, _my) _mx = _sx2 + _x1 _my = _sy2 + _y1 _dimbar.storeMarkerPoint(_mx, _my) _mx = _sx1 + _x2 _my = _sy1 + _y2 _dimbar.storeMarkerPoint(_mx, _my) _mx = _sx2 + _x2 _my = _sy2 + _y2 _dimbar.storeMarkerPoint(_mx, _my) else: raise ValueError, "Unexpected endpoint type: '%s'" % str(_type) def calculate(self): """Return the radius or diamter of this RadialDimension. calculate() By default, a RadialDimension will return the radius of the circle. The setDiaMode() method can be called to set the returned value to corresponed to a diameter. """ _val = self.__circle.getRadius() if self.__dia_mode is True: _val = _val * 2.0 return _val def inRegion(self, xmin, ymin, xmax, ymax, fully=False): """Return whether or not a RadialDimension exists within a region. isRegion(xmin, ymin, xmax, ymax[, fully]) The four arguments define the boundary of an area, and the function returns True if the RadialDimension lies within that area. If the optional argument 'fully' is used and is True, then the dimensioned circle and the location of the dimension text must lie within the boundary. Otherwise, the function returns False. """ _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Illegal values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Illegal values: ymax < ymin" util.test_boolean(fully) _dxmin, _dymin, _dxmax, _dymax = self.getBounds() if ((_dxmin > _xmax) or (_dymin > _ymax) or (_dxmax < _xmin) or (_dymax < _ymin)): return False if fully: if ((_dxmin > _xmin) and (_dymin > _ymin) and (_dxmax < _xmax) and (_dymax < _ymax)): return True return False _dx, _dy = self.getLocation() if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text return True _p1, _p2 = self.__crossbar.getEndpoints() _x1, _y1 = _p1 _x2, _y2 = _p2 return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax) def mapCoords(self, x, y, tol=tolerance.TOL): """Test an x/y coordinate pair if it could lay on the dimension. mapCoords(x, y[, tol]) This method has two required parameters: x: The x-coordinate y: The y-coordinate These should both be float values. There is an optional third parameter, 'tol', giving the maximum distance from the dimension bars that the x/y coordinates may sit. """ _x = util.get_float(x) _y = util.get_float(y) _t = tolerance.toltest(tol) _p1, _p2 = self.__crossbar.getEndpoints() _x1, _y1 = _p1 _x2, _y2 = _p2 return util.map_coords(_x, _y, _x1, _y1, _x2, _y2, _t) def onDimension(self, x, y, tol=tolerance.TOL): return self.mapCoords(x, y, tol) is not None def getBounds(self): """Return the minimal and maximal locations of the dimension getBounds() This method overrides the Dimension::getBounds() method """ _p1, _p2 = self.__crossbar.getEndpoints() _x1, _y1 = _p1 _x2, _y2 = _p2 _xmin = min(_x1, _x2) _ymin = min(_y1, _y2) _xmax = max(_x1, _x2) _ymax = max(_y1, _y2) return _xmin, _ymin, _xmax, _ymax def clone(self): _c = self.__circle _x, _y = self.getLocation() _ds = self.getDimStyle() _rdim = RadialDimension(_c, _x, _y, _ds) _rdim.copyDimValues(self) _rdim.setDiaMode(self.getDiaMode()) return _rdim def __circleChangePending(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved' or args[0] =='radius_changed': self.startChange('moved') def __circleChangeComplete(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved' or args[0] =='radius_changed': self.endChange('moved') def __moveCircle(self, circ, *args): _alen = len(args) if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen if circ is not self.__circle: raise ValueError, "Unexpected sender: " + `circ` _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.calcDimValues() self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) def __radiusChanged(self, circ, *args): self.calcDimValues() def sendsMessage(self, m): if m in RadialDimension.__messages: return True return super(RadialDimension, self).sendsMessage(m) class AngularDimension(Dimension): """A class for Angular dimensions. The AngularDimension class is derived from the Dimension class, so it shares all of those methods and attributes. AngularDimension objects have the following methods: {get/set}VertexPoint(): Get/Set the vertex point for the AngularDimension. {get/set}P1(): Get/Set the first Point for the AngularDimension. {get/set}P2(): Get/Set the second Point for the AngularDimension. getDimPoints(): Return the two Points used in this dimension. getDimLayers(): Return the two Layers holding the Points. getDimXPoints(): Get the x-coordinates of the dimension bar positions. getDimYPoints(): Get the y-coordinates of the dimension bar positions. getDimAngles(): Get the angles at which the dimension bars should be drawn. getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers. calcDimValues(): Calculate the endpoint of the dimension line. mapCoords(): Return coordinates on the dimension near some point. onDimension(): Test if an x/y coordinate pair fall on the dimension line. invert(): Switch the endpoints used to measure the dimension """ __messages = { 'point_changed' : True, 'inverted' : True, } def __init__(self, vp, p1, p2, x, y, ds=None, **kw): """Initialize an AngularDimension object. adim = AngularDimension(vp, p1, p2, x, y, ds) vp: A Point contained in a Layer p1: A Point contained in a Layer p2: A Point contained in a Layer x: The x-coordinate of the dimensional text y: The y-coordinate of the dimensional text ds: The DimStyle used for this Dimension. """ super(AngularDimension, self).__init__(x, y, ds, **kw) if not isinstance(vp, point.Point): raise TypeError, "Invalid point type: " + `type(vp)` if vp.getParent() is None: raise ValueError, "Vertex Point not found in a Layer!" if not isinstance(p1, point.Point): raise TypeError, "Invalid point type: " + `type(p1)` if p1.getParent() is None: raise ValueError, "Point P1 not found in a Layer!" if not isinstance(p2, point.Point): raise TypeError, "Invalid point type: " + `type(p2)` if p2.getParent() is None: raise ValueError, "Point P2 not found in a Layer!" self.__vp = vp self.__p1 = p1 self.__p2 = p2 self.__bar1 = DimBar() self.__bar2 = DimBar() self.__crossarc = DimCrossarc() _ds = self.getDimStyle() _pds, _sds = self.getDimstrings() _pds.mute() try: _pds.setPrefix(_ds.getValue('ANGULAR_DIM_PRIMARY_PREFIX')) _pds.setSuffix(_ds.getValue('ANGULAR_DIM_PRIMARY_SUFFIX')) finally: _pds.unmute() _sds.mute() try: _sds.setPrefix(_ds.getValue('ANGULAR_DIM_SECONDARY_PREFIX')) _sds.setSuffix(_ds.getValue('ANGULAR_DIM_SECONDARY_SUFFIX')) finally: _sds.unmute() vp.storeUser(self) vp.connect('moved', self.__movePoint) vp.connect('change_pending', self.__pointChangePending) vp.connect('change_complete', self.__pointChangeComplete) p1.storeUser(self) p1.connect('moved', self.__movePoint) p1.connect('change_pending', self.__pointChangePending) p1.connect('change_complete', self.__pointChangeComplete) p2.storeUser(self) p2.connect('moved', self.__movePoint) p2.connect('change_pending', self.__pointChangePending) p2.connect('change_complete', self.__pointChangeComplete) self.calcDimValues() def __eq__(self, adim): """Compare two AngularDimensions for equality. """ if not isinstance(adim, AngularDimension): return False _val = False _lvp = self.__vp.getParent() _lp1 = self.__p1.getParent() _lp2 = self.__p2.getParent() _vl, _l1, _l2 = adim.getDimLayers() _vp, _p1, _p2 = adim.getDimPoints() if (_lvp is _vl and self.__vp == _vp and _lp1 is _l1 and self.__p1 == _p1 and _lp2 is _l2 and self.__p2 == _p2): _val = True return _val def __ne__(self, adim): """Compare two AngularDimensions for inequality. """ if not isinstance(adim, AngularDimension): return True _val = True _lvp = self.__vp.getParent() _lp1 = self.__p1.getParent() _lp2 = self.__p2.getParent() _vl, _l1, _l2 = adim.getDimLayers() _vp, _p1, _p2 = adim.getDimPoints() if (_lvp is _vl and self.__vp == _vp and _lp1 is _l1 and self.__p1 == _p1 and _lp2 is _l2 and self.__p2 == _p2): _val = False return _val def finish(self): self.__vp.disconnect(self) self.__vp.freeUser(self) self.__p1.disconnect(self) self.__p1.freeUser(self) self.__p2.disconnect(self) self.__p2.freeUser(self) self.__bar1 = self.__bar2 = self.__crossarc = None self.__vp = self.__p1 = self.__p2 = None super(AngularDimension, self).finish() def getValues(self): """Return values comprising the AngularDimension. getValues() This method extends the Dimension::getValues() method. """ _data = super(AngularDimension, self).getValues() _data.setValue('type', 'adim') _data.setValue('vp', self.__vp.getID()) _layer = self.__vp.getParent() _data.setValue('vl', _layer.getID()) _data.setValue('p1', self.__p1.getID()) _layer = self.__p1.getParent() _data.setValue('l1', _layer.getID()) _data.setValue('p2', self.__p2.getID()) _layer = self.__p2.getParent() _data.setValue('l2', _layer.getID()) return _data def getDimLayers(self): """Return the layers used in an AngularDimension. getDimLayers() """ _vl = self.__vp.getParent() _l1 = self.__p1.getParent() _l2 = self.__p2.getParent() return _vl, _l1, _l2 def getDimPoints(self): """Return the points used in an AngularDimension. getDimPoints() """ return self.__vp, self.__p1, self.__p2 def getVertexPoint(self): """Return the vertex point used in an AngularDimension. getVertexPoint() """ return self.__vp def setVertexPoint(self, p): """Set the vertex point for an AngularDimension. setVertexPoint(p) There is one required argument for this method: p: A Point contained in Layer """ if self.isLocked(): raise RuntimeError, "Setting vertex point allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid point type: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in a Layer!" _vp = self.__vp if _vp is not p: _vp.disconnect(self) _vp.freeUser(self) self.startChange('point_changed') self.__vp = p self.endChange('point_changed') p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) self.sendMessage('point_changed', _vp, p) self.calcDimValues() if abs(_vp.x - p.x) > 1e-10 or abs(_vp.y - p.y) > 1e-10: _x1, _y1 = self.__p1.getCoords() _x2, _y2 = self.__p2.getCoords() _dx, _dy = self.getLocation() self.sendMessage('moved', _vp.x, _vp.y, _x1, _y1, _x2, _y2, _dx, _dy) self.modified() vp = property(getVertexPoint, None, None, "Angular Dimension vertex point.") def getP1(self): """Return the first angle point used in an AngularDimension. getP1() """ return self.__p1 def setP1(self, p): """Set the first Point for an AngularDimension. setP1(p) There is one required argument for this method: p: A Point contained in a Layer. """ if self.isLocked(): raise RuntimeError, "Setting vertex point allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid point type: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in a Layer!" _p1 = self.__p1 if _p1 is not p: _p1.disconnect(self) _p1.freeUser(self) self.startChange('point_changed') self.__p1 = p self.endChange('point_changed') p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) self.sendMessage('point_changed', _p1, p) self.calcDimValues() if abs(_p1.x - p.x) > 1e-10 or abs(_p1.y - p.y) > 1e-10: _vx, _vy = self.__vp.getCoords() _x2, _y2 = self.__p2.getCoords() _dx, _dy = self.getLocation() self.sendMessage('moved', _vx, _vy, _p1.x, _p1.y, _x2, _y2, _dx, _dy) self.modified() p1 = property(getP1, None, None, "Dimension first point.") def getP2(self): """Return the second angle point used in an AngularDimension. getP2() """ return self.__p2 def setP2(self, p): """Set the second Point for an AngularDimension. setP2(p) There is one required argument for this method: l: The layer holding the Point p p: A point in Layer l """ if self.isLocked(): raise RuntimeError, "Setting vertex point allowed - object locked." if not isinstance(p, point.Point): raise TypeError, "Invalid point type: " + `type(p)` if p.getParent() is None: raise ValueError, "Point not found in a Layer!" _p2 = self.__p2 if _p2 is not p: _p2.disconnect(self) _p2.freeUser(self) self.startChange('point_changed') self.__p2 = p self.endChange('point_changed') p.storeUser(self) p.connect('moved', self.__movePoint) p.connect('change_pending', self.__pointChangePending) p.connect('change_complete', self.__pointChangeComplete) self.sendMessage('point_changed', _p2, p) self.calcDimValues() if abs(_p2.x - p.x) > 1e-10 or abs(_p2.y - p.y) > 1e-10: _vx, _vy = self.__vp.getCoords() _x1, _y1 = self.__p1.getCoords() _dx, _dy = self.getLocation() self.sendMessage('moved', _vx, _vy, _x1, _y1, _p2.x, _p2.y, _dx, _dy) self.modified() p2 = property(getP2, None, None, "Dimension second point.") def getDimAngles(self): """Get the array of dimension bar angles. geDimAngles() """ _angle1 = self.__bar1.getAngle() _angle2 = self.__bar2.getAngle() return _angle1, _angle2 def getDimRadius(self): """Get the radius of the dimension crossarc. getDimRadius() """ return self.__crossarc.getRadius() def getDimBars(self): """Return the dimension boundary bars. getDimBars() """ return self.__bar1, self.__bar2 def getDimCrossarc(self): """Get the DimCrossarc object used by the AngularDimension. getDimCrossarc() """ return self.__crossarc def invert(self): """Switch the endpoints used in this object. invert() Invoking this method on an AngularDimension will result in it measuring the opposite angle than what it currently measures. """ _pt = self.__p1 self.startChange('inverted') self.__p1 = self.__p2 self.__p2 = _pt self.endChange('inverted') self.sendMessage('inverted') self.calcDimValues() self.modified() def calculate(self): """Find the value of the angle measured by this AngularDimension. calculate() """ _vx, _vy = self.__vp.getCoords() _p1x, _p1y = self.__p1.getCoords() _p2x, _p2y = self.__p2.getCoords() _a1 = _rtd * math.atan2((_p1y - _vy), (_p1x - _vx)) if _a1 < 0.0: _a1 = _a1 + 360.0 _a2 = _rtd * math.atan2((_p2y - _vy), (_p2x - _vx)) if _a2 < 0.0: _a2 = _a2 + 360.0 _val = _a2 - _a1 if _a1 > _a2: _val = _val + 360.0 return _val def inRegion(self, xmin, ymin, xmax, ymax, fully=False): """Return whether or not an AngularDimension exists within a region. isRegion(xmin, ymin, xmax, ymax[, fully]) The four arguments define the boundary of an area, and the function returns True if the RadialDimension lies within that area. If the optional argument 'fully' is used and is True, then the dimensioned circle and the location of the dimension text must lie within the boundary. Otherwise, the function returns False. """ _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Illegal values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Illegal values: ymax < ymin" util.test_boolean(fully) _vx, _vy = self.__vp.getCoords() _dx, _dy = self.getLocation() _pxmin, _pymin, _pxmax, _pymax = self.getBounds() _val = False if ((_pxmin > _xmax) or (_pymin > _ymax) or (_pxmax < _xmin) or (_pymax < _ymin)): return False if _xmin < _dx < _xmax and _ymin < _dy < _ymax: return True # # bar on vp-p1 line # _ep1, _ep2 = self.__bar1.getEndpoints() _ex1, _ey1 = _ep1 _ex2, _ey2 = _ep2 if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax): return True # # bar at vp-p2 line # _ep1, _ep2 = self.__bar2.getEndpoints() _ex1, _ey1 = _ep1 _ex2, _ey2 = _ep2 if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax): return True # # dimension crossarc # _val = False _r = self.__crossarc.getRadius() _d1 = math.hypot((_xmin - _vx), (_ymin - _vy)) _d2 = math.hypot((_xmin - _vx), (_ymax - _vy)) _d3 = math.hypot((_xmax - _vx), (_ymax - _vy)) _d4 = math.hypot((_xmax - _vx), (_ymin - _vy)) _dmin = min(_d1, _d2, _d3, _d4) _dmax = max(_d1, _d2, _d3, _d4) if _xmin < _vx < _xmax and _ymin < _vy < _ymax: _dmin = -1e-10 else: if _vx > _xmax and _ymin < _vy < _ymax: _dmin = _vx - _xmax elif _vx < _xmin and _ymin < _vy < _ymax: _dmin = _xmin - _vx elif _vy > _ymax and _xmin < _vx < _xmax: _dmin = _vy - _ymax elif _vy < _ymin and _xmin < _vx < _xmax: _dmin = _ymin - _vy if _dmin < _r < _dmax: _da = _rtd * math.atan2((_ymin - _vy), (_xmin - _vx)) if _da < 0.0: _da = _da + 360.0 _val = self._throughAngle(_da) if _val: return _val _da = _rtd * math.atan2((_ymin - _vy), (_xmax - _vx)) if _da < 0.0: _da = _da + 360.0 _val = self._throughAngle(_da) if _val: return _val _da = _rtd * math.atan2((_ymax - _vy), (_xmax - _vx)) if _da < 0.0: _da = _da + 360.0 _val = self._throughAngle(_da) if _val: return _val _da = _rtd * math.atan2((_ymax - _vy), (_xmin - _vx)) if _da < 0.0: _da = _da + 360.0 _val = self._throughAngle(_da) return _val def _throughAngle(self, angle): """Test if the angular crossarc exists at a certain angle. _throughAngle() This method is private to the AngularDimension class. """ _crossarc = self.__crossarc _sa = _crossarc.getStartAngle() _ea = _crossarc.getEndAngle() _val = True if abs(_sa - _ea) > 1e-10: if _sa > _ea: if angle > _ea and angle < _sa: _val = False else: if angle > _ea or angle < _sa: _val = False return _val def calcDimValues(self, allpts=True): """Recalculate the values for dimensional display. calcDimValues([allpts]) The optional argument 'allpts' is by default True. Calling this method with the argument set to False will skip the calculation of any dimension endpoint marker points. """ _allpts = allpts util.test_boolean(_allpts) _vx, _vy = self.__vp.getCoords() _p1x, _p1y = self.__p1.getCoords() _p2x, _p2y = self.__p2.getCoords() _dx, _dy = self.getLocation() _offset = self.getOffset() _ext = self.getExtension() _bar1 = self.__bar1 _bar2 = self.__bar2 _crossarc = self.__crossarc _dv1 = math.hypot((_p1x - _vx), (_p1y - _vy)) _dv2 = math.hypot((_p2x - _vx), (_p2y - _vy)) _ddp = math.hypot((_dx - _vx), (_dy - _vy)) _crossarc.setRadius(_ddp) # # first dimension bar # _angle = math.atan2((_p1y - _vy), (_p1x - _vx)) _sine = math.sin(_angle) _cosine = math.cos(_angle) _deg = _angle * _rtd if _deg < 0.0: _deg = _deg + 360.0 _crossarc.setStartAngle(_deg) _ex = _vx + (_ddp * _cosine) _ey = _vy + (_ddp * _sine) _crossarc.setFirstCrossbarPoint(_ex, _ey) _crossarc.setFirstEndpoint(_ex, _ey) if _ddp > _dv1: # dim point is radially further to vp than p1 _x1 = _p1x + (_offset * _cosine) _y1 = _p1y + (_offset * _sine) _x2 = _vx + ((_ddp + _ext) * _cosine) _y2 = _vy + ((_ddp + _ext) * _sine) else: # dim point is radially closer to vp than p1 _x1 = _p1x - (_offset * _cosine) _y1 = _p1y - (_offset * _sine) _x2 = _vx + ((_ddp - _ext) * _cosine) _y2 = _vy + ((_ddp - _ext) * _sine) _bar1.setFirstEndpoint(_x1, _y1) _bar1.setSecondEndpoint(_x2, _y2) # # second dimension bar # _angle = math.atan2((_p2y - _vy), (_p2x - _vx)) _sine = math.sin(_angle) _cosine = math.cos(_angle) _deg = _angle * _rtd if _deg < 0.0: _deg = _deg + 360.0 _crossarc.setEndAngle(_deg) _ex = _vx + (_ddp * _cosine) _ey = _vy + (_ddp * _sine) _crossarc.setSecondCrossbarPoint(_ex, _ey) _crossarc.setSecondEndpoint(_ex, _ey) if _ddp > _dv2: # dim point is radially further to vp than p2 _x1 = _p2x + (_offset * _cosine) _y1 = _p2y + (_offset * _sine) _x2 = _vx + ((_ddp + _ext) * _cosine) _y2 = _vy + ((_ddp + _ext) * _sine) else: # dim point is radially closers to vp than p2 _x1 = _p2x - (_offset * _cosine) _y1 = _p2y - (_offset * _sine) _x2 = _vx + ((_ddp - _ext) * _cosine) _y2 = _vy + ((_ddp - _ext) * _sine) _bar2.setFirstEndpoint(_x1, _y1) _bar2.setSecondEndpoint(_x2, _y2) # # test if the DimString lies outside the measured angle # and if it does adjust either the crossarc start or end angle # _deg = _rtd * math.atan2((_dy - _vy), (_dx - _vx)) if _deg < 0.0: _deg = _deg + 360.0 _csa = _crossarc.getStartAngle() _cea = _crossarc.getEndAngle() if ((_csa > _cea) and (_cea < _deg < _csa)): if abs(_csa - _deg) < abs(_deg - _cea): # closer to start _crossarc.setStartAngle(_deg) else: _crossarc.setEndAngle(_deg) elif ((_cea > _csa) and ((_csa > _deg) or (_cea < _deg))): if _deg > _cea: _a1 = _deg - _cea _a2 = 360.0 - _deg + _csa else: _a1 = 360.0 - _cea + _deg _a2 = _csa - _deg if abs(_a1) > abs(_a2): # closer to start _crossarc.setStartAngle(_deg) else: _crossarc.setEndAngle(_deg) else: pass if not _allpts: return # # calculate dimension endpoint marker coordinates # _type = self.getEndpointType() _crossarc.clearMarkerPoints() if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE: return _size = self.getEndpointSize() _a1 = _bar1.getAngle() - 90.0 _a2 = _bar2.getAngle() - 90.0 # print "a1: %g" % _a1 # print "a2: %g" % _a2 _mp1, _mp2 = _crossarc.getCrossbarPoints() _x1, _y1 = _mp1 _x2, _y2 = _mp2 # print "x1: %g" % _x1 # print "y1: %g" % _y1 # print "x2: %g" % _x2 # print "y2: %g" % _y2 _sin1 = math.sin(_dtr * _a1) _cos1 = math.cos(_dtr * _a1) _sin2 = math.sin(_dtr * _a2) _cos2 = math.cos(_dtr * _a2) if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW: _height = _size/5.0 # p1 -> (x,y) = (size, _height) _mx = (_cos1 * (-_size) - _sin1 * _height) + _x1 _my = (_sin1 * (-_size) + _cos1 * _height) + _y1 _crossarc.storeMarkerPoint(_mx, _my) # p2 -> (x,y) = (size, -_height) _mx = (_cos1 * (-_size) - _sin1 *(-_height)) + _x1 _my = (_sin1 * (-_size) + _cos1 *(-_height)) + _y1 _crossarc.storeMarkerPoint(_mx, _my) # p3 -> (x,y) = (size, _height) _mx = (_cos2 * _size - _sin2 * _height) + _x2 _my = (_sin2 * _size + _cos2 * _height) + _y2 _crossarc.storeMarkerPoint(_mx, _my) # p4 -> (x,y) = (size, -_height) _mx = (_cos2 * _size - _sin2 *(-_height)) + _x2 _my = (_sin2 * _size + _cos2 *(-_height)) + _y2 _crossarc.storeMarkerPoint(_mx, _my) elif _type == Dimension.DIM_ENDPT_SLASH: _angle = 30.0 * _dtr # slope of slash _height = 0.5 * _size * math.sin(_angle) _length = 0.5 * _size * math.cos(_angle) # p1 -> (x,y) = (-_length, -_height) _mx = (_cos1 * (-_length) - _sin1 * (-_height)) + _x1 _my = (_sin1 * (-_length) + _cos1 * (-_height)) + _y1 _crossarc.storeMarkerPoint(_mx, _my) # p2 -> (x,y) = (_length, _height) _mx = (_cos1 * _length - _sin1 * _height) + _x1 _my = (_sin1 * _length + _cos1 * _height) + _y1 _crossarc.storeMarkerPoint(_mx, _my) # p3 -> (x,y) = (-_length, -_height) _mx = (_cos2 * (-_length) - _sin2 * (-_height)) + _x2 _my = (_sin2 * (-_length) + _cos2 * (-_height)) + _y2 _crossarc.storeMarkerPoint(_mx, _my) # p4 -> (x,y) = (_length, _height) _mx = (_cos2 * _length - _sin2 * _height) + _x2 _my = (_sin2 * _length + _cos2 * _height) + _y2 _crossarc.storeMarkerPoint(_mx, _my) else: raise ValueError, "Unexpected endpoint type: '%s'" % str(_type) def mapCoords(self, x, y, tol=tolerance.TOL): """Test an x/y coordinate pair hit the dimension lines and arc. mapCoords(x, y[, tol]) This method has two required parameters: x: The x-coordinate y: The y-coordinate These should both be float values. There is an optional third parameter, 'tol', giving the maximum distance from the dimension bars that the x/y coordinates may sit. """ _x = util.get_float(x) _y = util.get_float(y) _t = tolerance.toltest(tol) # # test vp-p1 bar # _ep1, _ep2 = self.__bar1.getEndpoints() _ex1, _ey1 = _ep1 _ex2, _ey2 = _ep2 _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t) if _mp is not None: return _mp # # test vp-p2 bar # _ep1, _ep2 = self.__bar2.getEndpoints() _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t) if _mp is not None: return _mp # # test the arc # _vx, _vy = self.__vp.getCoords() _psep = math.hypot((_vx - _x), (_vy - y)) _dx, _dy = self.getLocation() _dsep = math.hypot((_vx - _dx), (_vy - _dy)) if abs(_psep - _dsep) < _t: _crossarc = self.__crossarc _sa = _crossarc.getStartAngle() _ea = _crossarc.getEndAngle() _angle = _rtd * math.atan2((_y - _vy), (_x - _vx)) _val = True if abs(_sa - _ea) > 1e-10: if _sa < _ea: if _angle < _sa or _angle > _ea: _val = False else: if _angle > _ea or _angle < _sa: _val = False if _val: _xoff = _dsep * math.cos(_angle) _yoff = _dsep * math.sin(_angle) return (_vx + _xoff), (_vy + _yoff) return None def onDimension(self, x, y, tol=tolerance.TOL): return self.mapCoords(x, y, tol) is not None def getBounds(self): """Return the minimal and maximal locations of the dimension getBounds() This method overrides the Dimension::getBounds() method """ _vx, _vy = self.__vp.getCoords() _dx, _dy = self.getLocation() _dxpts = [] _dypts = [] _ep1, _ep2 = self.__bar1.getEndpoints() _dxpts.append(_ep1[0]) _dypts.append(_ep1[1]) _dxpts.append(_ep2[0]) _dypts.append(_ep2[1]) _ep1, _ep2 = self.__bar2.getEndpoints() _dxpts.append(_ep1[0]) _dypts.append(_ep1[1]) _dxpts.append(_ep2[0]) _dypts.append(_ep2[1]) _rad = self.__crossarc.getRadius() if self._throughAngle(0.0): _dxpts.append((_vx + _rad)) if self._throughAngle(90.0): _dypts.append((_vy + _rad)) if self._throughAngle(180.0): _dxpts.append((_vx - _rad)) if self._throughAngle(270.0): _dypts.append((_vy - _rad)) _xmin = min(_dx, min(_dxpts)) _ymin = min(_dy, min(_dypts)) _xmax = max(_dx, max(_dxpts)) _ymax = max(_dy, max(_dypts)) return _xmin, _ymin, _xmax, _ymax def clone(self): _vp = self.__vp _p1 = self.__p1 _p2 = self.__p2 _x, _y = self.getLocation() _ds = self.getDimStyle() _adim = AngularDimension(_vp, _p1, _p2, _x, _y, _ds) _adim.copyDimValues(self) return _adim def __movePoint(self, p, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen if ((p is not self.__vp) and (p is not self.__p1) and (p is not self.__p2)): raise ValueError, "Unexpected dimension point: " + `p` _dxmin, _dymin, _dxmax, _dymax = self.getBounds() self.calcDimValues() self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax) def __pointChangePending(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved': self.startChange('moved') def __pointChangeComplete(self, p, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen if args[0] == 'moved': self.endChange('moved') def sendsMessage(self, m): if m in AngularDimension.__messages: return True return super(AngularDimension, self).sendsMessage(m) # # DimString history class # class DimStringLog(text.TextBlockLog): __setops = { 'prefix_changed' : DimString.setPrefix, 'suffix_changed' : DimString.setSuffix, 'units_changed' : DimString.setUnits, 'precision_changed' : DimString.setPrecision, 'print_zero_changed' : DimString.setPrintZero, 'print_decimal_changed' : DimString.setPrintDecimal, 'dimension_changed' : DimString.setDimension, } __getops = { 'prefix_changed' : DimString.getPrefix, 'suffix_changed' : DimString.getSuffix, 'units_changed' : DimString.getUnits, 'precision_changed' : DimString.getPrecision, 'print_zero_changed' : DimString.getPrintZero, 'print_decimal_changed' : DimString.getPrintDecimal, 'dimension_changed' : DimString.getDimension, } def __init__(self, obj): if not isinstance(obj, DimString): raise TypeError, "Invalid DimString type: " + `type(obj)` super(DimStringLog, self).__init__(obj) _ds = self.getObject() _ds.connect('prefix_changed', self._prefixChanged) _ds.connect('suffix_changed', self._suffixChanged) _ds.connect('units_changed', self._unitsChanged) _ds.connect('precision_changed', self._precisionChanged) _ds.connect('print_zero_changed', self._printZeroChanged) _ds.connect('print_decimal_changed', self._printDecimalChanged) _ds.connect('dimension_changed', self._dimensionChanged) def _prefixChanged(self, ds, *args): # print "prefixChanged() ..." _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _prefix = args[0] if not isinstance(_prefix, types.StringTypes): raise TypeError, "Invalid prefix type: " + `type(_prefix)` # print "old prefix: %s" % _prefix self.saveUndoData('prefix_changed', _prefix) def _suffixChanged(self, ds, *args): # print "suffixChanged() ..." _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _suffix = args[0] if not isinstance(_suffix, types.StringTypes): raise TypeError, "Invalid suffix type " + `type(_suffix)` # print "old suffix: %s" % _suffix self.saveUndoData('suffix_changed', _suffix) def _unitsChanged(self, ds, *args): # print "unitsChanged() ..." _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _units = args[0] if not isinstance(_units, int): raise TypeError, "Invalid unit type: " + `type(_units)` # print "old units: %d" % _units self.saveUndoData('units_changed', _units) def _precisionChanged(self, ds, *args): # print "precisionChanged() ..." _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _prec = args[0] if not isinstance(_prec, int): raise TypeError, "Invalid precision type: " + `type(_prec)` # print "old precision: %d" % _prec self.saveUndoData('precision_changed', _prec) def _printZeroChanged(self, ds, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _flag = args[0] util.test_boolean(_flag) self.saveUndoData('print_zero_changed', _flag) def _printDecimalChanged(self, ds, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _flag = args[0] util.test_boolean(_flag) self.saveUndoData('print_decimal_changed', _flag) def _dimensionChanged(self, ds, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _dim = args[0] if not isinstance(_dim, Dimension): raise TypeError, "Invalid dimension type: " + `type(_dim)` self.saveUndoData('dimension_changed', _dim.getID()) def execute(self, undo, *args): util.test_boolean(undo) _alen = len(args) if len(args) == 0: raise ValueError, "No arguments to execute()" _obj = self.getObject() _op = args[0] if (_op == 'prefix_changed' or _op == 'suffix_changed' or _op == 'units_changed' or _op == 'precision_changed' or _op == 'print_zero_changed' or _op == 'print_decimal_changed'): if len(args) < 2: raise ValueError, "Invalid argument count: %d" % _alen _val = args[1] _get = DimStringLog.__getops[_op] _sdata = _get(_obj) self.ignore(_op) try: _set = DimStringLog.__setops[_op] if undo: _obj.startUndo() try: _set(_obj, _val) finally: _obj.endUndo() else: _obj.startRedo() try: _set(_obj, _val) finally: _obj.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _sdata) elif _op == 'dimension_changed': pass # fixme else: super(DimStringLog, self).execute(undo, *args) # # Dimension history class # class DimLog(entity.EntityLog): __setops = { 'offset_changed' : Dimension.setOffset, 'extension_changed' : Dimension.setExtension, 'endpoint_type_changed' : Dimension.setEndpointType, 'endpoint_size_changed' : Dimension.setEndpointSize, 'dual_mode_changed' : Dimension.setDualDimMode, 'dual_mode_offset_changed' : Dimension.setDualModeOffset, 'position_changed' : Dimension.setPosition, 'position_offset_changed' : Dimension.setPositionOffset, 'thickness_changed' : Dimension.setThickness, 'scale_changed' : Dimension.setScale, 'dia_mode_changed' : RadialDimension.setDiaMode, } __getops = { 'offset_changed' : Dimension.getOffset, 'extension_changed' : Dimension.getExtension, 'endpoint_type_changed' : Dimension.getEndpointType, 'endpoint_size_changed' : Dimension.getEndpointSize, 'dual_mode_changed' : Dimension.getDualDimMode, 'dual_mode_offset_changed' : Dimension.getDualModeOffset, 'position_changed' : Dimension.getPosition, 'position_offset_changed' : Dimension.getPositionOffset, 'thickness_changed' : Dimension.getThickness, 'scale_changed' : Dimension.getScale, 'dia_mode_changed' : RadialDimension.getDiaMode, } def __init__(self, dim): if not isinstance(dim, Dimension): raise TypeError, "Invalid dimension type: " + `type(dim)` super(DimLog, self).__init__(dim) _ds1, _ds2 = dim.getDimstrings() _ds1.connect('modified', self._dimstringChanged) _ds2.connect('modified', self._dimstringChanged) dim.connect('offset_changed', self._offsetChanged) dim.connect('extension_changed', self._extensionChanged) # dim.connect('dimstyle_changed', self._dimstyleChanged) dim.connect('endpoint_type_changed', self._endpointTypeChanged) dim.connect('endpoint_size_changed', self._endpointSizeChanged) dim.connect('dual_mode_changed', self._dualModeChanged) dim.connect('dual_mode_offset_changed', self._dualModeOffsetChanged) dim.connect('position_changed', self._positionChanged) dim.connect('position_offset_changed', self._positionOffsetChanged) dim.connect('color_changed', self._colorChanged) dim.connect('thickness_changed', self._thicknessChanged) dim.connect('scale_changed', self._scaleChanged) dim.connect('location_changed', self._locationChanged) if not isinstance(dim, RadialDimension): dim.connect('point_changed', self._pointChanged) if isinstance(dim, RadialDimension): dim.connect('dia_mode_changed', self._diaModeChanged) dim.connect('dimobj_changed', self._dimObjChanged) if isinstance(dim, AngularDimension): dim.connect('inverted', self._dimInverted) # dim.connect('moved', self._moveDim) def detatch(self): _dim = self.getObject() super(DimLog, self).detatch() _ds1, _ds2 = _dim.getDimstrings() _ds1.disconnect(self) _log = _ds1.getLog() if _log is not None: _log.detatch() _ds1.setLog(None) _ds2.disconnect(self) _log = _ds2.getLog() if _log is not None: _log.detatch() _ds2.setLog(None) def _moveDim(self, dim, *args): _alen = len(args) if _alen < 4: raise ValueError, "Invalid argument count: %d" % _alen _xmin = util.get_float(args[0]) _ymin = util.get_float(args[1]) _xmax = util.get_float(args[2]) _ymax = util.get_float(args[3]) self.saveUndoData('moved', _xmin, _ymin, _xmax, _ymax) def _dimstringChanged(self, dstr, *args): _dim = self.getObject() _ds1, _ds2 = _dim.getDimstrings() if dstr is _ds1: _dstr = 'ds1' elif dstr is _ds2: _dstr = 'ds2' else: raise ValueError, "Unexpected Dimstring: " + `dstr` self.saveUndoData('dimstring_changed', _dstr) _dim.modified() def _endpointTypeChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _ep = args[0] if (_ep != Dimension.DIM_ENDPT_NONE and _ep != Dimension.DIM_ENDPT_ARROW and _ep != Dimension.DIM_ENDPT_FILLED_ARROW and _ep != Dimension.DIM_ENDPT_SLASH and _ep != Dimension.DIM_ENDPT_CIRCLE): raise ValueError, "Invalid endpoint value: '%s'" % str(_ep) self.saveUndoData('endpoint_type_changed', _ep) def _endpointSizeChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _size = args[0] if not isinstance(_size, float): raise TypeError, "Unexpected type for size: " + `type(_size)` if _size < 0.0: raise ValueError, "Invalid endpoint size: %g" % _size self.saveUndoData('endpoint_size_changed', _size) def _dualModeChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _flag = args[0] util.test_boolean(_flag) self.saveUndoData('dual_mode_changed', _flag) def _dualModeOffsetChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _offset = args[0] if not isinstance(_offset, float): raise TypeError, "Unexpected type for flag: " + `type(_offset)` if _offset < 0.0: raise ValueError, "Invalid dual mode offset: %g" % _offset self.saveUndoData('dual_mode_offset_changed', _offset) def _positionChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _pos = args[0] if (_pos != Dimension.DIM_TEXT_POS_SPLIT and _pos != Dimension.DIM_TEXT_POS_ABOVE and _pos != Dimension.DIM_TEXT_POS_BELOW): raise ValueError, "Invalid position: '%s'" % str(_pos) self.saveUndoData('position_changed', _pos) def _positionOffsetChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _offset = args[0] if not isinstance(_offset, float): raise TypeError, "Unexpected type for offset: " + `type(_offset)` if _offset < 0.0: raise ValueError, "Invalid position offset: %g" % _offset self.saveUndoData('position_offset_changed', _offset) def _thicknessChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _t = args[0] if not isinstance(_t, float): raise TypeError, "Unexpected type for thickness" + `type(_t)` if _t < 0.0: raise ValueError, "Invalid thickness: %g" % _t self.saveUndoData('thickness_changed', _t) def _scaleChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _s = args[0] if not isinstance(_s, float): raise TypeError, "Unexpected type for scale" + `type(_s)` if not _s > 0.0: raise ValueError, "Invalid scale: %g" % _s self.saveUndoData('scale_changed', _s) def _colorChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _color = args[0] if not isinstance(_color, color.Color): raise TypeError, "Invalid color: " + str(_color) self.saveUndoData('color_changed', _color.getColors()) def _offsetChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _offset = args[0] if not isinstance(_offset, float): raise TypeError, "Unexpected type for offset: " + `type(_offset)` if _offset < 0.0: raise ValueError, "Invalid offset: %g" % _offset self.saveUndoData('offset_changed', _offset) def _extensionChanged(self, dim, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _extlen = args[0] if not isinstance(_extlen, float): raise TypeError, "Unexpected type for length: " + `type(_extlen)` if _extlen < 0.0: raise ValueError, "Invalid extension length: %g" % _extlen self.saveUndoData('extension_changed', _extlen) def _diaModeChanged(self, dim, *args): # RadialDimensions _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _flag = args[0] util.test_boolean(_flag) self.saveUndoData('dia_mode_changed', _flag) def _dimInverted(self, dim, *args): # AngularDimensions self.saveUndoData('inverted') def _dimstyleChanged(self, dim, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _ds = args[0] if not isinstance(_ds, DimStyle): raise TypeError, "Invalid DimStyle type: " + `type(_ds)` _opts = args[1] if not isinstance(_opts, dict): raise TypeError, "Invalid option type: " + `type(_opts)` _data = {} _data['dimstyle'] = _ds.getValues() _data.update(_opts) self.saveUndoData('dimstyle_changed', _data) def _locationChanged(self, dim, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _x = args[0] if not isinstance(_x, float): raise TypeError, "Unexpected type for x location" + `type(_x)` _y = args[1] if not isinstance(_y, float): raise TypeError, "Unexpected type for y location" + `type(_y)` self.saveUndoData('location_changed', _x, _y) def _pointChanged(self, dim, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _op = args[0] if not isinstance(_op, point.Point): raise TypeError, "Unexpected type for old point" + `type(_op)` _np = args[1] if not isinstance(_np, point.Point): raise TypeError, "Unexpected type for new point" + `type(_np)` _ol = _op.getParent() if _ol is None: raise RuntimeError, "Invalid Parent for replaced Point" + `_op` _olid = _ol.getID() _oid = _op.getID() _nl = _np.getParent() if _nl is None: raise RuntimeError, "Invalid Parent for new Point" + `_np` _nlid = _nl.getID() _nid = _np.getID() self.saveUndoData('point_changed', _olid, _oid, _nlid, _nid) def _dimObjChanged(self, dim, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _oo = args[0] if not isinstance(_oo, (circle.Circle, arc.Arc)): raise TypeError, "Unexpected type for old object" + `type(_oo)` _no = args[1] if not isinstance(_no, (circle.Circle, arc.Arc)): raise TypeError, "Unexpected type for new object" + `type(_no)` _ol = _oo.getParent() if _ol is None: raise RuntimeError, "Invalid Parent for replaced object" + `_oo` _olid = _ol.getID() _oid = _oo.getID() _nl = _no.getParent() if _nl is None: raise RuntimeError, "Invalid Parent for new object" + `_no` _nlid = _nl.getID() _nid = _no.getID() self.saveUndoData('dimobj_changed', _olid, _oid, _nlid, _nid) def execute(self, undo, *args): util.test_boolean(undo) _alen = len(args) if len(args) == 0: raise ValueError, "No arguments to execute()" _dim = self.getObject() _image = None _layer = _dim.getParent() if _layer is not None: _image = _layer.getParent() _op = args[0] if (_op == 'offset_changed' or _op == 'extension_changed' or _op == 'endpoint_type_changed' or _op == 'endpoint_size_changed' or _op == 'dual_mode_changed' or _op == 'dual_mode_offset_changed' or _op == 'dia_mode_changed' or _op == 'thickness_changed' or _op == 'scale_changed' or _op == 'position_changed' or _op == 'position_offset_changed'): if len(args) < 2: raise ValueError, "Invalid argument count: %d" % _alen _val = args[1] _get = DimLog.__getops[_op] _sdata = _get(_dim) self.ignore(_op) try: _set = DimLog.__setops[_op] if undo: _dim.startUndo() try: _set(_dim, _val) finally: _dim.endUndo() else: _dim.startRedo() try: _set(_dim, _val) finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _sdata) elif _op == 'color_changed': if _image is None: raise RuntimeError, "Dimension not stored in an Image" if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _sdata = _dim.getColor().getColors() self.ignore(_op) try: _color = None for _c in _image.getImageEntities('color'): if _c.getColors() == args[1]: _color = _c break if undo: _dim.startUndo() try: _dim.setColor(_color) finally: _dim.endUndo() else: _dim.startRedo() try: _dim.setColor(_color) finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _sdata) elif _op == 'location_changed': if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _x = args[1] if not isinstance(_x, float): raise TypeError, "Unexpected type for 'x': " + `type(_x)` _y = args[2] if not isinstance(_y, float): raise TypeError, "Unexpected type for 'y': " + `type(_y)` _dx, _dy = _dim.getLocation() self.ignore(_op) try: if undo: _dim.startUndo() try: _dim.setLocation(_x, _y) finally: _dim.endUndo() else: _dim.startRedo() try: _dim.setLocation(_x, _y) finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _dx, _dy) elif _op == 'point_changed': if _image is None: raise RuntimeError, "Dimension not stored in an Image" if _alen < 5: raise ValueError, "Invalid argument count: %d" % _alen _olid = args[1] _oid = args[2] _nlid = args[3] _nid = args[4] if undo: _lid = _olid else: _lid = _nlid _parent = None _layers = [_image.getTopLayer()] while len(_layers): _layer = _layers.pop() if _lid == _layer.getID(): _parent = _layer break _layers.extend(_layer.getSublayers()) if _parent is None: raise RuntimeError, "Parent layer of old point not found" self.ignore(_op) try: _vp = None _p1 = _dim.getP1() _p2 = _dim.getP2() if isinstance(_dim, AngularDimension): _vp = _dim.getVertexPoint() if undo: _pt = _parent.getObject(_oid) if _pt is None or not isinstance(_pt, point.Point): raise ValueError, "Old endpoint missing: id=%d" % _oid _dim.startUndo() try: if _p1.getID() == _nid: _dim.setP1(_pt) elif _p2.getID() == _nid: _dim.setP2(_pt) elif _vp is not None and _vp.getID() == _nid: _dim.setVertexPoint(_pt) else: raise ValueError, "Unexpected point ID: %d" % _nid finally: _dim.endUndo() else: _pt = _parent.getObject(_nid) if _pt is None or not isinstance(_pt, point.Point): raise ValueError, "New endpoint missing: id=%d" % _nid _dim.startRedo() try: if _p1.getID() == _oid: _dim.setP1(_pt) elif _p2.getID() == _oid: _dim.setP2(_pt) elif _vp is not None and _vp.getID() == _oid: _dim.setVertexPoint(_pt) else: raise ValueError, "Unexpected point ID: %d" % _oid finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _olid, _oid, _nlid, _nid) elif _op == 'dimobj_changed': if _image is None: raise RuntimeError, "Dimension not stored in an Image" if _alen < 5: raise ValueError, "Invalid argument count: %d" % _alen _olid = args[1] _oid = args[2] _nlid = args[3] _nid = args[4] if undo: _lid = _olid else: _lid = _nlid _parent = None _layers = [_image.getTopLayer()] while len(_layers): _layer = _layers.pop() if _lid == _layer.getID(): _parent = _layer break _layers.extend(_layer.getSublayers()) if _parent is None: raise RuntimeError, "Parent layer of old object not found" self.ignore(_op) try: if undo: _oo = _parent.getObject(_oid) if _oo is None or not isinstance(_oo, (circle.Circle, arc.Arc)): raise ValueError, "Old object missing: id=%d" % _oid _dim.startUndo() try: _dim.setDimCircle(_oo) finally: _dim.endUndo() else: _no = _parent.getObject(_nid) if _no is None or not isinstance(_no, (circle.Circle, arc.Arc)): raise ValueError, "New object missing: id=%d" % _nid _dim.startRedo() try: _dim.setDimCircle(_no) finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _olid, _oid, _nlid, _nid) elif _op == 'dimstring_changed': if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _dstr = args[1] _ds1, _ds2 = _dim.getDimstrings() self.ignore('modified') try: if _dstr == 'ds1': if undo: _ds1.undo() else: _ds1.redo() elif _dstr == 'ds2': if undo: _ds2.undo() else: _ds2.redo() else: raise ValueError, "Unexpected dimstring key: " + str(_dstr) finally: self.receive('modified') self.saveData(undo, _op, _dstr) if not undo: _dim.modified() elif _op == 'inverted': # AngularDimensions only self.ignore(_op) try: if undo: _dim.startUndo() try: _dim.invert() finally: _dim.endUndo() else: _dim.startRedo() try: _dim.invert() finally: _dim.endRedo() finally: self.receive(_op) self.saveData(undo, _op) else: super(DimLog, self).execute(undo, *args)