Updating download.html page with Git information
[pythoncad.git] / PythonCAD / Generic / dimension.py
blob640703a8452d7d742d10771586667404bf7dc482
2 # Copyright (c) 2002, 2003, 2004, 2005, 2006 Art Haas
4 # This file is part of PythonCAD.
6 # PythonCAD is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # PythonCAD is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with PythonCAD; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # generic dimension classes
24 import math
25 import sys
26 import types
28 from PythonCAD.Generic import text
29 from PythonCAD.Generic import point
30 from PythonCAD.Generic import circle
31 from PythonCAD.Generic import arc
32 from PythonCAD.Generic import color
33 from PythonCAD.Generic import units
34 from PythonCAD.Generic import util
35 from PythonCAD.Generic import tolerance
36 from PythonCAD.Generic import baseobject
37 from PythonCAD.Generic import entity
39 _dtr = math.pi/180.0
40 _rtd = 180.0/math.pi
42 class DimString(text.TextBlock):
43 """A class for the visual presentation of the dimensional value.
45 The DimString class is used to present the numerical display for
46 the dimension. A DimString object is derived from the text.TextBlock
47 class, so it shares all of that classes methods and attributes.
49 The DimString class has the following additional properties:
51 prefix: A prefix prepended to the dimension text
52 suffix: A suffix appended to the dimension text
53 units: The units the dimension text will display
54 precision: The displayed dimension precision
55 print_zero: Displayed dimensions will have a leading 0 if needed
56 print_decimal: Displayed dimensions will have a trailing decimal point
58 The DimString class has the following additional methods:
60 {get/set}Prefix(): Get/Set the text preceding the dimension value.
61 {get/set}Suffix(): Get/Set the text following the dimension value.
62 {get/set}Units(): Define what units the dimension will be in.
63 {get/set}Precision(): Get/Set how many fractional digits are displayed.
64 {get/set}PrintZero(): Get/Set whether or not a dimension less than one
65 unit long should have a leading 0 printed
66 {get/set}PrintDecimal(): Get/Set whether or not a dimension with 0
67 fractional digits prints out the decimal point.
68 {get/set}Dimension(): Get/Set the dimension using the DimString
70 The DimString class has the following classmethods:
72 {get/set}DefaultTextStyle(): Get/Set the default TextStyle for the class.
73 """
75 __defstyle = None
77 __messages = {
78 'prefix_changed' : True,
79 'suffix_changed' : True,
80 'units_changed' : True,
81 'precision_changed' : True,
82 'print_zero_changed' : True,
83 'print_decimal_changed' : True,
84 'dimension_changed' : True,
87 def __init__(self, x, y, **kw):
88 """Initialize a DimString object
90 ds = DimString(x, y, **kw)
92 Arguments 'x' and 'y' should be floats. Various keyword arguments can
93 also be used:
95 text: Displayed text
96 textstyle: The default textstyle
97 family: Font family
98 style: Font style
99 weight: Font weight
100 color: Text color
101 size: Text size
102 angle: Text angle
103 align: Text alignment
104 prefix: Default prefix
105 suffix: Default suffix
106 units: Displayed units
107 precision: Displayed precision of units
108 print_zero: Boolean to print a leading '0'
109 print_decimal: Boolean to print a leading '.'
111 By default, a DimString object has the following values ...
113 prefix: Empty string
114 suffix: Empty string
115 unit: Millimeters
116 precision: 3 decimal places
117 print zero: True
118 print decimal: True
120 _text = u''
121 if 'text' in kw:
122 _text = kw['text']
123 _tstyle = self.getDefaultTextStyle()
124 if 'textstyle' in kw:
125 _tstyle = kw['textstyle']
126 del kw['textstyle']
127 _prefix = u''
128 if 'prefix' in kw:
129 _prefix = kw['prefix']
130 if not isinstance(_prefix, types.StringTypes):
131 raise TypeError, "Invalid prefix type: " + `type(_prefix)`
132 _suffix = u''
133 if 'suffix' in kw:
134 _suffix = kw['suffix']
135 if not isinstance(_suffix, types.StringTypes):
136 raise TypeError, "Invalid suffix type: " + `type(_suffix)`
137 _unit = units.MILLIMETERS
138 if 'units' in kw:
139 _unit = kw['units']
140 _prec = 3
141 if 'precision' in kw:
142 _prec = kw['precision']
143 if not isinstance(_prec, int):
144 raise TypeError, "Invalid precision type: " + `type(_prec)`
145 if _prec < 0 or _prec > 15:
146 raise ValueError, "Invalid precision: %d" % _prec
147 _pz = True
148 if 'print_zero' in kw:
149 _pz = kw['print_zero']
150 util.test_boolean(_pz)
151 _pd = True
152 if 'print_decimal' in kw:
153 _pd = kw['print_decimal']
154 util.test_boolean(_pd)
155 super(DimString, self).__init__(x, y, _text, textstyle=_tstyle, **kw)
156 self.__prefix = _prefix
157 self.__suffix = _suffix
158 self.__unit = units.Unit(_unit)
159 self.__precision = _prec
160 self.__print_zero = _pz
161 self.__print_decimal = _pd
162 self.__dim = None
164 def getDefaultTextStyle(cls):
165 if cls.__defstyle is None:
166 _s = text.TextStyle(u'Default Dimension Text Style',
167 color=color.Color(0xffffff),
168 align=text.TextStyle.ALIGN_CENTER)
169 cls.__defstyle = _s
170 return cls.__defstyle
172 getDefaultTextStyle = classmethod(getDefaultTextStyle)
174 def setDefaultTextStyle(cls, s):
175 if not isinstance(s, text.TextStyle):
176 raise TypeError, "Invalid TextStyle: " + `type(s)`
177 cls.__defstyle = s
179 setDefaultTextStyle = classmethod(setDefaultTextStyle)
181 def finish(self):
182 """Finalization for DimString instances.
184 finish()
186 if self.__dim is not None:
187 self.__dim = None
188 super(DimString, self).finish()
190 def getValues(self):
191 """Return values comprising the DimString.
193 getValues()
195 This method extends the TextBlock::getValues() method.
197 _data = super(DimString, self).getValues()
198 _data.setValue('type', 'dimstring')
199 _data.setValue('prefix', self.__prefix)
200 _data.setValue('suffix', self.__suffix)
201 _data.setValue('units', self.__unit.getStringUnit())
202 _data.setValue('precision', self.__precision)
203 _data.setValue('print_zero', self.__print_zero)
204 _data.setValue('print_decimal', self.__print_decimal)
205 return _data
207 def getParent(self):
208 """Get the entity containing the DimString.
210 getParent()
212 This method overrides the Entity::getParent() call.
214 _parent = None
215 if self.__dim is not None:
216 _parent = self.__dim.getParent()
217 return _parent
219 def setLocation(self, x, y):
220 """Set the location of the DimString.
222 setLocation(x, y)
224 Arguments 'x' and 'y' should be floats. This method extends
225 the TextBlock::setLocation() method.
228 # the DimString location is defined in relation to
229 # the position defined by the Dimension::setLocation()
230 # call, so don't bother sending out 'moved' or 'modified'
231 # messages
233 self.mute()
234 try:
235 super(DimString, self).setLocation(x, y)
236 finally:
237 self.unmute()
239 def getPrefix(self):
240 """Return the prefix for the DimString object.
242 getPrefix()
244 return self.__prefix
246 def setPrefix(self, prefix=None):
247 """Set the prefix for the DimString object.
249 setPrefix([prefix])
251 Invoking this method without arguments sets the prefix
252 to an empty string, or to the DimStyle value in the associated
253 Dimension if one is set for the DimString. When an argument
254 is passed, the argument should be a Unicode string.
256 if self.isLocked():
257 raise RuntimeError, "Setting prefix not allowed - object locked."
258 _p = prefix
259 if _p is None:
260 _p = u''
261 if self.__dim is not None:
262 _p = self.__dim.getStyleValue(self, 'prefix')
263 if not isinstance(_p, unicode):
264 _p = unicode(prefix)
265 _op = self.__prefix
266 if _op != _p:
267 self.startChange('prefix_changed')
268 self.__prefix = _p
269 self.endChange('prefix_changed')
270 self.setBounds()
271 self.sendMessage('prefix_changed', _op)
272 self.modified()
274 prefix = property(getPrefix, setPrefix, None, 'Dimension string prefix')
276 def getSuffix(self):
277 """Return the suffix for the DimString object.
279 getSuffix()
281 return self.__suffix
283 def setSuffix(self, suffix=None):
284 """Set the suffix for the DimString object.
286 setSuffix([suffix])
288 Invoking this method without arguments sets the suffix
289 to an empty string, or to the DimStyle value in the associated
290 Dimension if one is set for the DimString.. When an argument
291 is passed, the argument should be a Unicode string.
293 if self.isLocked():
294 raise RuntimeError, "Setting suffix not allowed - object locked."
295 _s = suffix
296 if _s is None:
297 _s = u''
298 if self.__dim is not None:
299 _s = self.__dim.getStyleValue(self, 'suffix')
300 if not isinstance(_s, unicode):
301 _s = unicode(suffix)
302 _os = self.__suffix
303 if _os != _s:
304 self.startChange('suffix_changed')
305 self.__suffix = _s
306 self.endChange('suffix_changed')
307 self.setBounds()
308 self.sendMessage('suffix_changed', _os)
309 self.modified()
311 suffix = property(getSuffix, setSuffix, None, 'Dimension string suffix')
313 def getPrecision(self):
314 """Return the number of decimal points used for the DimString.
316 getPrecision()
318 return self.__precision
320 def setPrecision(self, precision=None):
321 """Set the number of decimal points used for the DimString.
323 setPrecision([p])
325 The valid range of p is 0 <= p <= 15. Invoking this method without
326 arguments sets the precision to 3, or to the DimStyle value in the
327 associated Dimension if one is set for the DimString..
329 if self.isLocked():
330 raise RuntimeError, "Setting precision not allowed - object locked."
331 _p = precision
332 if _p is None:
333 _p = 3
334 if self.__dim is not None:
335 _p = self.__dim.getStyleValue(self, 'precision')
336 if not isinstance(_p, int):
337 raise TypeError, "Invalid precision type: " + `type(_p)`
338 if _p < 0 or _p > 15:
339 raise ValueError, "Invalid precision: %d" % _p
340 _op = self.__precision
341 if _op != _p:
342 self.startChange('precision_changed')
343 self.__precision = _p
344 self.endChange('precision_changed')
345 self.setBounds()
346 self.sendMessage('precision_changed', _op)
347 self.modified()
349 precision = property(getPrecision, setPrecision, None,
350 'Dimension precision')
352 def getUnits(self):
353 """Return the current units used in the DimString().
355 getUnits()
357 return self.__unit.getUnit()
359 def setUnits(self, unit=None):
360 """The the units for the DimString.
362 setUnits([unit])
364 The value units are given in the units module. Invoking this
365 method without arguments sets the units to millimeters, or
366 to the DimStyle value of the associated Dimension if one
367 is set for the DimString.
369 _u = unit
370 if _u is None:
371 _u = units.MILLIMETERS
372 if self.__dim is not None:
373 _u = self.__dim.getStyleValue(self, 'units')
374 _ou = self.__unit.getUnit()
375 if _ou != _u:
376 self.startChange('units_changed')
377 self.__unit.setUnit(_u)
378 self.endChange('units_changed')
379 self.setBounds()
380 self.sendMessage('units_changed', _ou)
381 self.modified()
383 units = property(getUnits, setUnits, None, 'Dimensional units.')
385 def getPrintZero(self):
386 """Return whether or not a leading 0 is printed for the DimString.
388 getPrintZero()
390 return self.__print_zero
392 def setPrintZero(self, print_zero=None):
393 """Set whether or not a leading 0 is printed for the DimString.
395 setPrintZero([pz])
397 Invoking this method without arguments sets the value to True,
398 or to the DimStyle value of the associated Dimension if one is
399 set for the DimString. If called with an argument, the argument
400 should be either True or False.
402 _pz = print_zero
403 if _pz is None:
404 _pz = True
405 if self.__dim is not None:
406 _pz = self.__dim.getStyleValue(self, 'print_zero')
407 util.test_boolean(_pz)
408 _flag = self.__print_zero
409 if _flag is not _pz:
410 self.startChange('print_zero_changed')
411 self.__print_zero = _pz
412 self.endChange('print_zero_changed')
413 self.setBounds()
414 self.sendMessage('print_zero_changed', _flag)
415 self.modified()
417 print_zero = property(getPrintZero, setPrintZero, None,
418 'Print a leading 0 for decimal dimensions')
420 def getPrintDecimal(self):
421 """Return whether or not the DimString will print a trailing decimal.
423 getPrintDecimal()
425 return self.__print_decimal
427 def setPrintDecimal(self, print_decimal=None):
428 """Set whether or not the DimString will print a trailing decimal.
430 setPrintDecimal([pd])
432 Invoking this method without arguments sets the value to True, or
433 to the DimStyle value of the associated Dimension if one is set
434 for the DimString. If called with an argument, the argument should
435 be either True or False.
437 _pd = print_decimal
438 if _pd is None:
439 _pd = True
440 if self.__dim is not None:
441 _pd = self.__dim.getStyleValue(self, 'print_decimal')
442 util.test_boolean(_pd)
443 _flag = self.__print_decimal
444 if _flag is not _pd:
445 self.startChange('print_decimal_changed')
446 self.__print_decimal = _pd
447 self.endChange('print_decimal_changed')
448 self.setBounds()
449 self.sendMessage('print_decimal_changed', _flag)
450 self.modified()
452 print_decimal = property(getPrintDecimal, setPrintDecimal, None,
453 'Print a decimal point after the dimension value')
455 def getDimension(self):
456 """Return the dimension using the Dimstring.
458 getDimension()
460 This method can return None if there is not Dimension association set
461 for the DimString.
463 return self.__dim
465 def setDimension(self, dim, adjust):
466 """Set the dimension using this DimString.
468 setDimension(dim, adjust)
470 Argument 'dim' must be a Dimension or None, and argument
471 'adjust' must be a Boolean. Argument 'adjust' is only used
472 if a Dimension is passed for the first argument.
474 _dim = dim
475 if _dim is not None and not isinstance(_dim, Dimension):
476 raise TypeError, "Invalid dimension: " + `type(_dim)`
477 util.test_boolean(adjust)
478 _d = self.__dim
479 if _d is not _dim:
480 self.startChange('dimension_changed')
481 self.__dim = _dim
482 self.endChange('dimension_changed')
483 if _dim is not None and adjust:
484 self.setPrefix()
485 self.setSuffix()
486 self.setPrecision()
487 self.setUnits()
488 self.setPrintZero()
489 self.setPrintDecimal()
490 self.setFamily()
491 self.setStyle()
492 self.setWeight()
493 self.setColor()
494 self.setSize()
495 self.setAngle()
496 self.setAlignment()
497 self.sendMessage('dimension_changed', _d)
498 self.modified()
499 if self.__dim is not None:
500 self.setParent(self.__dim.getParent())
503 # extend the TextBlock set methods to use the values
504 # found in a DimStyle if one is available
507 def setFamily(self, family=None):
508 """Set the font family for the DimString.
510 setFamily([family])
512 Calling this method without an argument will set the
513 family to that given in the DimStyle of the associated
514 Dimension if one is set for this DimString.
516 _family = family
517 if _family is None and self.__dim is not None:
518 _family = self.__dim.getStyleValue(self, 'font_family')
519 super(DimString, self).setFamily(_family)
521 def setStyle(self, style=None):
522 """Set the font style for the DimString.
524 setStyle([style])
526 Calling this method without an argument will set the
527 font style to that given in the DimStyle of the associated
528 Dimension if one is set for this DimString.
530 _style = style
531 if _style is None and self.__dim is not None:
532 _style = self.__dim.getStyleValue(self, 'font_style')
533 super(DimString, self).setStyle(_style)
535 def setWeight(self, weight=None):
536 """Set the font weight for the DimString.
538 setWeight([weight])
540 Calling this method without an argument will set the
541 font weight to that given in the DimStyle of the associated
542 Dimension if one is set for this DimString.
544 _weight = weight
545 if _weight is None and self.__dim is not None:
546 _weight = self.__dim.getStyleValue(self, 'font_weight')
547 super(DimString, self).setWeight(_weight)
549 def setColor(self, color=None):
550 """Set the font color for the DimString.
552 setColor([color])
554 Calling this method without an argument will set the
555 font color to that given in the DimStyle of the associated
556 Dimension if one is set for this DimString.
558 _color = color
559 if _color is None and self.__dim is not None:
560 _color = self.__dim.getStyleValue(self, 'color')
561 super(DimString, self).setColor(_color)
563 def setSize(self, size=None):
564 """Set the text size for the DimString.
566 setSize([size])
568 Calling this method without an argument will set the
569 text size to that given in the DimStyle of the associated
570 Dimension if one is set for this DimString.
572 _size = size
573 if _size is None and self.__dim is not None:
574 _size = self.__dim.getStyleValue(self, 'size')
575 super(DimString, self).setSize(_size)
577 def setAngle(self, angle=None):
578 """Set the text angle for the DimString.
580 setAngle([angle])
582 Calling this method without an argument will set the
583 text angle to that given in the DimStyle of the associated
584 Dimension if one is set for this DimString.
586 _angle = angle
587 if _angle is None and self.__dim is not None:
588 _angle = self.__dim.getStyleValue(self, 'angle')
589 super(DimString, self).setAngle(_angle)
591 def setAlignment(self, align=None):
592 """Set the text alignment for the DimString.
594 setAlignment([align])
596 Calling this method without an argument will set the
597 text alignment to that given in the DimStyle of the associated
598 Dimension if one is set for this DimString.
600 _align = align
601 if _align is None and self.__dim is not None:
602 _align = self.__dim.getStyleValue(self, 'alignment')
603 super(DimString, self).setAlignment(_align)
605 def setText(self, text):
606 """Set the text in the DimString.
608 This method overrides the setText method in the TextBlock.
610 pass
612 def formatDimension(self, dist):
613 """Return a formatted numerical value for a dimension.
615 formatDimension(dist)
617 The argument 'dist' should be a float value representing the
618 distance in millimeters. The returned value will have the
619 prefix prepended and suffix appended to the numerical value
620 that has been formatted with the precision.
622 _d = abs(util.get_float(dist))
623 _fmtstr = u"%%#.%df" % self.__precision
624 _dstr = _fmtstr % self.__unit.fromMillimeters(_d)
625 if _d < 1.0 and self.__print_zero is False:
626 _dstr = _dstr[1:]
627 if _dstr.endswith('.') and self.__print_decimal is False:
628 _dstr = _dstr[:-1]
629 _text = self.__prefix + _dstr + self.__suffix
631 # don't send out 'text_changed' or 'modified' messages
633 self.mute()
634 try:
635 super(DimString, self).setText(_text)
636 finally:
637 self.unmute()
638 return _text
640 def sendsMessage(self, m):
641 if m in DimString.__messages:
642 return True
643 return super(DimString, self).sendsMessage(m)
645 class DimBar(entity.Entity):
646 """The class for the dimension bar.
648 A dimension bar leads from the point the dimension references
649 out to, and possibly beyond, the point where the dimension
650 text bar the DimBar to another DimBar. Linear,
651 horizontal, vertical, and angular dimension will have two
652 dimension bars; radial dimensions have none.
654 The DimBar class has the following methods:
656 getEndpoints(): Get the x/y position of the DimBar start and end
657 {get/set}FirstEndpoint(): Get/Set the starting x/y position of the DimBar.
658 {get/set}SecondEndpoint(): Get/Set the ending x/y position of the DimBar.
659 getAngle(): Get the angle at which the DimBar slopes
660 getSinCosValues(): Get trig values used for transformation calculations.
663 __messages = {
664 'attribute_changed' : True,
667 def __init__(self, x1=0.0, y1=0.0, x2=0.0, y2=0.0, **kw):
668 """Initialize a DimBar.
670 db = DimBar([x1, y1, x2, y2])
672 By default all the arguments are 0.0. Any arguments passed to this
673 method should be float values.
675 _x1 = util.get_float(x1)
676 _y1 = util.get_float(y1)
677 _x2 = util.get_float(x2)
678 _y2 = util.get_float(y2)
679 super(DimBar, self).__init__(**kw)
680 self.__ex1 = _x1
681 self.__ey1 = _y1
682 self.__ex2 = _x2
683 self.__ey2 = _y2
685 def getEndpoints(self):
686 """Return the coordinates of the DimBar endpoints.
688 getEndpoints()
690 This method returns two tuples, each containing two float values.
691 The first tuple gives the x/y coordinates of the DimBar start,
692 the second gives the coordinates of the DimBar end.
694 _ep1 = (self.__ex1, self.__ey1)
695 _ep2 = (self.__ex2, self.__ey2)
696 return _ep1, _ep2
698 def setFirstEndpoint(self, x, y):
699 """Set the starting coordinates for the DimBar
701 setFirstEndpoint(x, y)
703 Arguments x and y should be float values.
705 if self.isLocked():
706 raise RuntimeError, "Setting endpoint not allowed - object locked."
707 _x = util.get_float(x)
708 _y = util.get_float(y)
709 _sx = self.__ex1
710 _sy = self.__ey1
711 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
712 self.__ex1 = _x
713 self.__ey1 = _y
714 self.sendMessage('attribute_changed', 'endpoint', _sx, _sy,
715 self.__ex2, self.__ey2)
716 self.modified()
718 def getFirstEndpoint(self):
719 """Return the starting coordinates of the DimBar.
721 getFirstEndpoint()
723 This method returns a tuple giving the x/y coordinates.
725 return self.__ex1, self.__ey1
727 def setSecondEndpoint(self, x, y):
728 """Set the ending coordinates for the DimBar
730 setSecondEndpoint(x, y)
732 Arguments x and y should be float values.
734 if self.isLocked():
735 raise RuntimeError, "Setting endpoint not allowed - object locked."
736 _x = util.get_float(x)
737 _y = util.get_float(y)
738 _sx = self.__ex2
739 _sy = self.__ey2
740 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
741 self.__ex2 = _x
742 self.__ey2 = _y
743 self.sendMessage('attribute_changed', 'endpoint',
744 self.__ex1, self.__ey1, _sx, _sy)
745 self.modified()
747 def getSecondEndpoint(self):
748 """Return the ending coordinates of the DimBar.
750 getSecondEndpoint()
752 This method returns a tuple giving the x/y coordinates.
754 return self.__ex2, self.__ey2
756 def getAngle(self):
757 """Return the angle at which the DimBar lies.
759 getAngle()
761 This method returns a float value giving the angle of inclination
762 of the DimBar.
764 The value returned will be a positive value less than 360.0.
766 _x1 = self.__ex1
767 _y1 = self.__ey1
768 _x2 = self.__ex2
769 _y2 = self.__ey2
770 if abs(_x2 - _x1) < 1e-10 and abs(_y2 - _y1) < 1e-10:
771 raise ValueError, "Endpoints are equal"
772 if abs(_x2 - _x1) < 1e-10: # vertical
773 if _y2 > _y1:
774 _angle = 90.0
775 else:
776 _angle = 270.0
777 elif abs(_y2 - _y1) < 1e-10: # horizontal
778 if _x2 > _x1:
779 _angle = 0.0
780 else:
781 _angle = 180.0
782 else:
783 _angle = _rtd * math.atan2((_y2 - _y1), (_x2 - _x1))
784 if _angle < 0.0:
785 _angle = _angle + 360.0
786 return _angle
788 def getSinCosValues(self):
789 """Return sin()/cos() values based on the DimBar slope
791 getSinCosValues()
793 This method returns a tuple of two floats. The first value is
794 the sin() value, the second is the cos() value.
796 _x1 = self.__ex1
797 _y1 = self.__ey1
798 _x2 = self.__ex2
799 _y2 = self.__ey2
800 if abs(_x2 - _x1) < 1e-10: # vertical
801 _cosine = 0.0
802 if _y2 > _y1:
803 _sine = 1.0
804 else:
805 _sine = -1.0
806 elif abs(_y2 - _y1) < 1e-10: # horizontal
807 _sine = 0.0
808 if _x2 > _x1:
809 _cosine = 1.0
810 else:
811 _cosine = -1.0
812 else:
813 _angle = math.atan2((_y2 - _y1), (_x2 - _x1))
814 _sine = math.sin(_angle)
815 _cosine = math.cos(_angle)
816 return _sine, _cosine
818 def sendsMessage(self, m):
819 if m in DimBar.__messages:
820 return True
821 return super(DimBar, self).sendsMessage(m)
823 class DimCrossbar(DimBar):
824 """The class for the Dimension crossbar.
826 The DimCrossbar class is drawn between two DimBar objects for
827 horizontal, vertical, and generic linear dimensions. The dimension
828 text is place over the DimCrossbar object. Arrow heads, circles, or
829 slashes can be drawn at the intersection of the DimCrossbar and
830 the DimBar if desired. These objects are called markers.
832 The DimCrossbar class is derived from the DimBar class so it shares
833 all the methods of that class. In addition the DimCrossbar class has
834 the following methods:
836 {set/get}FirstCrossbarPoint(): Set/Get the initial location of the crossbar.
837 {set/get}SecondCrossbarPoint(): Set/Get the ending location of the crossbar.
838 getCrossbarPoints(): Get the location of the crossbar endpoints.
839 clearMarkerPoints(): Delete the stored coordintes of the dimension markers.
840 storeMarkerPoint(): Save a coordinate pair of the dimension marker.
841 getMarkerPoints(): Return the coordinates of the dimension marker.
843 __messages = {}
845 def __init__(self, **kw):
846 """Initialize a DimCrossbar object.
848 dcb = DimCrossbar()
850 This method takes no arguments.
852 super(DimCrossbar, self).__init__(**kw)
853 self.__mx1 = 0.0
854 self.__my1 = 0.0
855 self.__mx2 = 0.0
856 self.__my2 = 0.0
857 self.__mpts = []
859 def setFirstCrossbarPoint(self, x, y):
860 """Store the initial endpoint of the DimCrossbar.
862 setFirstCrossbarPoint(x, y)
864 Arguments x and y should be floats.
866 if self.isLocked():
867 raise RuntimeError, "Setting crossbar point not allowed - object locked."
868 _x = util.get_float(x)
869 _y = util.get_float(y)
870 _sx = self.__mx1
871 _sy = self.__my1
872 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
873 self.__mx1 = _x
874 self.__my1 = _y
875 self.sendMessage('attribute_changed', 'barpoint', _sx, _sy,
876 self.__mx2, self.__my2)
877 self.modified()
879 def getFirstCrossbarPoint(self):
880 """Return the initial coordinates of the DimCrossbar.
882 getFirstCrossbarPoint()
884 This method returns a tuple of two floats giving the x/y coordinates.
886 return self.__mx1, self.__my1
888 def setSecondCrossbarPoint(self, x, y):
889 """Store the terminal endpoint of the DimCrossbar.
891 setSecondCrossbarPoint(x, y)
893 Arguments 'x' and 'y' should be floats.
895 if self.isLocked():
896 raise RuntimeError, "Setting crossbar point not allowed - object locked"
897 _x = util.get_float(x)
898 _y = util.get_float(y)
899 _sx = self.__mx2
900 _sy = self.__my2
901 if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
902 self.__mx2 = _x
903 self.__my2 = _y
904 self.sendMessage('attribute_changed', 'barpoint',
905 self.__mx1, self.__my1, _sx, _sy)
906 self.modified()
908 def getSecondCrossbarPoint(self):
909 """Return the terminal coordinates of the DimCrossbar.
911 getSecondCrossbarPoint()
913 This method returns a tuple of two floats giving the x/y coordinates.
915 return self.__mx2, self.__my2
917 def getCrossbarPoints(self):
918 """Return the endpoints of the DimCrossbar.
920 getCrossbarPoints()
922 This method returns two tuples, each tuple containing two float
923 values giving the x/y coordinates.
925 _mp1 = (self.__mx1, self.__my1)
926 _mp2 = (self.__mx2, self.__my2)
927 return _mp1, _mp2
929 def clearMarkerPoints(self):
930 """Delete the stored location of any dimension markers.
932 clearMarkerPoints()
934 del self.__mpts[:]
936 def storeMarkerPoint(self, x, y):
937 """Save a coordinate pair of the current dimension marker.
939 storeMarkerPoint(x, y)
941 Arguments 'x' and 'y' should be floats. Each time this method is invoked
942 the list of stored coordinates is appended with the values given as
943 arguments.
945 _x = util.get_float(x)
946 _y = util.get_float(y)
947 self.__mpts.append((_x, _y))
949 def getMarkerPoints(self):
950 """Return the stored marker coordinates.
952 getMarkerPoints()
954 This method returns a list of coordinates stored with storeMarkerPoint().
955 Each item in the list is a tuple holding two float values - the x/y
956 coordinate of the point.
958 return self.__mpts[:]
960 def sendsMessage(self, m):
961 if m in DimCrossbar.__messages:
962 return True
963 return super(DimCrossbar, self).sendsMessage(m)
965 class DimCrossarc(DimCrossbar):
966 """The class for specialized crossbars for angular dimensions.
968 The DimCrossarc class is meant to be used only with angular dimensions.
969 As an angular dimension has two DimBar objects that are connected
970 with an arc. The DimCrossarc class is derived from the DimCrossbar
971 class so it shares all the methods of that class. The DimCrossarc
972 class has the following additional methods:
974 {get/set}Radius(): Get/Set the radius of the arc.
975 {get/set}StartAngle(): Get/Set the arc starting angle.
976 {get/set}EndAngle(): Get/Set the arc finishing angle.
979 __messages = {
980 'arcpoint_changed' : True,
981 'radius_changed' : True,
982 'start_angle_changed' : True,
983 'end_angle_changed' : True,
986 def __init__(self, radius=0.0, start=0.0, end=0.0, **kw):
987 """Initialize a DimCrossarc object.
989 dca = DimCrossarc([radius, start, end])
991 By default the arguments are all 0.0. Any arguments passed to
992 this method should be floats.
994 super(DimCrossarc, self).__init__(**kw)
995 _r = util.get_float(radius)
996 if _r < 0.0:
997 raise ValueError, "Invalid radius: %g" % _r
998 _start = util.make_c_angle(start)
999 _end = util.make_c_angle(end)
1000 self.__radius = _r
1001 self.__start = _start
1002 self.__end = _end
1004 def getRadius(self):
1005 """Return the radius of the arc.
1007 getRadius()
1009 This method returns a float value.
1011 return self.__radius
1013 def setRadius(self, radius):
1014 """Set the radius of the arc.
1016 setRadius(radius)
1018 Argument 'radius' should be a float value greater than 0.0.
1020 if self.isLocked():
1021 raise RuntimeError, "Setting radius not allowed - object locked."
1022 _r = util.get_float(radius)
1023 if _r < 0.0:
1024 raise ValueError, "Invalid radius: %g" % _r
1025 _sr = self.__radius
1026 if abs(_sr - _r) > 1e-10:
1027 self.startChange('radius_changed')
1028 self.__radius = _r
1029 self.endChange('radius_changed')
1030 self.sendMessage('radius_changed', _sr)
1031 self.modified()
1033 def getStartAngle(self):
1034 """Return the arc starting angle.
1036 getStartAngle()
1038 This method returns a float.
1040 return self.__start
1042 def setStartAngle(self, angle):
1043 """Set the starting angle of the arc.
1045 setStartAngle(angle)
1047 Argument angle should be a float value.
1049 if self.isLocked():
1050 raise RuntimeError, "Setting start angle not allowed - object locked."
1051 _sa = self.__start
1052 _angle = util.make_c_angle(angle)
1053 if abs(_sa - _angle) > 1e-10:
1054 self.startChange('start_angle_changed')
1055 self.__start = _angle
1056 self.endChange('start_angle_changed')
1057 self.sendMessage('start_angle_changed', _sa)
1058 self.modified()
1060 def getEndAngle(self):
1061 """Return the arc ending angle.
1063 getEndAngle()
1065 This method returns a float.
1067 return self.__end
1069 def setEndAngle(self, angle):
1070 """Set the ending angle of the arc.
1072 setEndAngle(angle)
1074 Argument angle should be a float value.
1076 if self.isLocked():
1077 raise RuntimeError, "Setting end angle not allowed - object locked."
1078 _ea = self.__end
1079 _angle = util.make_c_angle(angle)
1080 if abs(_ea - _angle) > 1e-10:
1081 self.startChange('end_angle_changed')
1082 self.__end = _angle
1083 self.endChange('end_angle_changed')
1084 self.sendMessage('end_angle_changed', _ea)
1085 self.modified()
1087 def getAngle(self):
1088 pass # override the DimBar::getAngle() method
1090 def getSinCosValues(self):
1091 pass # override the DimBar::getSinCosValues() method
1093 def sendsMessage(self, m):
1094 if m in DimCrossarc.__messages:
1095 return True
1096 return super(DimCrossarc, self).sendsMessage(m)
1098 class Dimension(entity.Entity):
1099 """The base class for Dimensions
1101 A Dimension object is meant to be a base class for specialized
1102 dimensions.
1104 Every Dimension object holds two DimString objects, so any
1105 dimension can be displayed with two separate formatting options
1106 and units.
1108 A Dimension has the following methods
1110 {get/set}DimStyle(): Get/Set the DimStyle used for this Dimension.
1111 getPrimaryDimstring(): Return the DimString used for formatting the
1112 Primary dimension.
1113 getSecondaryDimstring(): Return the DimString used for formatting the
1114 Secondary dimension.
1115 {get/set}EndpointType(): Get/Set the type of endpoints used in the Dimension
1116 {get/set}EndpointSize(): Get/Set the size of the dimension endpoints
1117 {get/set}DualDimMode(): Get/Set whether or not to display both the Primary
1118 and Secondary DimString objects
1119 {get/set}Offset(): Get/Set how far from the dimension endpoints to draw
1120 dimension lines at the edges of the dimension.
1121 {get/set}Extension(): Get/Set how far past the dimension crossbar line
1122 to draw.
1123 {get/set}Position(): Get/Set where the dimensional values are placed on the
1124 dimension cross bar.
1125 {get/set}Color(): Get/Set the color used to draw the dimension lines.
1126 {get/set}Location(): Get/Set where to draw the dimensional values.
1127 {get/set}PositionOffset(): Get/Set the dimension text offset when the text is
1128 above or below the crossbar/crossarc
1129 {get/set}DualModeOffset(): Get/Set the text offset for spaceing the two
1130 dimension strings above and below the bar
1131 separating the two dimensions
1132 {get/set}Thickness(): Get/Set the Dimension thickness.
1133 {get/set}Scale(): Get/Set the Dimension scaling factor.
1134 getStyleValue(): Return the DimStyle value for some option
1135 getDimensions(): Return the formatted dimensional values in this Dimension.
1136 inRegion(): Return if the dimension is visible within some are.
1137 calcDimValues(): Calculate the dimension lines endpoints.
1138 mapCoords(): Return the coordinates on the dimension within some point.
1139 onDimension(): Test if an x/y coordinate pair hit the dimension lines.
1140 getBounds(): Return the minma and maximum locations of the dimension.
1142 The Dimension class has the following classmethods:
1144 {get/set}DefaultDimStyle(): Get/Set the default DimStyle for the class.
1145 getEndpointTypeAsString(): Return the endpoint type as a string for a value.
1146 getEndpointTypeFromString(): Return the endpoint type value given a string.
1147 getEndpointTypeStrings(): Get the endpoint types values as strings.
1148 getEndpointTypeValues(): Get the endpoint type values.
1149 getPositionAsString(): Return the text position as a string for a value.
1150 getPositionFromString(): Return the text postion value given a string.
1151 getPositionStrings(): Get the text position values as strings.
1152 getPositionValues(): Get the text position values.
1156 # Endpoint
1158 DIM_ENDPT_NONE= 0
1159 DIM_ENDPT_ARROW = 1
1160 DIM_ENDPT_FILLED_ARROW = 2
1161 DIM_ENDPT_SLASH = 3
1162 DIM_ENDPT_CIRCLE = 4
1165 # Dimension position on dimline
1167 DIM_TEXT_POS_SPLIT = 0
1168 DIM_TEXT_POS_ABOVE = 1
1169 DIM_TEXT_POS_BELOW = 2
1171 __defstyle = None
1173 __messages = {
1174 'dimstyle_changed' : True,
1175 'endpoint_type_changed' : True,
1176 'endpoint_size_changed' : True,
1177 'dual_mode_changed' : True,
1178 'offset_changed' : True,
1179 'extension_changed' : True,
1180 'position_changed' : True,
1181 'position_offset_changed' : True,
1182 'dual_mode_offset_changed' : True,
1183 'color_changed' : True,
1184 'thickness_changed' : True,
1185 'scale_changed' : True,
1186 'location_changed' : True,
1187 'dimstring_changed' : True,
1188 'moved' : True,
1191 def __init__(self, x, y, dimstyle=None, **kw):
1192 """Initialize a Dimension object
1194 dim = Dimension(x, y[, ds])
1196 Arguments 'x' and 'y' should be float values. Optional argument
1197 'ds' should be a DimStyle instance. A default DimStyle is used
1198 of the optional argument is not used.
1200 _x = util.get_float(x)
1201 _y = util.get_float(y)
1202 _ds = dimstyle
1203 if _ds is None:
1204 _ds = self.getDefaultDimStyle()
1205 if not isinstance(_ds, DimStyle):
1206 raise TypeError, "Invalid DimStyle type: " + `type(_ds)`
1207 _ddm = None
1208 if 'dual-mode' in kw:
1209 _ddm = kw['dual-mode']
1210 if _ddm is not None:
1211 util.test_boolean(_ddm)
1212 if _ddm is _ds.getValue('DIM_DUAL_MODE'):
1213 _ddm = None
1214 _offset = None
1215 if 'offset' in kw:
1216 _offset = util.get_float(kw['offset'])
1217 if _offset is not None:
1218 if _offset < 0.0:
1219 raise ValueError, "Invalid dimension offset: %g" % _offset
1220 if abs(_offset - _ds.getValue('DIM_OFFSET')) < 1e-10:
1221 _offset = None
1222 _extlen = None
1223 if 'extlen' in kw:
1224 _extlen = util.get_float(kw['extlen'])
1225 if _extlen < 0.0:
1226 raise ValueError, "Invalid dimension extension: %g" % _extlen
1227 if abs(_extlen - _ds.getValue('DIM_EXTENSION')) < 1e-10:
1228 _extlen = None
1229 _textpos = None
1230 if 'textpos' in kw:
1231 _textpos = kw['textpos']
1232 if (_textpos != Dimension.DIM_TEXT_POS_SPLIT and
1233 _textpos != Dimension.DIM_TEXT_POS_ABOVE and
1234 _textpos != Dimension.DIM_TEXT_POS_BELOW):
1235 raise ValueError, "Invalid dimension text position: '%s'" % str(_textpos)
1236 if _textpos == _ds.getValue('DIM_POSITION'):
1237 _textpos = None
1238 _poffset = None
1239 if 'poffset' in kw:
1240 _poffset = util.get_float(kw['poffset'])
1241 if _poffset < 0.0:
1242 raise ValueError, "Invalid text offset length %g" % _poffset
1243 if abs(_poffset - _ds.getValue('DIM_POSITION_OFFSET')) < 1e-10:
1244 _poffset = None
1245 _dmoffset = None
1246 if 'dmoffset' in kw:
1247 _dmoffset = util.get_float(kw['dmoffset'])
1248 if _dmoffset < 0.0:
1249 raise ValueError, "Invalid dual mode offset length %g" % _dmoffset
1250 if abs(_dmoffset - _ds.getValue('DIM_DUAL_MODE_OFFSET')) < 1e-10:
1251 _dmoffset = None
1252 _eptype = None
1253 if 'eptype' in kw:
1254 _eptype = kw['eptype']
1255 if (_eptype != Dimension.DIM_ENDPT_NONE and
1256 _eptype != Dimension.DIM_ENDPT_ARROW and
1257 _eptype != Dimension.DIM_ENDPT_FILLED_ARROW and
1258 _eptype != Dimension.DIM_ENDPT_SLASH and
1259 _eptype != Dimension.DIM_ENDPT_CIRCLE):
1260 raise ValueError, "Invalid endpoint: '%s'" % str(_eptype)
1261 if _eptype == _ds.getValue('DIM_ENDPOINT'):
1262 _eptype = None
1263 _epsize = None
1264 if 'epsize' in kw:
1265 _epsize = util.get_float(kw['epsize'])
1266 if _epsize < 0.0:
1267 raise ValueError, "Invalid endpoint size %g" % _epsize
1268 if abs(_epsize - _ds.getValue('DIM_ENDPOINT_SIZE')) < 1e-10:
1269 _epsize = None
1270 _color = None
1271 if 'color' in kw:
1272 _color = kw['color']
1273 if not isinstance(_color, color.Color):
1274 raise TypeError, "Invalid color type: " + `type(_color)`
1275 if _color == _ds.getValue('DIM_COLOR'):
1276 _color = None
1277 _thickness = None
1278 if 'thickness' in kw:
1279 _thickness = util.get_float(kw['thickness'])
1280 if _thickness < 0.0:
1281 raise ValueError, "Invalid thickness: %g" % _thickness
1282 if abs(_thickness - _ds.getValue('DIM_THICKNESS')) < 1e-10:
1283 _thickness = None
1284 _scale = 1.0
1285 if 'scale' in kw:
1286 _scale = util.get_float(kw['scale'])
1287 if not _scale > 0.0:
1288 raise ValueError, "Invalid scale: %g" % _scale
1290 # dimstrings
1292 # the setDimension() call will adjust the values in the
1293 # new DimString instances if they get created
1295 _ds1 = _ds2 = None
1296 _ds1adj = _ds2adj = True
1297 if 'ds1' in kw:
1298 _ds1 = kw['ds1']
1299 if not isinstance(_ds1, DimString):
1300 raise TypeError, "Invalid DimString type: " + `type(_ds1)`
1301 _ds1adj = False
1302 if _ds1 is None:
1303 _ds1 = DimString(_x, _y)
1305 if 'ds2' in kw:
1306 _ds2 = kw['ds2']
1307 if not isinstance(_ds2, DimString):
1308 raise TypeError, "Invalid DimString type: " + `type(_ds2)`
1309 _ds2adj = False
1310 if _ds2 is None:
1311 _ds2 = DimString(_x, _y)
1313 # finally ...
1315 super(Dimension, self).__init__(**kw)
1316 self.__dimstyle = _ds
1317 self.__ddm = _ddm
1318 self.__offset = _offset
1319 self.__extlen = _extlen
1320 self.__textpos = _textpos
1321 self.__poffset = _poffset
1322 self.__dmoffset = _dmoffset
1323 self.__eptype = _eptype
1324 self.__epsize = _epsize
1325 self.__color = _color
1326 self.__thickness = _thickness
1327 self.__scale = _scale
1328 self.__dimloc = (_x, _y)
1329 self.__ds1 = _ds1
1330 self.__ds2 = _ds2
1331 self.__ds1.setDimension(self, _ds1adj)
1332 self.__ds2.setDimension(self, _ds2adj)
1333 _ds1.connect('change_pending', self.__dimstringChangePending)
1334 _ds1.connect('change_complete', self.__dimstringChangeComplete)
1335 _ds2.connect('change_pending', self.__dimstringChangePending)
1336 _ds2.connect('change_complete', self.__dimstringChangeComplete)
1338 def getDefaultDimStyle(cls):
1339 if cls.__defstyle is None:
1340 cls.__defstyle = DimStyle(u'Default DimStyle')
1341 return cls.__defstyle
1343 getDefaultDimStyle = classmethod(getDefaultDimStyle)
1345 def setDefaultDimStyle(cls, s):
1346 if not isinstance(s, DimStyle):
1347 raise TypeError, "Invalid DimStyle: " + `type(s)`
1348 cls.__defstyle = s
1350 setDefaultDimStyle = classmethod(setDefaultDimStyle)
1352 def finish(self):
1353 self.__ds1.disconnect(self)
1354 self.__ds2.disconnect(self)
1355 self.__ds1.finish()
1356 self.__ds2.finish()
1357 self.__ds1 = self.__ds2 = None
1358 super(Dimension, self).finish()
1360 def getValues(self):
1361 """Return values comprising the Dimension.
1363 getValues()
1365 This method extends the Entity::getValues() method.
1367 _data = super(Dimension, self).getValues()
1368 _data.setValue('location', self.__dimloc)
1369 if self.__offset is not None:
1370 _data.setValue('offset', self.__offset)
1371 if self.__extlen is not None:
1372 _data.setValue('extension', self.__extlen)
1373 if self.__textpos is not None:
1374 _data.setValue('position', self.__textpos)
1375 if self.__eptype is not None:
1376 _data.setValue('eptype', self.__eptype)
1377 if self.__epsize is not None:
1378 _data.setValue('epsize', self.__epsize)
1379 if self.__color is not None:
1380 _data.setValue('color', self.__color.getColors())
1381 if self.__ddm is not None:
1382 _data.setValue('dualmode', self.__ddm)
1383 if self.__poffset is not None:
1384 _data.setValue('poffset', self.__poffset)
1385 if self.__dmoffset is not None:
1386 _data.setValue('dmoffset', self.__dmoffset)
1387 if self.__thickness is not None:
1388 _data.setValue('thickness', self.__thickness)
1389 _data.setValue('ds1', self.__ds1.getValues())
1390 _data.setValue('ds2', self.__ds2.getValues())
1391 _data.setValue('dimstyle', self.__dimstyle.getValues())
1392 return _data
1394 def getDimStyle(self):
1395 """Return the DimStyle used in this Dimension.
1397 getDimStyle()
1399 return self.__dimstyle
1401 def setDimStyle(self, ds):
1402 """Set the DimStyle used for this Dimension.
1404 setDimStyle(ds)
1406 After setting the DimStyle, the values stored in it
1407 are applied to the DimensionObject.
1409 if self.isLocked():
1410 raise RuntimeError, "Changing dimstyle not allowed - object locked."
1411 if not isinstance(ds, DimStyle):
1412 raise TypeError, "Invalid DimStyle type: " + `type(ds)`
1413 _sds = self.__dimstyle
1414 if ds is not _sds:
1415 _opts = self.getValues()
1416 self.startChange('dimstyle_changed')
1417 self.__dimstyle = ds
1418 self.endChange('dimstyle_changed')
1420 # call the various methods without arguments
1421 # so the values given in the new DimStyle are used
1423 self.setOffset()
1424 self.setExtension()
1425 self.setPosition()
1426 self.setEndpointType()
1427 self.setEndpointSize()
1428 self.setColor()
1429 self.setThickness()
1430 self.setDualDimMode()
1431 self.setPositionOffset()
1432 self.setDualModeOffset()
1434 # set the values in the two DimString instances
1436 _d = self.__ds1
1437 _d.setPrefix(ds.getValue('DIM_PRIMARY_PREFIX'))
1438 _d.setSuffix(ds.getValue('DIM_PRIMARY_SUFFIX'))
1439 _d.setPrecision(ds.getValue('DIM_PRIMARY_PRECISION'))
1440 _d.setUnits(ds.getValue('DIM_PRIMARY_UNITS'))
1441 _d.setPrintZero(ds.getValue('DIM_PRIMARY_LEADING_ZERO'))
1442 _d.setPrintDecimal(ds.getValue('DIM_PRIMARY_TRAILING_DECIMAL'))
1443 _d.setFamily(ds.getValue('DIM_PRIMARY_FONT_FAMILY'))
1444 _d.setWeight(ds.getValue('DIM_PRIMARY_FONT_WEIGHT'))
1445 _d.setStyle(ds.getValue('DIM_PRIMARY_FONT_STYLE'))
1446 _d.setColor(ds.getValue('DIM_PRIMARY_FONT_COLOR'))
1447 _d.setSize(ds.getValue('DIM_PRIMARY_TEXT_SIZE'))
1448 _d.setAngle(ds.getValue('DIM_PRIMARY_TEXT_ANGLE'))
1449 _d.setAlignment(ds.getVaue('DIM_PRIMARY_TEXT_ALIGNMENT'))
1450 _d = self.__ds2
1451 _d.setPrefix(ds.getValue('DIM_SECONDARY_PREFIX'))
1452 _d.setSuffix(ds.getValue('DIM_SECONDARY_SUFFIX'))
1453 _d.setPrecision(ds.getValue('DIM_SECONDARY_PRECISION'))
1454 _d.setUnits(ds.getValue('DIM_SECONDARY_UNITS'))
1455 _d.setPrintZero(ds.getValue('DIM_SECONDARY_LEADING_ZERO'))
1456 _d.setPrintDecimal(ds.getValue('DIM_SECONDARY_TRAILING_DECIMAL'))
1457 _d.setFamily(ds.getValue('DIM_SECONDARY_FONT_FAMILY'))
1458 _d.setWeight(ds.getValue('DIM_SECONDARY_FONT_WEIGHT'))
1459 _d.setStyle(ds.getValue('DIM_SECONDARY_FONT_STYLE'))
1460 _d.setColor(ds.getValue('DIM_SECONDARY_FONT_COLOR'))
1461 _d.setSize(ds.getValue('DIM_SECONDARY_TEXT_SIZE'))
1462 _d.setAngle(ds.getValue('DIM_SECONDARY_TEXT_ANGLE'))
1463 _d.setAlignment(ds.getVaue('DIM_SECONDARY_TEXT_ALIGNMENT'))
1464 self.sendMessage('dimstyle_changed', _sds, _opts)
1465 self.modified()
1467 dimstyle = property(getDimStyle, setDimStyle, None,
1468 "Dimension DimStyle object.")
1470 def getEndpointTypeAsString(cls, ep):
1471 """Return a text string for the dimension endpoint type.
1473 getEndpointTypeAsString(ep)
1475 This classmethod returns 'none', 'arrow', or 'filled-arrow', 'slash',
1476 or 'circle'.
1478 if not isinstance(ep, int):
1479 raise TypeError, "Invalid argument type: " + `type(ep)`
1480 if ep == Dimension.DIM_ENDPT_NONE:
1481 _str = 'none'
1482 elif ep == Dimension.DIM_ENDPT_ARROW:
1483 _str = 'arrow'
1484 elif ep == Dimension.DIM_ENDPT_FILLED_ARROW:
1485 _str = 'filled-arrow'
1486 elif ep == Dimension.DIM_ENDPT_SLASH:
1487 _str = 'slash'
1488 elif ep == Dimension.DIM_ENDPT_CIRCLE:
1489 _str = 'circle'
1490 else:
1491 raise ValueError, "Unexpected endpoint type value: %d" % ep
1492 return _str
1494 getEndpointTypeAsString = classmethod(getEndpointTypeAsString)
1496 def getEndpointTypeFromString(cls, s):
1497 """Return the dimension endpoint type given a string argument.
1499 getEndpointTypeFromString(ep)
1501 This classmethod returns a value based on the string argument:
1503 'none' -> Dimension.DIM_ENDPT_NONE
1504 'arrow' -> Dimension.DIM_ENDPT_ARROW
1505 'filled-arrow' -> Dimension.DIM_ENDPT_FILLED_ARROW
1506 'slash' -> Dimension.DIM_ENDPT_SLASH
1507 'circle' -> Dimension.DIM_ENDPT_CIRCLE
1509 If the string is not listed above a ValueError execption is raised.
1511 if not isinstance(s, str):
1512 raise TypeError, "Invalid argument type: " + `type(s)`
1513 _ls = s.lower()
1514 if _ls == 'none':
1515 _v = Dimension.DIM_ENDPT_NONE
1516 elif _ls == 'arrow':
1517 _v = Dimension.DIM_ENDPT_ARROW
1518 elif (_ls == 'filled-arrow' or _ls == 'filled_arrow'):
1519 _v = Dimension.DIM_ENDPT_FILLED_ARROW
1520 elif _ls == 'slash':
1521 _v = Dimension.DIM_ENDPT_SLASH
1522 elif _ls == 'circle':
1523 _v = Dimension.DIM_ENDPT_CIRCLE
1524 else:
1525 raise ValueError, "Unexpected endpoint type string: " + s
1526 return _v
1528 getEndpointTypeFromString = classmethod(getEndpointTypeFromString)
1530 def getEndpointTypeStrings(cls):
1531 """Return the endpoint types as strings.
1533 getEndpointTypeStrings()
1535 This classmethod returns a list of strings.
1537 return [_('None'),
1538 _('Arrow'),
1539 _('Filled-Arrow'),
1540 _('Slash'),
1541 _('Circle')
1544 getEndpointTypeStrings = classmethod(getEndpointTypeStrings)
1546 def getEndpointTypeValues(cls):
1547 """Return the endpoint type values.
1549 getEndpointTypeValues()
1551 This classmethod returns a list of values.
1553 return [Dimension.DIM_ENDPT_NONE,
1554 Dimension.DIM_ENDPT_ARROW,
1555 Dimension.DIM_ENDPT_FILLED_ARROW,
1556 Dimension.DIM_ENDPT_SLASH,
1557 Dimension.DIM_ENDPT_CIRCLE
1560 getEndpointTypeValues = classmethod(getEndpointTypeValues)
1562 def getEndpointType(self):
1563 """Return what type of endpoints the Dimension uses.
1565 getEndpointType()
1567 _et = self.__eptype
1568 if _et is None:
1569 _et = self.__dimstyle.getValue('DIM_ENDPOINT')
1570 return _et
1572 def setEndpointType(self, eptype=None):
1573 """Set what type of endpoints the Dimension will use.
1575 setEndpointType([e])
1577 The argument 'e' should be one of the following
1579 dimension.NO_ENDPOINT => no special marking at the dimension crossbar ends
1580 dimension.ARROW => an arrowhead at the dimension crossbar ends
1581 dimension.FILLED_ARROW => a filled arrohead at the dimension crossbar ends
1582 dimension.SLASH => a slash mark at the dimension crossbar ends
1583 dimension.CIRCLE => a filled circle at the dimension crossbar ends
1585 If this method is called without an argument, the endpoint type is set
1586 to that given in the DimStyle.
1588 if self.isLocked():
1589 raise RuntimeError, "Changing endpoint type allowed - object locked."
1590 _ep = eptype
1591 if _ep is not None:
1592 if (_ep != Dimension.DIM_ENDPT_NONE and
1593 _ep != Dimension.DIM_ENDPT_ARROW and
1594 _ep != Dimension.DIM_ENDPT_FILLED_ARROW and
1595 _ep != Dimension.DIM_ENDPT_SLASH and
1596 _ep != Dimension.DIM_ENDPT_CIRCLE):
1597 raise ValueError, "Invalid endpoint value: '%s'" % str(_ep)
1598 _et = self.getEndpointType()
1599 if ((_ep is None and self.__eptype is not None) or
1600 (_ep is not None and _ep != _et)):
1601 self.startChange('endpoint_type_changed')
1602 self.__eptype = _ep
1603 self.endChange('endpoint_type_changed')
1604 self.calcDimValues()
1605 self.sendMessage('endpoint_type_changed', _et)
1606 self.modified()
1608 endpoint = property(getEndpointType, setEndpointType,
1609 None, "Dimension endpoint type.")
1611 def getEndpointSize(self):
1612 """Return the size of the Dimension endpoints.
1614 getEndpointSize()
1616 _es = self.__epsize
1617 if _es is None:
1618 _es = self.__dimstyle.getValue('DIM_ENDPOINT_SIZE')
1619 return _es
1621 def setEndpointSize(self, size=None):
1622 """Set the size of the Dimension endpoints.
1624 setEndpointSize([size])
1626 Optional argument 'size' should be a float greater than or equal to 0.0.
1627 Calling this method without an argument sets the endpoint size to that
1628 given in the DimStle.
1630 if self.isLocked():
1631 raise RuntimeError, "Changing endpoint type allowed - object locked."
1632 _size = size
1633 if _size is not None:
1634 _size = util.get_float(_size)
1635 if _size < 0.0:
1636 raise ValueError, "Invalid endpoint size: %g" % _size
1637 _es = self.getEndpointSize()
1638 if ((_size is None and self.__epsize is not None) or
1639 (_size is not None and abs(_size - _es) > 1e-10)):
1640 self.startChange('endpoint_size_changed')
1641 self.__epsize = _size
1642 self.endChange('endpoint_size_changed')
1643 self.calcDimValues()
1644 self.sendMessage('endpoint_size_changed', _es)
1645 self.modified()
1647 def getDimstrings(self):
1648 """Return both primary and secondry dimstrings.
1650 getDimstrings()
1652 return self.__ds1, self.__ds2
1654 def getPrimaryDimstring(self):
1655 """ Return the DimString used for formatting the primary dimension.
1657 getPrimaryDimstring()
1659 return self.__ds1
1661 def getSecondaryDimstring(self):
1662 """Return the DimString used for formatting the secondary dimension.
1664 getSecondaryDimstring()
1666 return self.__ds2
1668 def getDualDimMode(self):
1669 """Return if the Dimension is displaying primary and secondary values.
1671 getDualDimMode(self)
1673 _mode = self.__ddm
1674 if _mode is None:
1675 _mode = self.__dimstyle.getValue('DIM_DUAL_MODE')
1676 return _mode
1678 def setDualDimMode(self, mode=None):
1679 """Set the Dimension to display both primary and secondary values.
1681 setDualDimMode([mode])
1683 Optional argument 'mode' should be either True or False.
1684 Invoking this method without arguments will set the dual dimension
1685 value display mode to that given from the DimStyle
1687 if self.isLocked():
1688 raise RuntimeError, "Changing dual mode not allowed - object locked."
1689 _mode = mode
1690 if _mode is not None:
1691 util.test_boolean(_mode)
1692 _ddm = self.getDualDimMode()
1693 if ((_mode is None and self.__ddm is not None) or
1694 (_mode is not None and _mode is not _ddm)):
1695 self.startChange('dual_mode_changed')
1696 self.__ddm = _mode
1697 self.endChange('dual_mode_changed')
1698 self.__ds1.setBounds()
1699 self.__ds2.setBounds()
1700 self.calcDimValues()
1701 self.sendMessage('dual_mode_changed', _ddm)
1702 self.modified()
1704 dual_mode = property(getDualDimMode, setDualDimMode, None,
1705 "Display both primary and secondary dimensions")
1707 def getOffset(self):
1708 """Return the current offset value for the Dimension.
1710 getOffset()
1712 _offset = self.__offset
1713 if _offset is None:
1714 _offset = self.__dimstyle.getValue('DIM_OFFSET')
1715 return _offset
1717 def setOffset(self, offset=None):
1718 """Set the offset value for the Dimension.
1720 setOffset([offset])
1722 Optional argument 'offset' should be a positive float.
1723 Calling this method without arguments sets the value to that
1724 given in the DimStyle.
1726 if self.isLocked():
1727 raise RuntimeError, "Setting offset not allowed - object locked."
1728 _o = offset
1729 if _o is not None:
1730 _o = util.get_float(_o)
1731 if _o < 0.0:
1732 raise ValueError, "Invalid dimension offset length: %g" % _o
1733 _off = self.getOffset()
1734 if ((_o is None and self.__offset is not None) or
1735 (_o is not None and abs(_o - _off) > 1e-10)):
1736 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
1737 self.startChange('offset_changed')
1738 self.__offset = _o
1739 self.endChange('offset_changed')
1740 self.calcDimValues()
1741 self.sendMessage('offset_changed', _off)
1742 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
1743 self.modified()
1745 offset = property(getOffset, setOffset, None, "Dimension offset.")
1747 def getExtension(self):
1748 """Get the extension length of the Dimension.
1750 getExtension()
1752 _ext = self.__extlen
1753 if _ext is None:
1754 _ext = self.__dimstyle.getValue('DIM_EXTENSION')
1755 return _ext
1757 def setExtension(self, ext=None):
1758 """Set the extension length of the Dimension.
1760 setExtension([ext])
1762 Optional argument 'ext' should be a positive float value.
1763 Calling this method without arguments set the extension length
1764 to that given in the DimStyle.
1766 if self.isLocked():
1767 raise RuntimeError, "Setting extension not allowed - object locked."
1768 _e = ext
1769 if _e is not None:
1770 _e = util.get_float(_e)
1771 if _e < 0.0:
1772 raise ValueError, "Invalid dimension extension length: %g" % _e
1773 _ext = self.getExtension()
1774 if ((_e is None and self.__extlen is not None) or
1775 (_e is not None and abs(_e - _ext) > 1e-10)):
1776 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
1777 self.startChange('extension_changed')
1778 self.__extlen = _e
1779 self.endChange('extension_changed')
1780 self.calcDimValues()
1781 self.sendMessage('extension_changed', _ext)
1782 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
1783 self.modified()
1785 extension = property(getExtension, setExtension, None,
1786 "Dimension extension length.")
1788 def getPositionAsString(cls, p):
1789 """Return a text string for the dimension text position.
1791 getPositionAsString(p)
1793 This classmethod returns 'split', 'above', or 'below'
1795 if not isinstance(p, int):
1796 raise TypeError, "Invalid argument type: " + `type(p)`
1797 if p == Dimension.DIM_TEXT_POS_SPLIT:
1798 _str = 'split'
1799 elif p == Dimension.DIM_TEXT_POS_ABOVE:
1800 _str = 'above'
1801 elif p == Dimension.DIM_TEXT_POS_BELOW:
1802 _str = 'below'
1803 else:
1804 raise ValueError, "Unexpected position value: %d" % p
1805 return _str
1807 getPositionAsString = classmethod(getPositionAsString)
1809 def getPositionFromString(cls, s):
1810 """Return the dimension text position given a string argument.
1812 getPositionFromString(s)
1814 This classmethod returns a value based on the string argument:
1816 'split' -> Dimension.DIM_TEXT_POS_SPLIT
1817 'above' -> Dimension.DIM_TEXT_POS_ABOVE
1818 'below' -> Dimension.DIM_TEXT_POS_BELOW
1820 If the string is not listed above a ValueError execption is raised.
1823 if not isinstance(s, str):
1824 raise TypeError, "Invalid argument type: " + `type(s)`
1825 _ls = s.lower()
1826 if _ls == 'split':
1827 _v = Dimension.DIM_TEXT_POS_SPLIT
1828 elif _ls == 'above':
1829 _v = Dimension.DIM_TEXT_POS_ABOVE
1830 elif _ls == 'below':
1831 _v = Dimension.DIM_TEXT_POS_BELOW
1832 else:
1833 raise ValueError, "Unexpected position string: " + s
1834 return _v
1836 getPositionFromString = classmethod(getPositionFromString)
1838 def getPositionStrings(cls):
1839 """Return the position values as strings.
1841 getPositionStrings()
1843 This classmethod returns a list of strings.
1845 return [_('Split'),
1846 _('Above'),
1847 _('Below')
1850 getPositionStrings = classmethod(getPositionStrings)
1852 def getPositionValues(cls):
1853 """Return the position values.
1855 getPositionValues()
1857 This classmethod reutrns a list of values.
1859 return [Dimension.DIM_TEXT_POS_SPLIT,
1860 Dimension.DIM_TEXT_POS_ABOVE,
1861 Dimension.DIM_TEXT_POS_BELOW
1864 getPositionValues = classmethod(getPositionValues)
1866 def getPosition(self):
1867 """Return how the dimension text intersects the crossbar.
1869 getPosition()
1871 _pos = self.__textpos
1872 if _pos is None:
1873 _pos = self.__dimstyle.getValue('DIM_POSITION')
1874 return _pos
1876 def setPosition(self, pos=None):
1877 """Set where the dimension text should be placed at the crossbar.
1879 setPosition([pos])
1881 Choices for optional argument 'pos' are:
1883 dimension.SPLIT => In the middle of the crossbar.
1884 dimension.ABOVE => Beyond the crossbar from the dimensioned objects.
1885 dimension.BELOW => Between the crossbar and the dimensioned objects.
1887 Calling this method without arguments sets the position to that given
1888 in the DimStyle.
1890 if self.isLocked():
1891 raise RuntimeError, "Setting position not allowed - object locked."
1892 _pos = pos
1893 if (_pos != Dimension.DIM_TEXT_POS_SPLIT and
1894 _pos != Dimension.DIM_TEXT_POS_ABOVE and
1895 _pos != Dimension.DIM_TEXT_POS_BELOW):
1896 raise ValueError, "Invalid dimension text position: '%s'" % str(_pos)
1897 _dp = self.getPosition()
1898 if ((_pos is None and self.__textpos is not None) or
1899 (_pos is not None and _pos != _dp)):
1900 self.startChange('position_changed')
1901 self.__textpos = _pos
1902 self.endChange('position_changed')
1903 self.__ds1.setBounds()
1904 self.__ds2.setBounds()
1905 self.sendMessage('position_changed', _dp)
1906 self.modified()
1908 position = property(getPosition, setPosition, None,
1909 "Dimension text position")
1911 def getPositionOffset(self):
1912 """Get the offset for the dimension text and the crossbar/crossarc.
1914 getPositionOffset()
1916 _po = self.__poffset
1917 if _po is None:
1918 _po = self.__dimstyle.getValue('DIM_POSITION_OFFSET')
1919 return _po
1921 def setPositionOffset(self, offset=None):
1922 """Set the separation between the dimension text and the crossbar.
1924 setPositionOffset([offset])
1926 If this method is called without arguments, the text offset
1927 length is set to the value given in the DimStyle.
1928 If the argument 'offset' is supplied, it should be a positive float value.
1930 if self.isLocked():
1931 raise RuntimeError, "Setting text offset length not allowed - object locked."
1932 _o = offset
1933 if _o is not None:
1934 _o = util.get_float(_o)
1935 if _o < 0.0:
1936 raise ValueError, "Invalid text offset length: %g" % _o
1937 _to = self.getPositionOffset()
1938 if ((_o is None and self.__poffset is not None) or
1939 (_o is not None and abs(_o - _to) > 1e-10)):
1940 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
1941 self.startChange('position_offset_changed')
1942 self.__poffset = _o
1943 self.endChange('position_offset_changed')
1944 self.__ds1.setBounds()
1945 self.__ds2.setBounds()
1946 self.calcDimValues()
1947 self.sendMessage('position_offset_changed', _to)
1948 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
1949 self.modified()
1951 position_offset = property(getPositionOffset, setPositionOffset, None,
1952 "Text offset from crossbar/crossarc distance.")
1954 def getDualModeOffset(self):
1955 """Get the offset for the dimension text when displaying two dimensions.
1957 getDualModeOffset()
1959 _dmo = self.__dmoffset
1960 if _dmo is None:
1961 _dmo = self.__dimstyle.getValue('DIM_DUAL_MODE_OFFSET')
1962 return _dmo
1964 def setDualModeOffset(self, offset=None):
1965 """Set the separation between the dimensions and the dual mode dimension divider.
1967 setDualModeOffset([offset])
1969 If this method is called without arguments, the dual mode offset
1970 length is set to the value given in the DimStyle.
1971 If the argument 'offset' is supplied, it should be a positive float value.
1973 if self.isLocked():
1974 raise RuntimeError, "Setting dual mode offset length not allowed - object locked."
1975 _o = offset
1976 if _o is not None:
1977 _o = util.get_float(_o)
1978 if _o < 0.0:
1979 raise ValueError, "Invalid dual mode offset length: %g" % _o
1980 _dmo = self.getDualModeOffset()
1981 if ((_o is None and self.__dmoffset is not None) or
1982 (_o is not None and abs(_o - _dmo) > 1e-10)):
1983 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
1984 self.startChange('dual_mode_offset_changed')
1985 self.__dmoffset = _o
1986 self.endChange('dual_mode_offset_changed')
1987 self.__ds1.setBounds()
1988 self.__ds2.setBounds()
1989 self.calcDimValues()
1990 self.sendMessage('dual_mode_offset_changed', _dmo)
1991 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
1992 self.modified()
1994 dual_mode_offset = property(getDualModeOffset, setDualModeOffset, None,
1995 "Text offset from dimension splitting bar when displaying two dimensions.")
1997 def getColor(self):
1998 """Return the color of the dimension lines.
2000 getColor()
2002 _col = self.__color
2003 if _col is None:
2004 _col = self.__dimstyle.getValue('DIM_COLOR')
2005 return _col
2007 def setColor(self, c=None):
2008 """Set the color of the dimension lines.
2010 setColor([c])
2012 Optional argument 'c' should be a Color instance. Calling this
2013 method without an argument sets the color to the value given
2014 in the DimStyle.
2016 if self.isLocked():
2017 raise RuntimeError, "Setting object color not allowed - object locked."
2018 _c = c
2019 if _c is not None:
2020 if not isinstance(_c, color.Color):
2021 raise TypeError, "Invalid color type: " + `type(_c)`
2022 _oc = self.getColor()
2023 if ((_c is None and self.__color is not None) or
2024 (_c is not None and _c != _oc)):
2025 self.startChange('color_changed')
2026 self.__color = _c
2027 self.endChange('color_changed')
2028 self.sendMessage('color_changed', _oc)
2029 self.modified()
2031 color = property(getColor, setColor, None, "Dimension Color")
2033 def getThickness(self):
2034 """Return the thickness of the dimension bars.
2036 getThickness()
2038 This method returns a float.
2040 _t = self.__thickness
2041 if _t is None:
2042 _t = self.__dimstyle.getValue('DIM_THICKNESS')
2043 return _t
2045 def setThickness(self, thickness=None):
2046 """Set the thickness of the dimension bars.
2048 setThickness([thickness])
2050 Optional argument 'thickness' should be a float value. Setting the
2051 thickness to 0 will display and print the lines with the thinnest
2052 value possible. Calling this method without arguments resets the
2053 thickness to the value defined in the DimStyle.
2055 if self.isLocked():
2056 raise RuntimeError, "Setting thickness not allowed - object locked."
2057 _t = thickness
2058 if _t is not None:
2059 _t = util.get_float(_t)
2060 if _t < 0.0:
2061 raise ValueError, "Invalid thickness: %g" % _t
2062 _ot = self.getThickness()
2063 if ((_t is None and self.__thickness is not None) or
2064 (_t is not None and abs(_t - _ot) > 1e-10)):
2065 self.startChange('thickness_changed')
2066 self.__thickness = _t
2067 self.endChange('thickness_changed')
2068 self.sendMessage('thickness_changed', _ot)
2069 self.modified()
2071 thickness = property(getThickness, setThickness, None,
2072 "Dimension bar thickness.")
2074 def getScale(self):
2075 """Return the Dimension scale factor.
2077 getScale()
2079 return self.__scale
2081 def setScale(self, scale=None):
2082 """Set the Dimension scale factor.
2084 setScale([scale])
2086 Optional argument 's' should be a float value greater than 0. If
2087 no argument is supplied the default scale factor of 1 is set.
2089 if self.isLocked():
2090 raise RuntimeError, "Setting scale not allowed - object locked."
2091 _s = scale
2092 if _s is None:
2093 _s = 1.0
2094 _s = util.get_float(_s)
2095 if not _s > 0.0:
2096 raise ValueError, "Invalid scale factor: %g" % _s
2097 _os = self.__scale
2098 if abs(_os - _s) > 1e-10:
2099 self.startChange('scale_changed')
2100 self.__scale = _s
2101 self.endChange('scale_changed')
2102 self.sendMessage('scale_changed', _os)
2103 self.modified()
2105 scale = property(getScale, setScale, None, "Dimension scale factor.")
2107 def getLocation(self):
2108 """Return the location of the dimensional text values.
2110 getLocation()
2112 return self.__dimloc
2114 def setLocation(self, x, y):
2115 """Set the location of the dimensional text values.
2117 setLocation(x, y)
2119 The 'x' and 'y' arguments should be float values. The text is
2120 centered around that point.
2122 if self.isLocked():
2123 raise RuntimeError, "Setting location not allowed - object locked."
2124 _x = util.get_float(x)
2125 _y = util.get_float(y)
2126 _ox, _oy = self.__dimloc
2127 if abs(_ox - _x) > 1e-10 or abs(_oy - _y) > 1e-10:
2128 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
2129 self.startChange('location_changed')
2130 self.__dimloc = (_x, _y)
2131 self.endChange('location_changed')
2132 self.__ds1.setBounds()
2133 self.__ds2.setBounds()
2134 self.calcDimValues()
2135 self.sendMessage('location_changed', _ox, _oy)
2136 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
2137 self.modified()
2139 location = property(getLocation, setLocation, None,
2140 "Dimension location")
2142 def move(self, dx, dy):
2143 """Move a Dimension.
2145 move(dx, dy)
2147 The first argument gives the x-coordinate displacement,
2148 and the second gives the y-coordinate displacement. Both
2149 values should be floats.
2151 if self.isLocked():
2152 raise RuntimeError, "Moving not allowed - object locked."
2153 _dx = util.get_float(dx)
2154 _dy = util.get_float(dy)
2155 if abs(_dx) > 1e-10 or abs(_dy) > 1e-10:
2156 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
2157 _x, _y = self.__dimloc
2158 self.startChange('location_changed')
2159 self.__dimloc = ((_x + _dx), (_y + _dy))
2160 self.endChange('location_changed')
2161 self.__ds1.setBounds()
2162 self.__ds2.setBounds()
2163 self.calcDimValues()
2164 self.sendMessage('location_changed', _x, _y)
2165 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
2166 self.modified()
2168 def getStyleValue(self, ds, opt):
2169 """Get the value in the DimStyle for some option
2171 getStyleValue(ds, opt)
2173 Argument 'ds' should be one of the DimString objects in
2174 the Dimension, and argument 'opt' should be a string.
2175 Valid choices for 'opt' are 'prefix', 'suffix', 'precision',
2176 'units', 'print_zero', 'print_decimal', 'font_family',
2177 'font_style', 'font_weight', 'size', 'color', 'angle',
2178 and 'alignment'.
2180 if not isinstance(ds, DimString):
2181 raise TypeError, "Invalid DimString type: " + `type(ds)`
2182 if not isinstance(opt, str):
2183 raise TypeError, "Invalid DimStyle option type: " + `type(opt)`
2184 _key = None
2185 if ds is self.__ds1:
2186 if opt == 'prefix':
2187 _key = 'DIM_PRIMARY_PREFIX'
2188 elif opt == 'suffix':
2189 _key = 'DIM_PRIMARY_SUFFIX'
2190 elif opt == 'precision':
2191 _key = 'DIM_PRIMARY_PRECISION'
2192 elif opt == 'units':
2193 _key = 'DIM_PRIMARY_UNITS'
2194 elif opt == 'print_zero':
2195 _key = 'DIM_PRIMARY_LEADING_ZERO'
2196 elif opt == 'print_decimal':
2197 _key = 'DIM_PRIMARY_TRAILING_DECIMAL'
2198 elif opt == 'font_family':
2199 _key = 'DIM_PRIMARY_FONT_FAMILY'
2200 elif opt == 'font_weight':
2201 _key = 'DIM_PRIMARY_FONT_WEIGHT'
2202 elif opt == 'font_style':
2203 _key = 'DIM_PRIMARY_FONT_STYLE'
2204 elif opt == 'size':
2205 _key = 'DIM_PRIMARY_TEXT_SIZE'
2206 elif opt == 'color':
2207 _key = 'DIM_PRIMARY_FONT_COLOR'
2208 elif opt == 'angle':
2209 _key = 'DIM_PRIMARY_TEXT_ANGLE'
2210 elif opt == 'alignment':
2211 _key = 'DIM_PRIMARY_TEXT_ALIGNMENT'
2212 else:
2213 raise ValueError, "Unexpected option: %s" % opt
2214 elif ds is self.__ds2:
2215 if opt == 'prefix':
2216 _key = 'DIM_SECONDARY_PREFIX'
2217 elif opt == 'suffix':
2218 _key = 'DIM_SECONDARY_SUFFIX'
2219 elif opt == 'precision':
2220 _key = 'DIM_SECONDARY_PRECISION'
2221 elif opt == 'units':
2222 _key = 'DIM_SECONDARY_UNITS'
2223 elif opt == 'print_zero':
2224 _key = 'DIM_SECONDARY_LEADING_ZERO'
2225 elif opt == 'print_decimal':
2226 _key = 'DIM_SECONDARY_TRAILING_DECIMAL'
2227 elif opt == 'font_family':
2228 _key = 'DIM_SECONDARY_FONT_FAMILY'
2229 elif opt == 'font_weight':
2230 _key = 'DIM_SECONDARY_FONT_WEIGHT'
2231 elif opt == 'font_style':
2232 _key = 'DIM_SECONDARY_FONT_STYLE'
2233 elif opt == 'size':
2234 _key = 'DIM_SECONDARY_TEXT_SIZE'
2235 elif opt == 'color':
2236 _key = 'DIM_SECONDARY_FONT_COLOR'
2237 elif opt == 'angle':
2238 _key = 'DIM_SECONDARY_TEXT_ANGLE'
2239 elif opt == 'alignment':
2240 _key = 'DIM_SECONDARY_TEXT_ALIGNMENT'
2241 else:
2242 raise ValueError, "Unexpected option: %s" % opt
2243 else:
2244 raise ValueError, "DimString not used in this Dimension: " + `ds`
2245 if _key is None:
2246 raise ValueError, "Unexpected option: %s" % opt
2247 return self.__dimstyle.getValue(_key)
2249 def getDimensions(self, dimlen):
2250 """Return the formatted dimensional values.
2252 getDimensions(dimlen)
2254 The argument 'dimlen' should be the length in millimeters.
2256 This method returns a list of the primary and secondary
2257 dimensional values.
2259 _dl = util.get_float(dimlen)
2260 dims = []
2261 dims.append(self.__ds1.formatDimension(_dl))
2262 dims.append(self.__ds2.formatDimension(_dl))
2263 return dims
2265 def calcDimValues(self, allpts=True):
2266 """Recalculate the values for dimensional display
2268 calcDimValues([allpts])
2270 This method is meant to be overriden by subclasses.
2272 pass
2274 def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
2275 """Return whether or not a Dimension exists with a region.
2277 isRegion(xmin, ymin, xmax, ymax[, fully])
2279 The first four arguments define the boundary. The optional
2280 fifth argument 'fully' indicates whether or not the Dimension
2281 must be completely contained within the region or just pass
2282 through it.
2284 This method should be overriden in classes derived from Dimension.
2286 return False
2288 def getBounds(self):
2289 """Return the minimal and maximal locations of the dimension
2291 getBounds()
2293 This method returns a tuple of four values - xmin, ymin, xmax, ymax.
2294 These values give the mimimum and maximum coordinates of the dimension
2295 object.
2297 This method should be overriden in classes derived from Dimension.
2299 _xmin = _ymin = -float(sys.maxint)
2300 _xmax = _ymax = float(sys.maxint)
2301 return _xmin, _ymin, _xmax, _ymax
2303 def copyDimValues(self, dim):
2304 """This method adjusts one Dimension to match another Dimension
2306 copyDimValues(dim)
2308 Argument 'dim' must be a Dimension instance
2310 if not isinstance(dim, Dimension):
2311 raise TypeError, "Invalid Dimension type: " + `type(dim)`
2312 self.setDimStyle(dim.getDimStyle())
2313 self.setOffset(dim.getOffset())
2314 self.setExtension(dim.getExtension())
2315 self.setEndpointType(dim.getEndpointType())
2316 self.setEndpointSize(dim.getEndpointSize())
2317 self.setColor(dim.getColor())
2318 self.setThickness(dim.getThickness())
2319 self.setDualDimMode(dim.getDualDimMode())
2320 self.setPositionOffset(dim.getPositionOffset())
2321 self.setDualModeOffset(dim.getDualModeOffset())
2323 _ds1, _ds2 = dim.getDimstrings()
2325 _ds = self.__ds1
2326 _ds.setTextStyle(_ds1.getTextStyle())
2327 _ds.setPrefix(_ds1.getPrefix())
2328 _ds.setSuffix(_ds1.getSuffix())
2329 _ds.setPrecision(_ds1.getPrecision())
2330 _ds.setUnits(_ds1.getUnits())
2331 _ds.setPrintZero(_ds1.getPrintZero())
2332 _ds.setPrintDecimal(_ds1.getPrintDecimal())
2333 _ds.setFamily(_ds1.getFamily())
2334 _ds.setWeight(_ds1.getWeight())
2335 _ds.setStyle(_ds1.getStyle())
2336 _ds.setColor(_ds1.getColor())
2337 _ds.setSize(_ds1.getSize())
2338 _ds.setAngle(_ds1.getAngle())
2339 _ds.setAlignment(_ds1.getAlignment())
2341 _ds = self.__ds2
2342 _ds.setTextStyle(_ds2.getTextStyle())
2343 _ds.setPrefix(_ds2.getPrefix())
2344 _ds.setSuffix(_ds2.getSuffix())
2345 _ds.setPrecision(_ds2.getPrecision())
2346 _ds.setUnits(_ds2.getUnits())
2347 _ds.setPrintZero(_ds2.getPrintZero())
2348 _ds.setPrintDecimal(_ds2.getPrintDecimal())
2349 _ds.setFamily(_ds2.getFamily())
2350 _ds.setWeight(_ds2.getWeight())
2351 _ds.setStyle(_ds2.getStyle())
2352 _ds.setColor(_ds2.getColor())
2353 _ds.setSize(_ds2.getSize())
2354 _ds.setAngle(_ds2.getAngle())
2355 _ds.setAlignment(_ds2.getAlignment())
2357 def __dimstringChangePending(self, p, *args):
2358 _alen = len(args)
2359 if _alen < 1:
2360 raise ValueError, "Invalid argument count: %d" % _alen
2361 _arg = args[0]
2362 if _arg == 'moved':
2363 self.startChange(_arg)
2364 elif (_arg == 'textstyle_changed' or
2365 _arg == 'font_family_changed' or
2366 _arg == 'font_style_changed' or
2367 _arg == 'font_weight_changed' or
2368 _arg == 'font_color_changed' or
2369 _arg == 'text_size_changed' or
2370 _arg == 'text_angle_changed' or
2371 _arg == 'text_alignment_changed' or
2372 _arg == 'prefix_changed' or
2373 _arg == 'suffix_changed' or
2374 _arg == 'units_changed' or
2375 _arg == 'precision_changed' or
2376 _arg == 'print_zero_changed' or
2377 _arg == 'print_decimal_changed'):
2378 self.startChange('dimstring_changed')
2379 else:
2380 pass
2382 def __dimstringChangeComplete(self, p, *args):
2383 _alen = len(args)
2384 if _alen < 1:
2385 raise ValueError, "Invalid argument count: %d" % _alen
2386 _arg = args[0]
2387 if _arg == 'moved':
2388 self.endChanged(_arg)
2389 elif (_arg == 'textstyle_changed' or
2390 _arg == 'font_family_changed' or
2391 _arg == 'font_style_changed' or
2392 _arg == 'font_weight_changed' or
2393 _arg == 'font_color_changed' or
2394 _arg == 'text_size_changed' or
2395 _arg == 'text_angle_changed' or
2396 _arg == 'text_alignment_changed' or
2397 _arg == 'prefix_changed' or
2398 _arg == 'suffix_changed' or
2399 _arg == 'units_changed' or
2400 _arg == 'precision_changed' or
2401 _arg == 'print_zero_changed' or
2402 _arg == 'print_decimal_changed'):
2403 self.endChange('dimstring_changed')
2404 else:
2405 pass
2407 def sendsMessage(self, m):
2408 if m in Dimension.__messages:
2409 return True
2410 return super(Dimension, self).sendsMessage(m)
2413 # class stuff for dimension styles
2416 class DimStyle(object):
2417 """A class storing preferences for Dimensions
2419 The DimStyle class stores a set of dimension parameters
2420 that will be used when creating dimensions when the
2421 particular style is active.
2423 A DimStyle object has the following methods:
2425 getName(): Return the name of the DimStyle.
2426 getOption(): Return a single value in the DimStyle.
2427 getOptions(): Return all the options in the DimStyle.
2428 getValue(): Return the value of one of the DimStyle options.
2430 getOption() and getValue() are synonymous.
2432 The DimStyle class has the following classmethods:
2434 getDimStyleOptions(): Return the options defining a DimStyle.
2435 getDimStyleDefaultValue(): Return the default value for a DimStyle option.
2439 # the default values for the DimStyle class
2441 __deftextcolor = color.Color('#ffffff')
2442 __defdimcolor = color.Color(255,165,0)
2443 __defaults = {
2444 'DIM_PRIMARY_FONT_FAMILY' : 'Sans',
2445 'DIM_PRIMARY_TEXT_SIZE' : 1.0,
2446 'DIM_PRIMARY_FONT_WEIGHT' : text.TextStyle.FONT_NORMAL,
2447 'DIM_PRIMARY_FONT_STYLE' : text.TextStyle.FONT_NORMAL,
2448 'DIM_PRIMARY_FONT_COLOR' : __deftextcolor,
2449 'DIM_PRIMARY_TEXT_ANGLE' : 0.0,
2450 'DIM_PRIMARY_TEXT_ALIGNMENT' : text.TextStyle.ALIGN_CENTER,
2451 'DIM_PRIMARY_PREFIX' : u'',
2452 'DIM_PRIMARY_SUFFIX' : u'',
2453 'DIM_PRIMARY_PRECISION' : 3,
2454 'DIM_PRIMARY_UNITS' : units.MILLIMETERS,
2455 'DIM_PRIMARY_LEADING_ZERO' : True,
2456 'DIM_PRIMARY_TRAILING_DECIMAL' : True,
2457 'DIM_SECONDARY_FONT_FAMILY' : 'Sans',
2458 'DIM_SECONDARY_TEXT_SIZE' : 1.0,
2459 'DIM_SECONDARY_FONT_WEIGHT' : text.TextStyle.FONT_NORMAL,
2460 'DIM_SECONDARY_FONT_STYLE' : text.TextStyle.FONT_NORMAL,
2461 'DIM_SECONDARY_FONT_COLOR' : __deftextcolor,
2462 'DIM_SECONDARY_TEXT_ANGLE' : 0.0,
2463 'DIM_SECONDARY_TEXT_ALIGNMENT' : text.TextStyle.ALIGN_CENTER,
2464 'DIM_SECONDARY_PREFIX' : u'',
2465 'DIM_SECONDARY_SUFFIX' : u'',
2466 'DIM_SECONDARY_PRECISION' : 3,
2467 'DIM_SECONDARY_UNITS' : units.MILLIMETERS,
2468 'DIM_SECONDARY_LEADING_ZERO' : True,
2469 'DIM_SECONDARY_TRAILING_DECIMAL' : True,
2470 'DIM_OFFSET' : 1.0,
2471 'DIM_EXTENSION' : 1.0,
2472 'DIM_COLOR' : __defdimcolor,
2473 'DIM_THICKNESS' : 0.0,
2474 'DIM_POSITION' : Dimension.DIM_TEXT_POS_SPLIT,
2475 'DIM_ENDPOINT' : Dimension.DIM_ENDPT_NONE,
2476 'DIM_ENDPOINT_SIZE' : 1.0,
2477 'DIM_DUAL_MODE' : False,
2478 'DIM_POSITION_OFFSET' : 0.0,
2479 'DIM_DUAL_MODE_OFFSET' : 1.0,
2480 'RADIAL_DIM_PRIMARY_PREFIX' : u'',
2481 'RADIAL_DIM_PRIMARY_SUFFIX' : u'',
2482 'RADIAL_DIM_SECONDARY_PREFIX' : u'',
2483 'RADIAL_DIM_SECONDARY_SUFFIX' : u'',
2484 'RADIAL_DIM_DIA_MODE' : False,
2485 'ANGULAR_DIM_PRIMARY_PREFIX' : u'',
2486 'ANGULAR_DIM_PRIMARY_SUFFIX' : u'',
2487 'ANGULAR_DIM_SECONDARY_PREFIX' : u'',
2488 'ANGULAR_DIM_SECONDARY_SUFFIX' : u'',
2491 def __init__(self, name, keywords={}):
2492 """Instantiate a DimStyle object.
2494 ds = DimStyle(name, keywords)
2496 The argument 'name' should be a unicode name, and the
2497 'keyword' argument should be a dict. The keys should
2498 be the same keywords used to set option values, such
2499 as DIM_OFFSET, DIM_EXTENSION, etc, and the value corresponding
2500 to each key should be set appropriately.
2502 super(DimStyle, self).__init__()
2503 _n = name
2504 if not isinstance(_n, types.StringTypes):
2505 raise TypeError, "Invalid DimStyle name type: "+ `type(_n)`
2506 if isinstance(_n, str):
2507 _n = unicode(_n)
2508 if not isinstance(keywords, dict):
2509 raise TypeError, "Invalid keywords argument type: " + `type(keywords)`
2510 from PythonCAD.Generic.options import test_option
2511 self.__opts = baseobject.ConstDict(str)
2512 self.__name = _n
2513 for _kw in keywords:
2514 if _kw not in DimStyle.__defaults:
2515 raise KeyError, "Unknown DimStyle keyword: " + _kw
2516 _val = keywords[_kw]
2517 _valid = test_option(_kw, _val)
2518 self.__opts[_kw] = _val
2520 def __eq__(self, obj):
2521 """Test a DimStyle object for equality with another DimStyle.
2523 if not isinstance(obj, DimStyle):
2524 return False
2525 if obj is self:
2526 return True
2527 if self.__name != obj.getName():
2528 return False
2529 _val = True
2530 for _key in DimStyle.__defaults.keys():
2531 _sv = self.getOption(_key)
2532 _ov = obj.getOption(_key)
2533 if ((_key == 'DIM_PRIMARY_TEXT_SIZE') or
2534 (_key == 'DIM_PRIMARY_TEXT_ANGLE') or
2535 (_key == 'DIM_SECONDARY_TEXT_SIZE') or
2536 (_key == 'DIM_SECONDARY_TEXT_ANGLE') or
2537 (_key == 'DIM_OFFSET') or
2538 (_key == 'DIM_EXTENSION') or
2539 (_key == 'DIM_THICKNESS') or
2540 (_key == 'DIM_ENDPOINT_SIZE') or
2541 (_key == 'DIM_POSITION_OFFSET') or
2542 (_key == 'DIM_DUAL_MODE_OFFSET')):
2543 if abs(_sv - _ov) > 1e-10:
2544 _val = False
2545 else:
2546 if _sv != _ov:
2547 _val = False
2548 if _val is False:
2549 break
2550 return _val
2552 def __ne__(self, obj):
2553 """Test a DimStyle object for inequality with another DimStyle.
2555 return not self == obj
2557 def getDimStyleOptions(cls):
2558 """Return the options used to define a DimStyle instance.
2560 getDimStyleOptions()
2562 This classmethod returns a list of strings.
2564 return cls.__defaults.keys()
2566 getDimStyleOptions = classmethod(getDimStyleOptions)
2568 def getDimStyleDefaultValue(cls, key):
2569 """Return the default value for a DimStyle option.
2571 getDimStyleValue(key)
2573 Argument 'key' must be one of the options given in getDimStyleOptions().
2575 return cls.__defaults[key]
2577 getDimStyleDefaultValue = classmethod(getDimStyleDefaultValue)
2579 def getName(self):
2580 """Return the name of the DimStyle.
2582 getName()
2584 return self.__name
2586 name = property(getName, None, None, "DimStyle name.")
2588 def getKeys(self):
2589 """Return the non-default options within the DimStyle.
2591 getKeys()
2593 return self.__opts.keys()
2595 def getOptions(self):
2596 """Return all the options stored within the DimStyle.
2598 getOptions()
2600 _keys = self.__opts.keys()
2601 for _key in DimStyle.__defaults:
2602 if _key not in self.__opts:
2603 _keys.append(_key)
2604 return _keys
2606 def getOption(self, key):
2607 """Return the value of a particular option in the DimStyle.
2609 getOption(key)
2611 The key should be one of the strings returned from getOptions. If
2612 there is no value found in the DimStyle for the key, the value None
2613 is returned.
2615 if key in self.__opts:
2616 _val = self.__opts[key]
2617 elif key in DimStyle.__defaults:
2618 _val = DimStyle.__defaults[key]
2619 else:
2620 raise KeyError, "Unexpected DimStyle keyword: '%s'" % key
2621 return _val
2623 def getValue(self, key):
2624 """Return the value of a particular option in the DimStyle.
2626 getValue(key)
2628 The key should be one of the strings returned from getOptions. This
2629 method raises a KeyError exception if the key is not found.
2632 if key in self.__opts:
2633 _val = self.__opts[key]
2634 elif key in DimStyle.__defaults:
2635 _val = DimStyle.__defaults[key]
2636 else:
2637 raise KeyError, "Unexpected DimStyle keyword: '%s'" % key
2638 return _val
2640 def getValues(self):
2641 """Return values comprising the DimStyle.
2643 getValues()
2645 _vals = {}
2646 _vals['name'] = self.__name
2647 for _opt in self.__opts:
2648 _val = self.__opts[_opt]
2649 if ((_opt == 'DIM_PRIMARY_FONT_COLOR') or
2650 (_opt == 'DIM_SECONDARY_FONT_COLOR') or
2651 (_opt == 'DIM_COLOR')):
2652 _vals[_opt] = _val.getColors()
2653 else:
2654 _vals[_opt] = _val
2655 return _vals
2657 class LinearDimension(Dimension):
2658 """A class for Linear dimensions.
2660 The LinearDimension class is derived from the Dimension
2661 class, so it shares all of those methods and attributes.
2662 A LinearDimension should be used to display the absolute
2663 distance between two Point objects.
2665 A LinearDimension object has the following methods:
2667 {get/set}P1(): Get/Set the first Point for the LinearDimension.
2668 {get/set}P2(): Get/Set the second Point for the LinearDimension.
2669 getDimPoints(): Return the two Points used in this dimension.
2670 getDimLayers(): Return the two Layers holding the Points.
2671 getDimXPoints(): Get the x-coordinates of the dimension bar positions.
2672 getDimYPoints(): Get the y-coordinates of the dimension bar positions.
2673 getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
2674 calcMarkerPoints(): Calculate the coordinates of any dimension marker objects.
2677 __messages = {
2678 'point_changed' : True,
2681 def __init__(self, p1, p2, x, y, ds=None, **kw):
2682 """Instantiate a LinearDimension object.
2684 ldim = LinearDimension(p1, p2, x, y, ds)
2686 p1: A Point contained in a Layer
2687 p2: A Point contained in a Layer
2688 x: The x-coordinate of the dimensional text
2689 y: The y-coordinate of the dimensional text
2690 ds: The DimStyle used for this Dimension.
2692 if not isinstance(p1, point.Point):
2693 raise TypeError, "Invalid point type: " + `type(p1)`
2694 if p1.getParent() is None:
2695 raise ValueError, "Point P1 not stored in a Layer!"
2696 if not isinstance(p2, point.Point):
2697 raise TypeError, "Invalid point type: " + `type(p2)`
2698 if p2.getParent() is None:
2699 raise ValueError, "Point P2 not stored in a Layer!"
2700 super(LinearDimension, self).__init__(x, y, ds, **kw)
2701 self.__p1 = p1
2702 self.__p2 = p2
2703 self.__bar1 = DimBar()
2704 self.__bar2 = DimBar()
2705 self.__crossbar = DimCrossbar()
2706 p1.storeUser(self)
2707 p1.connect('moved', self.__movePoint)
2708 p1.connect('change_pending', self.__pointChangePending)
2709 p1.connect('change_complete', self.__pointChangeComplete)
2710 p2.storeUser(self)
2711 p2.connect('moved', self.__movePoint)
2712 p2.connect('change_pending', self.__pointChangePending)
2713 p2.connect('change_complete', self.__pointChangeComplete)
2714 self.calcDimValues()
2716 def __eq__(self, ldim):
2717 """Test two LinearDimension objects for equality.
2719 if not isinstance(ldim, LinearDimension):
2720 return False
2721 _lp1 = self.__p1.getParent()
2722 _lp2 = self.__p2.getParent()
2723 _p1, _p2 = ldim.getDimPoints()
2724 _l1 = _p1.getParent()
2725 _l2 = _p2.getParent()
2726 if (_lp1 is _l1 and
2727 _lp2 is _l2 and
2728 self.__p1 == _p1 and
2729 self.__p2 == _p2):
2730 return True
2731 if (_lp1 is _l2 and
2732 _lp2 is _l1 and
2733 self.__p1 == _p2 and
2734 self.__p2 == _p1):
2735 return True
2736 return False
2738 def __ne__(self, ldim):
2739 """Test two LinearDimension objects for equality.
2741 if not isinstance(ldim, LinearDimension):
2742 return True
2743 _lp1 = self.__p1.getParent()
2744 _lp2 = self.__p2.getParent()
2745 _p1, _p2 = ldim.getDimPoints()
2746 _l1 = _p1.getParent()
2747 _p2 = self.__p2
2748 _l2 = _p2.getParent()
2749 if (_lp1 is _l1 and
2750 _lp2 is _l2 and
2751 self.__p1 == _p1 and
2752 self.__p2 == _p2):
2753 return False
2754 if (_lp1 is _l2 and
2755 _lp2 is _l1 and
2756 self.__p1 == _p2 and
2757 self.__p2 == _p1):
2758 return False
2759 return True
2761 def finish(self):
2762 self.__p1.disconnect(self)
2763 self.__p1.freeUser(self)
2764 self.__p2.disconnect(self)
2765 self.__p2.freeUser(self)
2766 self.__bar1 = self.__bar2 = self.__crossbar = None
2767 self.__p1 = self.__p2 = None
2768 super(LinearDimension, self).finish()
2770 def getValues(self):
2771 """Return values comprising the LinearDimension.
2773 getValues()
2775 This method extends the Dimension::getValues() method.
2777 _data = super(LinearDimension, self).getValues()
2778 _data.setValue('type', 'ldim')
2779 _data.setValue('p1', self.__p1.getID())
2780 _layer = self.__p1.getParent()
2781 _data.setValue('l1', _layer.getID())
2782 _data.setValue('p2', self.__p2.getID())
2783 _layer = self.__p2.getParent()
2784 _data.setValue('l2', _layer.getID())
2785 return _data
2787 def getP1(self):
2788 """Return the first Point of a LinearDimension.
2790 getP1()
2792 return self.__p1
2794 def setP1(self, p):
2795 """Set the first Point of a LinearDimension.
2797 setP1(p)
2799 There is one required argument for this method:
2801 p: A Point contained in a Layer
2803 if self.isLocked():
2804 raise RuntimeError, "Setting point not allowed - object locked."
2805 if not isinstance(p, point.Point):
2806 raise TypeError, "Invalid point type: " + `type(p)`
2807 if p.getParent() is None:
2808 raise ValueError, "Point not stored in a Layer!"
2809 _pt = self.__p1
2810 if _pt is not p:
2811 _pt.disconnect(self)
2812 _pt.freeUser(self)
2813 self.startChange('point_changed')
2814 self.__p1 = p
2815 self.endChange('point_changed')
2816 self.sendMessage('point_changed', _pt, p)
2817 p.storeUser(self)
2818 p.connect('moved', self.__movePoint)
2819 p.connect('change_pending', self.__pointChangePending)
2820 p.connect('change_complete', self.__pointChangeComplete)
2821 if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10:
2822 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
2823 self.calcDimValues()
2824 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
2825 self.modified()
2827 p1 = property(getP1, None, None, "Dimension first point.")
2829 def getP2(self):
2830 """Return the second point of a LinearDimension.
2832 getP2()
2834 return self.__p2
2836 def setP2(self, p):
2837 """Set the second Point of a LinearDimension.
2839 setP2(p)
2841 There is one required argument for this method:
2843 p: A Point contained in a Layer
2845 if self.isLocked():
2846 raise RuntimeError, "Setting point not allowed - object locked."
2847 if not isinstance(p, point.Point):
2848 raise TypeError, "Invalid point type: " + `type(p)`
2849 if p.getParent() is None:
2850 raise ValueError, "Point not stored in a Layer!"
2851 _pt = self.__p2
2852 if _pt is not p:
2853 _pt.disconnect(self)
2854 _pt.freeUser(self)
2855 self.startChange('point_changed')
2856 self.__p2 = p
2857 self.endChange('point_changed')
2858 self.sendMessage('point_changed', _pt, p)
2859 p.storeUser(self)
2860 p.connect('moved', self.__movePoint)
2861 p.connect('change_pending', self.__pointChangePending)
2862 p.connect('change_complete', self.__pointChangeComplete)
2863 if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10:
2864 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
2865 self.calcDimValues()
2866 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
2867 self.modified()
2869 p2 = property(getP2, None, None, "Dimension second point.")
2871 def getDimPoints(self):
2872 """Return both points used in the LinearDimension.
2874 getDimPoints()
2876 The two points are returned in a tuple.
2878 return self.__p1, self.__p2
2880 def getDimBars(self):
2881 """Return the dimension boundary bars.
2883 getDimBars()
2885 return self.__bar1, self.__bar2
2887 def getDimCrossbar(self):
2888 """Return the dimension crossbar.
2890 getDimCrossbar()
2892 return self.__crossbar
2894 def getDimLayers(self):
2895 """Return both layers used in the LinearDimension.
2897 getDimLayers()
2899 The two layers are returned in a tuple.
2901 _l1 = self.__p1.getParent()
2902 _l2 = self.__p2.getParent()
2903 return _l1, _l2
2905 def calculate(self):
2906 """Determine the length of this LinearDimension.
2908 calculate()
2910 return self.__p1 - self.__p2
2912 def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
2913 """Return whether or not a LinearDimension exists within a region.
2915 isRegion(xmin, ymin, xmax, ymax[, fully])
2917 The four arguments define the boundary of an area, and the
2918 function returns True if the LinearDimension lies within that
2919 area. If the optional argument fully is used and is True,
2920 then the dimension points and the location of the dimension
2921 text must lie within the boundary. Otherwise, the function
2922 returns False.
2924 _xmin = util.get_float(xmin)
2925 _ymin = util.get_float(ymin)
2926 _xmax = util.get_float(xmax)
2927 if _xmax < _xmin:
2928 raise ValueError, "Illegal values: xmax < xmin"
2929 _ymax = util.get_float(ymax)
2930 if _ymax < _ymin:
2931 raise ValueError, "Illegal values: ymax < ymin"
2932 util.test_boolean(fully)
2933 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
2934 if ((_dxmin > _xmax) or
2935 (_dymin > _ymax) or
2936 (_dxmax < _xmin) or
2937 (_dymax < _ymin)):
2938 return False
2939 if fully:
2940 if ((_dxmin > _xmin) and
2941 (_dymin > _ymin) and
2942 (_dxmax < _xmax) and
2943 (_dymax < _ymax)):
2944 return True
2945 return False
2946 _dx, _dy = self.getLocation()
2947 if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text
2948 return True
2950 # bar at p1
2952 _ep1, _ep2 = self.__bar1.getEndpoints()
2953 _x1, _y1 = _ep1
2954 _x2, _y2 = _ep2
2955 if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax):
2956 return True
2958 # bar at p2
2960 _ep1, _ep2 = self.__bar2.getEndpoints()
2961 _x1, _y1 = _ep1
2962 _x2, _y2 = _ep2
2963 if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax):
2964 return True
2966 # crossbar
2968 _ep1, _ep2 = self.__crossbar.getEndpoints()
2969 _x1, _y1 = _ep1
2970 _x2, _y2 = _ep2
2971 return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax)
2973 def calcDimValues(self, allpts=True):
2974 """Recalculate the values for dimensional display.
2976 calcDimValues([allpts])
2978 This method calculates where the points for the dimension
2979 bars and crossbar are located. The argument 'allpts' is
2980 optional. By default it is True. If the argument is set to
2981 False, then the coordinates of the dimension marker points
2982 will not be calculated.
2984 _allpts = allpts
2985 util.test_boolean(_allpts)
2986 _p1, _p2 = self.getDimPoints()
2987 _bar1 = self.__bar1
2988 _bar2 = self.__bar2
2989 _crossbar = self.__crossbar
2990 _p1x, _p1y = _p1.getCoords()
2991 _p2x, _p2y = _p2.getCoords()
2992 _dx, _dy = self.getLocation()
2993 _offset = self.getOffset()
2994 _ext = self.getExtension()
2996 # see comp.graphics.algorithms.faq section on calcuating
2997 # the distance between a point and line for info about
2998 # the following equations ...
3000 _dpx = _p2x - _p1x
3001 _dpy = _p2y - _p1y
3002 _rnum = ((_dx - _p1x) * _dpx) + ((_dy - _p1y) * _dpy)
3003 _snum = ((_p1y - _dy) * _dpx) - ((_p1x - _dx) * _dpy)
3004 _den = pow(_dpx, 2) + pow(_dpy, 2)
3005 _r = _rnum/_den
3006 _s = _snum/_den
3007 _sep = abs(_s) * math.sqrt(_den)
3008 if abs(_dpx) < 1e-10: # vertical
3009 if _p2y > _p1y:
3010 _slope = math.pi/2.0
3011 else:
3012 _slope = -math.pi/2.0
3013 elif abs(_dpy) < 1e-10: # horizontal
3014 if _p2x > _p1x:
3015 _slope = 0.0
3016 else:
3017 _slope = -math.pi
3018 else:
3019 _slope = math.atan2(_dpy, _dpx)
3020 if _s < 0.0: # dim point left of p1-p2 line
3021 _angle = _slope + (math.pi/2.0)
3022 else: # dim point right of p1-p2 line (or on it)
3023 _angle = _slope - (math.pi/2.0)
3024 _sin_angle = math.sin(_angle)
3025 _cos_angle = math.cos(_angle)
3026 _x = _p1x + (_offset * _cos_angle)
3027 _y = _p1y + (_offset * _sin_angle)
3028 _bar1.setFirstEndpoint(_x, _y)
3029 if _r < 0.0:
3030 _px = _p1x + (_r * _dpx)
3031 _py = _p1y + (_r * _dpy)
3032 _x = _px + (_sep * _cos_angle)
3033 _y = _py + (_sep * _sin_angle)
3034 else:
3035 _x = _p1x + (_sep * _cos_angle)
3036 _y = _p1y + (_sep * _sin_angle)
3037 _crossbar.setFirstEndpoint(_x, _y)
3038 _x = _p1x + (_sep * _cos_angle)
3039 _y = _p1y + (_sep * _sin_angle)
3040 _crossbar.setFirstCrossbarPoint(_x, _y)
3041 _x = _p1x + ((_sep + _ext) * _cos_angle)
3042 _y = _p1y + ((_sep + _ext) * _sin_angle)
3043 _bar1.setSecondEndpoint(_x, _y)
3044 _x = _p2x + (_offset * _cos_angle)
3045 _y = _p2y + (_offset * _sin_angle)
3046 _bar2.setFirstEndpoint(_x, _y)
3047 if _r > 1.0:
3048 _px = _p1x + (_r * _dpx)
3049 _py = _p1y + (_r * _dpy)
3050 _x = _px + (_sep * _cos_angle)
3051 _y = _py + (_sep * _sin_angle)
3052 else:
3053 _x = _p2x + (_sep * _cos_angle)
3054 _y = _p2y + (_sep * _sin_angle)
3055 _crossbar.setSecondEndpoint(_x, _y)
3056 _x = _p2x + (_sep * _cos_angle)
3057 _y = _p2y + (_sep * _sin_angle)
3058 _crossbar.setSecondCrossbarPoint(_x, _y)
3059 _x = _p2x + ((_sep + _ext) * _cos_angle)
3060 _y = _p2y + ((_sep + _ext) * _sin_angle)
3061 _bar2.setSecondEndpoint(_x, _y)
3062 if _allpts:
3063 self.calcMarkerPoints()
3065 def calcMarkerPoints(self):
3066 """Calculate and store the dimension endpoint markers coordinates.
3068 calcMarkerPoints()
3070 _type = self.getEndpointType()
3071 _crossbar = self.__crossbar
3072 _crossbar.clearMarkerPoints()
3073 if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE:
3074 return
3075 _size = self.getEndpointSize()
3076 _p1, _p2 = _crossbar.getCrossbarPoints()
3077 _x1, _y1 = _p1
3078 _x2, _y2 = _p2
3079 # print "x1: %g" % _x1
3080 # print "y1: %g" % _y1
3081 # print "x2: %g" % _x2
3082 # print "y2: %g" % _y2
3083 _sine, _cosine = _crossbar.getSinCosValues()
3084 if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW:
3085 _height = _size/5.0
3086 # p1 -> (x,y) = (size, _height)
3087 _mx = (_cosine * _size - _sine * _height) + _x1
3088 _my = (_sine * _size + _cosine * _height) + _y1
3089 _crossbar.storeMarkerPoint(_mx, _my)
3090 # p2 -> (x,y) = (size, -_height)
3091 _mx = (_cosine * _size - _sine *(-_height)) + _x1
3092 _my = (_sine * _size + _cosine *(-_height)) + _y1
3093 _crossbar.storeMarkerPoint(_mx, _my)
3094 # p3 -> (x,y) = (-size, _height)
3095 _mx = (_cosine * (-_size) - _sine * _height) + _x2
3096 _my = (_sine * (-_size) + _cosine * _height) + _y2
3097 _crossbar.storeMarkerPoint(_mx, _my)
3098 # p4 -> (x,y) = (-size, -_height)
3099 _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2
3100 _my = (_sine * (-_size) + _cosine *(-_height)) + _y2
3101 _crossbar.storeMarkerPoint(_mx, _my)
3102 elif _type == Dimension.DIM_ENDPT_SLASH:
3103 _angle = 30.0 * _dtr # slope of slash
3104 _height = 0.5 * _size * math.sin(_angle)
3105 _length = 0.5 * _size * math.cos(_angle)
3106 # p1 -> (x,y) = (-_length, -_height)
3107 _sx1 = (_cosine * (-_length) - _sine * (-_height))
3108 _sy1 = (_sine * (-_length) + _cosine * (-_height))
3109 # p2 -> (x,y) = (_length, _height)
3110 _sx2 = (_cosine * _length - _sine * _height)
3111 _sy2 = (_sine * _length + _cosine * _height)
3113 # shift the calculate based on the location of the
3114 # marker point
3116 _mx = _sx1 + _x2
3117 _my = _sy1 + _y2
3118 _crossbar.storeMarkerPoint(_mx, _my)
3119 _mx = _sx2 + _x2
3120 _my = _sy2 + _y2
3121 _crossbar.storeMarkerPoint(_mx, _my)
3122 _mx = _sx1 + _x1
3123 _my = _sy1 + _y1
3124 _crossbar.storeMarkerPoint(_mx, _my)
3125 _mx = _sx2 + _x1
3126 _my = _sy2 + _y1
3127 _crossbar.storeMarkerPoint(_mx, _my)
3128 else:
3129 raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)
3131 def mapCoords(self, x, y, tol=tolerance.TOL):
3132 """Test an x/y coordinate pair if it could lay on the dimension.
3134 mapCoords(x, y[, tol])
3136 This method has two required parameters:
3138 x: The x-coordinate
3139 y: The y-coordinate
3141 These should both be float values.
3143 There is an optional third parameter tol giving the maximum distance
3144 from the dimension bars that the x/y coordinates may lie.
3146 _x = util.get_float(x)
3147 _y = util.get_float(y)
3148 _t = tolerance.toltest(tol)
3149 _ep1, _ep2 = self.__bar1.getEndpoints()
3151 # test p1 bar
3153 _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)
3154 if _mp is not None:
3155 return _mp
3157 # test p2 bar
3159 _ep1, _ep2 = self.__bar2.getEndpoints()
3160 _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)
3161 if _mp is not None:
3162 return _mp
3164 # test crossbar
3166 _ep1, _ep2 = self.__crossbar.getEndpoints()
3167 return util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)
3169 def onDimension(self, x, y, tol=tolerance.TOL):
3170 return self.mapCoords(x, y, tol) is not None
3172 def getBounds(self):
3173 """Return the minimal and maximal locations of the dimension
3175 getBounds()
3177 This method overrides the Dimension::getBounds() method
3179 _dx, _dy = self.getLocation()
3180 _dxpts = []
3181 _dypts = []
3182 _ep1, _ep2 = self.__bar1.getEndpoints()
3183 _dxpts.append(_ep1[0])
3184 _dypts.append(_ep1[1])
3185 _dxpts.append(_ep2[0])
3186 _dypts.append(_ep2[1])
3187 _ep1, _ep2 = self.__bar2.getEndpoints()
3188 _dxpts.append(_ep1[0])
3189 _dypts.append(_ep1[1])
3190 _dxpts.append(_ep2[0])
3191 _dypts.append(_ep2[1])
3192 _ep1, _ep2 = self.__crossbar.getEndpoints()
3193 _dxpts.append(_ep1[0])
3194 _dypts.append(_ep1[1])
3195 _dxpts.append(_ep2[0])
3196 _dypts.append(_ep2[1])
3197 _xmin = min(_dx, min(_dxpts))
3198 _ymin = min(_dy, min(_dypts))
3199 _xmax = max(_dx, max(_dxpts))
3200 _ymax = max(_dy, max(_dypts))
3201 return _xmin, _ymin, _xmax, _ymax
3203 def clone(self):
3204 _p1 = self.__p1
3205 _p2 = self.__p2
3206 _x, _y = self.getLocation()
3207 _ds = self.getDimStyle()
3208 _ldim = LinearDimension(_p1, _p2, _x, _y, _ds)
3209 _ldim.copyDimValues(self)
3210 return _ldim
3212 def __pointChangePending(self, p, *args):
3213 _alen = len(args)
3214 if _alen < 1:
3215 raise ValueError, "Invalid argument count: %d" % _alen
3216 if args[0] == 'moved':
3217 self.startChange('moved')
3219 def __pointChangeComplete(self, p, *args):
3220 _alen = len(args)
3221 if _alen < 1:
3222 raise ValueError, "Invalid argument count: %d" % _alen
3223 if args[0] == 'moved':
3224 self.endChange('moved')
3226 def __movePoint(self, p, *args):
3227 _alen = len(args)
3228 if _alen < 2:
3229 raise ValueError, "Invalid argument count: %d" % _alen
3230 if p is not self.__p1 and p is not self.__p2:
3231 raise ValueError, "Unexpected dimension point: " + `p`
3232 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
3233 self.calcDimValues(True)
3234 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
3236 def sendsMessage(self, m):
3237 if m in LinearDimension.__messages:
3238 return True
3239 return super(LinearDimension, self).sendsMessage(m)
3241 class HorizontalDimension(LinearDimension):
3242 """A class representing Horizontal dimensions.
3244 This class is derived from the LinearDimension class, so
3245 it shares all those attributes and methods of its parent.
3247 def __init__(self, p1, p2, x, y, ds=None, **kw):
3248 """Initialize a Horizontal Dimension.
3250 hdim = HorizontalDimension(p1, p2, x, y, ds)
3252 p1: A Point contained in a Layer
3253 p2: A Point contained in a Layer
3254 x: The x-coordinate of the dimensional text
3255 y: The y-coordinate of the dimensional text
3256 ds: The DimStyle used for this Dimension.
3258 super(HorizontalDimension, self).__init__(p1, p2, x, y, ds, **kw)
3260 def getValues(self):
3261 """Return values comprising the HorizontalDimension.
3263 getValues()
3265 This method extends the LinearDimension::getValues() method.
3267 _data = super(HorizontalDimension, self).getValues()
3268 _data.setValue('type', 'hdim')
3269 return _data
3271 def calculate(self):
3272 """Determine the length of this HorizontalDimension.
3274 calculate()
3276 _p1, _p2 = self.getDimPoints()
3277 return abs(_p1.x - _p2.x)
3279 def calcDimValues(self, allpts=True):
3280 """Recalculate the values for dimensional display.
3282 calcDimValues([allpts])
3284 This method overrides the LinearDimension::calcDimValues() method.
3286 _allpts = allpts
3287 util.test_boolean(_allpts)
3288 _p1, _p2 = self.getDimPoints()
3289 _bar1, _bar2 = self.getDimBars()
3290 _crossbar = self.getDimCrossbar()
3291 _p1x, _p1y = _p1.getCoords()
3292 _p2x, _p2y = _p2.getCoords()
3293 _dx, _dy = self.getLocation()
3294 _offset = self.getOffset()
3295 _ext = self.getExtension()
3296 _crossbar.setFirstEndpoint(_p1x, _dy)
3297 _crossbar.setSecondEndpoint(_p2x, _dy)
3298 if _dx < min(_p1x, _p2x) or _dx > max(_p1x, _p2x):
3299 if _p1x < _p2x:
3300 if _dx < _p1x:
3301 _crossbar.setFirstEndpoint(_dx, _dy)
3302 if _dx > _p2x:
3303 _crossbar.setSecondEndpoint(_dx, _dy)
3304 else:
3305 if _dx < _p2x:
3306 _crossbar.setSecondEndpoint(_dx, _dy)
3307 if _dx > _p1x:
3308 _crossbar.setFirstEndpoint(_dx, _dy)
3309 _crossbar.setFirstCrossbarPoint(_p1x, _dy)
3310 _crossbar.setSecondCrossbarPoint(_p2x, _dy)
3311 if _dy < min(_p1y, _p2y):
3312 _bar1.setFirstEndpoint(_p1x, (_p1y - _offset))
3313 _bar1.setSecondEndpoint(_p1x, (_dy - _ext))
3314 _bar2.setFirstEndpoint(_p2x, (_p2y - _offset))
3315 _bar2.setSecondEndpoint(_p2x, (_dy - _ext))
3316 elif _dy > max(_p1y, _p2y):
3317 _bar1.setFirstEndpoint(_p1x, (_p1y + _offset))
3318 _bar1.setSecondEndpoint(_p1x, (_dy + _ext))
3319 _bar2.setFirstEndpoint(_p2x, (_p2y + _offset))
3320 _bar2.setSecondEndpoint(_p2x, (_dy + _ext))
3321 else:
3322 if _dy > _p1y:
3323 _bar1.setFirstEndpoint(_p1x, (_p1y + _offset))
3324 _bar1.setSecondEndpoint(_p1x, (_dy + _ext))
3325 else:
3326 _bar1.setFirstEndpoint(_p1x, (_p1y - _offset))
3327 _bar1.setSecondEndpoint(_p1x, (_dy - _ext))
3328 if _dy > _p2y:
3329 _bar2.setFirstEndpoint(_p2x, (_p2y + _offset))
3330 _bar2.setSecondEndpoint(_p2x, (_dy + _ext))
3331 else:
3332 _bar2.setFirstEndpoint(_p2x, (_p2y - _offset))
3333 _bar2.setSecondEndpoint(_p2x, (_dy - _ext))
3334 if _allpts:
3335 self.calcMarkerPoints()
3337 def clone(self):
3338 _p1, _p2 = self.getDimPoints()
3339 _x, _y = self.getLocation()
3340 _ds = self.getDimStyle()
3341 _hdim = HorizontalDimension(_p1, _p2, _x, _y, _ds)
3342 _hdim.copyDimValues(self)
3343 return _hdim
3345 class VerticalDimension(LinearDimension):
3346 """A class representing Vertical dimensions.
3348 This class is derived from the LinearDimension class, so
3349 it shares all those attributes and methods of its parent.
3352 def __init__(self, p1, p2, x, y, ds=None, **kw):
3353 """Initialize a Vertical Dimension.
3355 vdim = VerticalDimension(p1, p2, x, y, ds)
3357 p1: A Point contained in a Layer
3358 p2: A Point contained in a Layer
3359 x: The x-coordinate of the dimensional text
3360 y: The y-coordinate of the dimensional text
3361 ds: The DimStyle used for this Dimension.
3363 super(VerticalDimension, self).__init__(p1, p2, x, y, ds, **kw)
3365 def getValues(self):
3366 """Return values comprising the VerticalDimension.
3368 getValues()
3370 This method extends the LinearDimension::getValues() method.
3372 _data = super(VerticalDimension, self).getValues()
3373 _data.setValue('type', 'vdim')
3374 return _data
3376 def calculate(self):
3377 """Determine the length of this VerticalDimension.
3379 calculate()
3381 _p1, _p2 = self.getDimPoints()
3382 return abs(_p1.y - _p2.y)
3384 def calcDimValues(self, allpts=True):
3385 """Recalculate the values for dimensional display.
3387 calcDimValues([allpts])
3389 This method overrides the LinearDimension::calcDimValues() method.
3391 _allpts = allpts
3392 util.test_boolean(_allpts)
3393 _p1, _p2 = self.getDimPoints()
3394 _bar1, _bar2 = self.getDimBars()
3395 _crossbar = self.getDimCrossbar()
3396 _p1x, _p1y = _p1.getCoords()
3397 _p2x, _p2y = _p2.getCoords()
3398 _dx, _dy = self.getLocation()
3399 _offset = self.getOffset()
3400 _ext = self.getExtension()
3401 _crossbar.setFirstEndpoint(_dx, _p1y)
3402 _crossbar.setSecondEndpoint(_dx, _p2y)
3403 if _dy < min(_p1y, _p2y) or _dy > max(_p1y, _p2y):
3404 if _p1y < _p2y:
3405 if _dy < _p1y:
3406 _crossbar.setFirstEndpoint(_dx, _dy)
3407 if _dy > _p2y:
3408 _crossbar.setSecondEndpoint(_dx, _dy)
3409 if _p2y < _p1y:
3410 if _dy < _p2y:
3411 _crossbar.setSecondEndpoint(_dx, _dy)
3412 if _dy > _p1y:
3413 _crossbar.setFirstEndpoint(_dx, _dy)
3414 _crossbar.setFirstCrossbarPoint(_dx, _p1y)
3415 _crossbar.setSecondCrossbarPoint(_dx, _p2y)
3416 if _dx < min(_p1x, _p2x):
3417 _bar1.setFirstEndpoint((_p1x - _offset), _p1y)
3418 _bar1.setSecondEndpoint((_dx - _ext), _p1y)
3419 _bar2.setFirstEndpoint((_p2x - _offset), _p2y)
3420 _bar2.setSecondEndpoint((_dx - _ext), _p2y)
3421 elif _dx > max(_p1x, _p2x):
3422 _bar1.setFirstEndpoint((_p1x + _offset), _p1y)
3423 _bar1.setSecondEndpoint((_dx + _ext), _p1y)
3424 _bar2.setFirstEndpoint((_p2x + _offset), _p2y)
3425 _bar2.setSecondEndpoint((_dx + _ext), _p2y)
3426 else:
3427 if _dx > _p1x:
3428 _bar1.setFirstEndpoint((_p1x + _offset), _p1y)
3429 _bar1.setSecondEndpoint((_dx + _ext), _p1y)
3430 else:
3431 _bar1.setFirstEndpoint((_p1x - _offset), _p1y)
3432 _bar1.setSecondEndpoint((_dx - _ext), _p1y)
3433 if _dx > _p2x:
3434 _bar2.setFirstEndpoint((_p2x + _offset), _p2y)
3435 _bar2.setSecondEndpoint((_dx + _ext), _p2y)
3436 else:
3437 _bar2.setFirstEndpoint((_p2x - _offset), _p2y)
3438 _bar2.setSecondEndpoint((_dx - _ext), _p2y)
3439 if _allpts:
3440 self.calcMarkerPoints()
3442 def clone(self):
3443 _p1, _p2 = self.getDimPoints()
3444 _x, _y = self.getLocation()
3445 _ds = self.getDimStyle()
3446 _vdim = VerticalDimension(_p1, _p2, _x, _y, _ds)
3447 _vdim.copyDimValues(self)
3448 return _vdim
3450 class RadialDimension(Dimension):
3451 """A class for Radial dimensions.
3453 The RadialDimension class is derived from the Dimension
3454 class, so it shares all of those methods and attributes.
3455 A RadialDimension should be used to display either the
3456 radius or diamter of a Circle object.
3458 A RadialDimension object has the following methods:
3460 {get/set}DimCircle(): Get/Set the measured circle object.
3461 getDimLayer(): Return the layer containing the measured circle.
3462 {get/set}DiaMode(): Get/Set if the RadialDimension should return diameters.
3463 getDimXPoints(): Get the x-coordinates of the dimension bar positions.
3464 getDimYPoints(): Get the y-coordinates of the dimension bar positions.
3465 getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
3466 getDimCrossbar(): Get the DimCrossbar object of the RadialDimension.
3467 calcDimValues(): Calculate the endpoint of the dimension line.
3468 mapCoords(): Return coordinates on the dimension near some point.
3469 onDimension(): Test if an x/y coordinate pair fall on the dimension line.
3471 __messages = {
3472 'dimobj_changed' : True,
3473 'dia_mode_changed' : True,
3476 def __init__(self, cir, x, y, ds=None, **kw):
3477 """Initialize a RadialDimension object.
3479 rdim = RadialDimension(cir, x, y, ds)
3481 cir: A Circle or Arc object
3482 x: The x-coordinate of the dimensional text
3483 y: The y-coordinate of the dimensional text
3484 ds: The DimStyle used for this Dimension.
3486 super(RadialDimension, self).__init__(x, y, ds, **kw)
3487 if not isinstance(cir, (circle.Circle, arc.Arc)):
3488 raise TypeError, "Invalid circle/arc type: " + `type(cir)`
3489 if cir.getParent() is None:
3490 raise ValueError, "Circle/Arc not found in Layer!"
3491 self.__circle = cir
3492 self.__crossbar = DimCrossbar()
3493 self.__dia_mode = False
3494 cir.storeUser(self)
3495 _ds = self.getDimStyle()
3496 _pds, _sds = self.getDimstrings()
3497 _pds.mute()
3498 try:
3499 _pds.setPrefix(_ds.getValue('RADIAL_DIM_PRIMARY_PREFIX'))
3500 _pds.setSuffix(_ds.getValue('RADIAL_DIM_PRIMARY_SUFFIX'))
3501 finally:
3502 _pds.unmute()
3503 _sds.mute()
3504 try:
3505 _sds.setPrefix(_ds.getValue('RADIAL_DIM_SECONDARY_PREFIX'))
3506 _sds.setSuffix(_ds.getValue('RADIAL_DIM_SECONDARY_SUFFIX'))
3507 finally:
3508 _sds.unmute()
3509 self.setDiaMode(_ds.getValue('RADIAL_DIM_DIA_MODE'))
3510 cir.connect('moved', self.__moveCircle)
3511 cir.connect('radius_changed', self.__radiusChanged)
3512 cir.connect('change_pending', self.__circleChangePending)
3513 cir.connect('change_complete', self.__circleChangeComplete)
3514 self.calcDimValues()
3516 def __eq__(self, rdim):
3517 """Compare two RadialDimensions for equality.
3519 if not isinstance(rdim, RadialDimension):
3520 return False
3521 _val = False
3522 _layer = self.__circle.getParent()
3523 _rc = rdim.getDimCircle()
3524 _rl = _rc.getParent()
3525 if _layer is _rl and self.__circle == _rc:
3526 _val = True
3527 return _val
3529 def __ne__(self, rdim):
3530 """Compare two RadialDimensions for inequality.
3532 if not isinstance(rdim, RadialDimension):
3533 return True
3534 _val = True
3535 _layer = self.__circle.getParent()
3536 _rc = rdim.getDimCircle()
3537 _rl = _rc.getParent()
3538 if _layer is _rl and self.__circle == _rc:
3539 _val = False
3540 return _val
3542 def finish(self):
3543 self.__circle.disconnect(self)
3544 self.__circle.freeUser(self)
3545 self.__circle = self.__crossbar = None
3546 super(RadialDimension, self).finish()
3548 def getValues(self):
3549 """Return values comprising the RadialDimension.
3551 getValues()
3553 This method extends the Dimension::getValues() method.
3555 _data = super(RadialDimension, self).getValues()
3556 _data.setValue('type', 'rdim')
3557 _data.setValue('circle', self.__circle.getID())
3558 _layer = self.__circle.getParent()
3559 _data.setValue('layer', _layer.getID())
3560 _data.setValue('dia_mode', self.__dia_mode)
3561 return _data
3563 def getDiaMode(self):
3564 """Return if the RadialDimension will return diametrical values.
3566 getDiaMode()
3568 This method returns True if the diameter value is returned,
3569 and False otherwise.
3571 return self.__dia_mode
3573 def setDiaMode(self, mode=False):
3574 """Set the RadialDimension to return diametrical values.
3576 setDiaMode([mode])
3578 Calling this method without an argument sets the RadialDimension
3579 to return radial measurements. If the argument "mode" is supplied,
3580 it should be either True or False.
3582 If the RadialDimension is measuring an arc, the returned value
3583 will always be set to return a radius.
3585 util.test_boolean(mode)
3586 if not isinstance(self.__circle, arc.Arc):
3587 _m = self.__dia_mode
3588 if _m is not mode:
3589 self.startChange('dia_mode_changed')
3590 self.__dia_mode = mode
3591 self.endChange('dia_mode_changed')
3592 self.sendMessage('dia_mode_changed', _m)
3593 self.calcDimValues()
3594 self.modified()
3596 dia_mode = property(getDiaMode, setDiaMode, None,
3597 "Draw the Dimension as a diameter")
3599 def getDimLayer(self):
3600 """Return the Layer object holding the Circle for this RadialDimension.
3602 getDimLayer()
3604 return self.__circle.getParent()
3606 def getDimCircle(self):
3607 """Return the Circle object this RadialDimension is measuring.
3609 getDimCircle()
3611 return self.__circle
3613 def setDimCircle(self, c):
3614 """Set the Circle object measured by this RadialDimension.
3616 setDimCircle(c)
3618 The argument for this method is:
3620 c: A Circle/Arc contained in a Layer
3622 if self.isLocked():
3623 raise RuntimeError, "Setting circle/arc not allowed - object locked."
3624 if not isinstance(c, (circle.Circle, arc.Arc)):
3625 raise TypeError, "Invalid circle/arc type: " + `type(c)`
3626 if c.getParent() is None:
3627 raise ValueError, "Circle/Arc not found in a Layer!"
3628 _circ = self.__circle
3629 if _circ is not c:
3630 _circ.disconnect(self)
3631 _circ.freeUser(self)
3632 self.startChange('dimobj_changed')
3633 self.__circle = c
3634 self.endChange('dimobj_changed')
3635 c.storeUser(self)
3636 self.sendMessage('dimobj_changed', _circ, c)
3637 c.connect('moved', self.__moveCircle)
3638 c.connect('radius_changed', self.__radiusChanged)
3639 c.connect('change_pending', self.__circleChangePending)
3640 c.connect('change_complete', self.__circleChangeComplete)
3641 self.calcDimValues()
3642 self.modified()
3644 circle = property(getDimCircle, None, None,
3645 "Radial dimension circle object.")
3647 def getDimCrossbar(self):
3648 """Get the DimCrossbar object used by the RadialDimension.
3650 getDimCrossbar()
3652 return self.__crossbar
3654 def calcDimValues(self, allpts=True):
3655 """Recalculate the values for dimensional display.
3657 calcDimValues([allpts])
3659 The optional argument 'allpts' is by default True. Calling
3660 this method with the argument set to False will skip the
3661 calculation of any dimension endpoint marker points.
3663 _allpts = allpts
3664 util.test_boolean(_allpts)
3665 _c = self.__circle
3666 _dimbar = self.__crossbar
3667 _cx, _cy = _c.getCenter().getCoords()
3668 _rad = _c.getRadius()
3669 _dx, _dy = self.getLocation()
3670 _dia_mode = self.__dia_mode
3671 _sep = math.hypot((_dx - _cx), (_dy - _cy))
3672 _angle = math.atan2((_dy - _cy), (_dx - _cx))
3673 _sx = _rad * math.cos(_angle)
3674 _sy = _rad * math.sin(_angle)
3675 if isinstance(_c, arc.Arc):
3676 assert _dia_mode is False, "dia_mode for arc radial dimension"
3677 _sa = _c.getStartAngle()
3678 _ea = _c.getEndAngle()
3679 _angle = _rtd * _angle
3680 if _angle < 0.0:
3681 _angle = _angle + 360.0
3682 if not _c.throughAngle(_angle):
3683 _ep1, _ep2 = _c.getEndpoints()
3684 if _angle < _sa:
3685 _sa = _dtr * _sa
3686 _sx = _rad * math.cos(_sa)
3687 _sy = _rad * math.sin(_sa)
3688 if _sep > _rad:
3689 _dx = _cx + (_sep * math.cos(_sa))
3690 _dy = _cy + (_sep * math.sin(_sa))
3691 if _angle > _ea:
3692 _ea = _dtr * _ea
3693 _sx = _rad * math.cos(_ea)
3694 _sy = _rad * math.sin(_ea)
3695 if _sep > _rad:
3696 _dx = _cx + (_sep * math.cos(_ea))
3697 _dy = _cy + (_sep * math.sin(_ea))
3698 if _dia_mode:
3699 _dimbar.setFirstEndpoint((_cx - _sx), (_cy - _sy))
3700 _dimbar.setFirstCrossbarPoint((_cx - _sx), (_cy - _sy))
3701 else:
3702 _dimbar.setFirstEndpoint(_cx, _cy)
3703 _dimbar.setFirstCrossbarPoint(_cx, _cy)
3704 if _sep > _rad:
3705 _dimbar.setSecondEndpoint(_dx, _dy)
3706 else:
3707 _dimbar.setSecondEndpoint((_cx + _sx), (_cy + _sy))
3708 _dimbar.setSecondCrossbarPoint((_cx + _sx), (_cy + _sy))
3709 if not _allpts:
3710 return
3712 # calculate dimension endpoint marker coordinates
3714 _type = self.getEndpointType()
3715 _dimbar.clearMarkerPoints()
3716 if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE:
3717 return
3718 _size = self.getEndpointSize()
3719 _x1, _y1 = _dimbar.getFirstCrossbarPoint()
3720 _x2, _y2 = _dimbar.getSecondCrossbarPoint()
3721 _sine, _cosine = _dimbar.getSinCosValues()
3722 if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW:
3723 _height = _size/5.0
3724 # p1 -> (x,y) = (size, _height)
3725 _mx = (_cosine * _size - _sine * _height) + _x1
3726 _my = (_sine * _size + _cosine * _height) + _y1
3727 _dimbar.storeMarkerPoint(_mx, _my)
3728 # p2 -> (x,y) = (size, -_height)
3729 _mx = (_cosine * _size - _sine *(-_height)) + _x1
3730 _my = (_sine * _size + _cosine *(-_height)) + _y1
3731 _dimbar.storeMarkerPoint(_mx, _my)
3732 # p3 -> (x,y) = (-size, _height)
3733 _mx = (_cosine * (-_size) - _sine * _height) + _x2
3734 _my = (_sine * (-_size) + _cosine * _height) + _y2
3735 _dimbar.storeMarkerPoint(_mx, _my)
3736 # p4 -> (x,y) = (-size, -_height)
3737 _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2
3738 _my = (_sine * (-_size) + _cosine *(-_height)) + _y2
3739 _dimbar.storeMarkerPoint(_mx, _my)
3740 elif _type == Dimension.DIM_ENDPT_SLASH:
3741 _angle = 30.0 * _dtr # slope of slash
3742 _height = 0.5 * _size * math.sin(_angle)
3743 _length = 0.5 * _size * math.cos(_angle)
3744 # p1 -> (x,y) = (-_length, -_height)
3745 _sx1 = (_cosine * (-_length) - _sine * (-_height))
3746 _sy1 = (_sine * (-_length) + _cosine * (-_height))
3747 # p2 -> (x,y) = (_length, _height)
3748 _sx2 = (_cosine * _length - _sine * _height)
3749 _sy2 = (_sine * _length + _cosine * _height)
3751 # shift the calculate based on the location of the
3752 # marker point
3754 _mx = _sx1 + _x1
3755 _my = _sy1 + _y1
3756 _dimbar.storeMarkerPoint(_mx, _my)
3757 _mx = _sx2 + _x1
3758 _my = _sy2 + _y1
3759 _dimbar.storeMarkerPoint(_mx, _my)
3760 _mx = _sx1 + _x2
3761 _my = _sy1 + _y2
3762 _dimbar.storeMarkerPoint(_mx, _my)
3763 _mx = _sx2 + _x2
3764 _my = _sy2 + _y2
3765 _dimbar.storeMarkerPoint(_mx, _my)
3766 else:
3767 raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)
3769 def calculate(self):
3770 """Return the radius or diamter of this RadialDimension.
3772 calculate()
3774 By default, a RadialDimension will return the radius of the
3775 circle. The setDiaMode() method can be called to set the
3776 returned value to corresponed to a diameter.
3778 _val = self.__circle.getRadius()
3779 if self.__dia_mode is True:
3780 _val = _val * 2.0
3781 return _val
3783 def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
3784 """Return whether or not a RadialDimension exists within a region.
3786 isRegion(xmin, ymin, xmax, ymax[, fully])
3788 The four arguments define the boundary of an area, and the
3789 function returns True if the RadialDimension lies within that
3790 area. If the optional argument 'fully' is used and is True,
3791 then the dimensioned circle and the location of the dimension
3792 text must lie within the boundary. Otherwise, the function
3793 returns False.
3795 _xmin = util.get_float(xmin)
3796 _ymin = util.get_float(ymin)
3797 _xmax = util.get_float(xmax)
3798 if _xmax < _xmin:
3799 raise ValueError, "Illegal values: xmax < xmin"
3800 _ymax = util.get_float(ymax)
3801 if _ymax < _ymin:
3802 raise ValueError, "Illegal values: ymax < ymin"
3803 util.test_boolean(fully)
3804 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
3805 if ((_dxmin > _xmax) or
3806 (_dymin > _ymax) or
3807 (_dxmax < _xmin) or
3808 (_dymax < _ymin)):
3809 return False
3810 if fully:
3811 if ((_dxmin > _xmin) and
3812 (_dymin > _ymin) and
3813 (_dxmax < _xmax) and
3814 (_dymax < _ymax)):
3815 return True
3816 return False
3817 _dx, _dy = self.getLocation()
3818 if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text
3819 return True
3820 _p1, _p2 = self.__crossbar.getEndpoints()
3821 _x1, _y1 = _p1
3822 _x2, _y2 = _p2
3823 return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax)
3825 def mapCoords(self, x, y, tol=tolerance.TOL):
3826 """Test an x/y coordinate pair if it could lay on the dimension.
3828 mapCoords(x, y[, tol])
3830 This method has two required parameters:
3832 x: The x-coordinate
3833 y: The y-coordinate
3835 These should both be float values.
3837 There is an optional third parameter, 'tol', giving
3838 the maximum distance from the dimension bars that the
3839 x/y coordinates may sit.
3841 _x = util.get_float(x)
3842 _y = util.get_float(y)
3843 _t = tolerance.toltest(tol)
3844 _p1, _p2 = self.__crossbar.getEndpoints()
3845 _x1, _y1 = _p1
3846 _x2, _y2 = _p2
3847 return util.map_coords(_x, _y, _x1, _y1, _x2, _y2, _t)
3849 def onDimension(self, x, y, tol=tolerance.TOL):
3850 return self.mapCoords(x, y, tol) is not None
3852 def getBounds(self):
3853 """Return the minimal and maximal locations of the dimension
3855 getBounds()
3857 This method overrides the Dimension::getBounds() method
3859 _p1, _p2 = self.__crossbar.getEndpoints()
3860 _x1, _y1 = _p1
3861 _x2, _y2 = _p2
3862 _xmin = min(_x1, _x2)
3863 _ymin = min(_y1, _y2)
3864 _xmax = max(_x1, _x2)
3865 _ymax = max(_y1, _y2)
3866 return _xmin, _ymin, _xmax, _ymax
3868 def clone(self):
3869 _c = self.__circle
3870 _x, _y = self.getLocation()
3871 _ds = self.getDimStyle()
3872 _rdim = RadialDimension(_c, _x, _y, _ds)
3873 _rdim.copyDimValues(self)
3874 _rdim.setDiaMode(self.getDiaMode())
3875 return _rdim
3877 def __circleChangePending(self, p, *args):
3878 _alen = len(args)
3879 if _alen < 1:
3880 raise ValueError, "Invalid argument count: %d" % _alen
3881 if args[0] == 'moved' or args[0] =='radius_changed':
3882 self.startChange('moved')
3884 def __circleChangeComplete(self, p, *args):
3885 _alen = len(args)
3886 if _alen < 1:
3887 raise ValueError, "Invalid argument count: %d" % _alen
3888 if args[0] == 'moved' or args[0] =='radius_changed':
3889 self.endChange('moved')
3891 def __moveCircle(self, circ, *args):
3892 _alen = len(args)
3893 if _alen < 3:
3894 raise ValueError, "Invalid argument count: %d" % _alen
3895 if circ is not self.__circle:
3896 raise ValueError, "Unexpected sender: " + `circ`
3897 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
3898 self.calcDimValues()
3899 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
3901 def __radiusChanged(self, circ, *args):
3902 self.calcDimValues()
3904 def sendsMessage(self, m):
3905 if m in RadialDimension.__messages:
3906 return True
3907 return super(RadialDimension, self).sendsMessage(m)
3909 class AngularDimension(Dimension):
3910 """A class for Angular dimensions.
3912 The AngularDimension class is derived from the Dimension
3913 class, so it shares all of those methods and attributes.
3915 AngularDimension objects have the following methods:
3917 {get/set}VertexPoint(): Get/Set the vertex point for the AngularDimension.
3918 {get/set}P1(): Get/Set the first Point for the AngularDimension.
3919 {get/set}P2(): Get/Set the second Point for the AngularDimension.
3920 getDimPoints(): Return the two Points used in this dimension.
3921 getDimLayers(): Return the two Layers holding the Points.
3922 getDimXPoints(): Get the x-coordinates of the dimension bar positions.
3923 getDimYPoints(): Get the y-coordinates of the dimension bar positions.
3924 getDimAngles(): Get the angles at which the dimension bars should be drawn.
3925 getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
3926 calcDimValues(): Calculate the endpoint of the dimension line.
3927 mapCoords(): Return coordinates on the dimension near some point.
3928 onDimension(): Test if an x/y coordinate pair fall on the dimension line.
3929 invert(): Switch the endpoints used to measure the dimension
3932 __messages = {
3933 'point_changed' : True,
3934 'inverted' : True,
3937 def __init__(self, vp, p1, p2, x, y, ds=None, **kw):
3938 """Initialize an AngularDimension object.
3940 adim = AngularDimension(vp, p1, p2, x, y, ds)
3942 vp: A Point contained in a Layer
3943 p1: A Point contained in a Layer
3944 p2: A Point contained in a Layer
3945 x: The x-coordinate of the dimensional text
3946 y: The y-coordinate of the dimensional text
3947 ds: The DimStyle used for this Dimension.
3949 super(AngularDimension, self).__init__(x, y, ds, **kw)
3950 if not isinstance(vp, point.Point):
3951 raise TypeError, "Invalid point type: " + `type(vp)`
3952 if vp.getParent() is None:
3953 raise ValueError, "Vertex Point not found in a Layer!"
3954 if not isinstance(p1, point.Point):
3955 raise TypeError, "Invalid point type: " + `type(p1)`
3956 if p1.getParent() is None:
3957 raise ValueError, "Point P1 not found in a Layer!"
3958 if not isinstance(p2, point.Point):
3959 raise TypeError, "Invalid point type: " + `type(p2)`
3960 if p2.getParent() is None:
3961 raise ValueError, "Point P2 not found in a Layer!"
3962 self.__vp = vp
3963 self.__p1 = p1
3964 self.__p2 = p2
3965 self.__bar1 = DimBar()
3966 self.__bar2 = DimBar()
3967 self.__crossarc = DimCrossarc()
3968 _ds = self.getDimStyle()
3969 _pds, _sds = self.getDimstrings()
3970 _pds.mute()
3971 try:
3972 _pds.setPrefix(_ds.getValue('ANGULAR_DIM_PRIMARY_PREFIX'))
3973 _pds.setSuffix(_ds.getValue('ANGULAR_DIM_PRIMARY_SUFFIX'))
3974 finally:
3975 _pds.unmute()
3976 _sds.mute()
3977 try:
3978 _sds.setPrefix(_ds.getValue('ANGULAR_DIM_SECONDARY_PREFIX'))
3979 _sds.setSuffix(_ds.getValue('ANGULAR_DIM_SECONDARY_SUFFIX'))
3980 finally:
3981 _sds.unmute()
3982 vp.storeUser(self)
3983 vp.connect('moved', self.__movePoint)
3984 vp.connect('change_pending', self.__pointChangePending)
3985 vp.connect('change_complete', self.__pointChangeComplete)
3986 p1.storeUser(self)
3987 p1.connect('moved', self.__movePoint)
3988 p1.connect('change_pending', self.__pointChangePending)
3989 p1.connect('change_complete', self.__pointChangeComplete)
3990 p2.storeUser(self)
3991 p2.connect('moved', self.__movePoint)
3992 p2.connect('change_pending', self.__pointChangePending)
3993 p2.connect('change_complete', self.__pointChangeComplete)
3994 self.calcDimValues()
3996 def __eq__(self, adim):
3997 """Compare two AngularDimensions for equality.
3999 if not isinstance(adim, AngularDimension):
4000 return False
4001 _val = False
4002 _lvp = self.__vp.getParent()
4003 _lp1 = self.__p1.getParent()
4004 _lp2 = self.__p2.getParent()
4005 _vl, _l1, _l2 = adim.getDimLayers()
4006 _vp, _p1, _p2 = adim.getDimPoints()
4007 if (_lvp is _vl and
4008 self.__vp == _vp and
4009 _lp1 is _l1 and
4010 self.__p1 == _p1 and
4011 _lp2 is _l2 and
4012 self.__p2 == _p2):
4013 _val = True
4014 return _val
4016 def __ne__(self, adim):
4017 """Compare two AngularDimensions for inequality.
4019 if not isinstance(adim, AngularDimension):
4020 return True
4021 _val = True
4022 _lvp = self.__vp.getParent()
4023 _lp1 = self.__p1.getParent()
4024 _lp2 = self.__p2.getParent()
4025 _vl, _l1, _l2 = adim.getDimLayers()
4026 _vp, _p1, _p2 = adim.getDimPoints()
4027 if (_lvp is _vl and
4028 self.__vp == _vp and
4029 _lp1 is _l1 and
4030 self.__p1 == _p1 and
4031 _lp2 is _l2 and
4032 self.__p2 == _p2):
4033 _val = False
4034 return _val
4036 def finish(self):
4037 self.__vp.disconnect(self)
4038 self.__vp.freeUser(self)
4039 self.__p1.disconnect(self)
4040 self.__p1.freeUser(self)
4041 self.__p2.disconnect(self)
4042 self.__p2.freeUser(self)
4043 self.__bar1 = self.__bar2 = self.__crossarc = None
4044 self.__vp = self.__p1 = self.__p2 = None
4045 super(AngularDimension, self).finish()
4047 def getValues(self):
4048 """Return values comprising the AngularDimension.
4050 getValues()
4052 This method extends the Dimension::getValues() method.
4054 _data = super(AngularDimension, self).getValues()
4055 _data.setValue('type', 'adim')
4056 _data.setValue('vp', self.__vp.getID())
4057 _layer = self.__vp.getParent()
4058 _data.setValue('vl', _layer.getID())
4059 _data.setValue('p1', self.__p1.getID())
4060 _layer = self.__p1.getParent()
4061 _data.setValue('l1', _layer.getID())
4062 _data.setValue('p2', self.__p2.getID())
4063 _layer = self.__p2.getParent()
4064 _data.setValue('l2', _layer.getID())
4065 return _data
4067 def getDimLayers(self):
4068 """Return the layers used in an AngularDimension.
4070 getDimLayers()
4072 _vl = self.__vp.getParent()
4073 _l1 = self.__p1.getParent()
4074 _l2 = self.__p2.getParent()
4075 return _vl, _l1, _l2
4077 def getDimPoints(self):
4078 """Return the points used in an AngularDimension.
4080 getDimPoints()
4082 return self.__vp, self.__p1, self.__p2
4084 def getVertexPoint(self):
4085 """Return the vertex point used in an AngularDimension.
4087 getVertexPoint()
4089 return self.__vp
4091 def setVertexPoint(self, p):
4092 """Set the vertex point for an AngularDimension.
4094 setVertexPoint(p)
4096 There is one required argument for this method:
4098 p: A Point contained in Layer
4100 if self.isLocked():
4101 raise RuntimeError, "Setting vertex point allowed - object locked."
4102 if not isinstance(p, point.Point):
4103 raise TypeError, "Invalid point type: " + `type(p)`
4104 if p.getParent() is None:
4105 raise ValueError, "Point not found in a Layer!"
4106 _vp = self.__vp
4107 if _vp is not p:
4108 _vp.disconnect(self)
4109 _vp.freeUser(self)
4110 self.startChange('point_changed')
4111 self.__vp = p
4112 self.endChange('point_changed')
4113 p.storeUser(self)
4114 p.connect('moved', self.__movePoint)
4115 p.connect('change_pending', self.__pointChangePending)
4116 p.connect('change_complete', self.__pointChangeComplete)
4117 self.sendMessage('point_changed', _vp, p)
4118 self.calcDimValues()
4119 if abs(_vp.x - p.x) > 1e-10 or abs(_vp.y - p.y) > 1e-10:
4120 _x1, _y1 = self.__p1.getCoords()
4121 _x2, _y2 = self.__p2.getCoords()
4122 _dx, _dy = self.getLocation()
4123 self.sendMessage('moved', _vp.x, _vp.y, _x1, _y1,
4124 _x2, _y2, _dx, _dy)
4125 self.modified()
4127 vp = property(getVertexPoint, None, None,
4128 "Angular Dimension vertex point.")
4130 def getP1(self):
4131 """Return the first angle point used in an AngularDimension.
4133 getP1()
4135 return self.__p1
4137 def setP1(self, p):
4138 """Set the first Point for an AngularDimension.
4140 setP1(p)
4142 There is one required argument for this method:
4144 p: A Point contained in a Layer.
4146 if self.isLocked():
4147 raise RuntimeError, "Setting vertex point allowed - object locked."
4148 if not isinstance(p, point.Point):
4149 raise TypeError, "Invalid point type: " + `type(p)`
4150 if p.getParent() is None:
4151 raise ValueError, "Point not found in a Layer!"
4152 _p1 = self.__p1
4153 if _p1 is not p:
4154 _p1.disconnect(self)
4155 _p1.freeUser(self)
4156 self.startChange('point_changed')
4157 self.__p1 = p
4158 self.endChange('point_changed')
4159 p.storeUser(self)
4160 p.connect('moved', self.__movePoint)
4161 p.connect('change_pending', self.__pointChangePending)
4162 p.connect('change_complete', self.__pointChangeComplete)
4163 self.sendMessage('point_changed', _p1, p)
4164 self.calcDimValues()
4165 if abs(_p1.x - p.x) > 1e-10 or abs(_p1.y - p.y) > 1e-10:
4166 _vx, _vy = self.__vp.getCoords()
4167 _x2, _y2 = self.__p2.getCoords()
4168 _dx, _dy = self.getLocation()
4169 self.sendMessage('moved', _vx, _vy, _p1.x, _p1.y,
4170 _x2, _y2, _dx, _dy)
4171 self.modified()
4173 p1 = property(getP1, None, None, "Dimension first point.")
4175 def getP2(self):
4176 """Return the second angle point used in an AngularDimension.
4178 getP2()
4180 return self.__p2
4182 def setP2(self, p):
4183 """Set the second Point for an AngularDimension.
4185 setP2(p)
4187 There is one required argument for this method:
4189 l: The layer holding the Point p
4190 p: A point in Layer l
4192 if self.isLocked():
4193 raise RuntimeError, "Setting vertex point allowed - object locked."
4194 if not isinstance(p, point.Point):
4195 raise TypeError, "Invalid point type: " + `type(p)`
4196 if p.getParent() is None:
4197 raise ValueError, "Point not found in a Layer!"
4198 _p2 = self.__p2
4199 if _p2 is not p:
4200 _p2.disconnect(self)
4201 _p2.freeUser(self)
4202 self.startChange('point_changed')
4203 self.__p2 = p
4204 self.endChange('point_changed')
4205 p.storeUser(self)
4206 p.connect('moved', self.__movePoint)
4207 p.connect('change_pending', self.__pointChangePending)
4208 p.connect('change_complete', self.__pointChangeComplete)
4209 self.sendMessage('point_changed', _p2, p)
4210 self.calcDimValues()
4211 if abs(_p2.x - p.x) > 1e-10 or abs(_p2.y - p.y) > 1e-10:
4212 _vx, _vy = self.__vp.getCoords()
4213 _x1, _y1 = self.__p1.getCoords()
4214 _dx, _dy = self.getLocation()
4215 self.sendMessage('moved', _vx, _vy, _x1, _y1,
4216 _p2.x, _p2.y, _dx, _dy)
4217 self.modified()
4220 p2 = property(getP2, None, None, "Dimension second point.")
4222 def getDimAngles(self):
4223 """Get the array of dimension bar angles.
4225 geDimAngles()
4227 _angle1 = self.__bar1.getAngle()
4228 _angle2 = self.__bar2.getAngle()
4229 return _angle1, _angle2
4231 def getDimRadius(self):
4232 """Get the radius of the dimension crossarc.
4234 getDimRadius()
4236 return self.__crossarc.getRadius()
4238 def getDimBars(self):
4239 """Return the dimension boundary bars.
4241 getDimBars()
4243 return self.__bar1, self.__bar2
4245 def getDimCrossarc(self):
4246 """Get the DimCrossarc object used by the AngularDimension.
4248 getDimCrossarc()
4250 return self.__crossarc
4252 def invert(self):
4253 """Switch the endpoints used in this object.
4255 invert()
4257 Invoking this method on an AngularDimension will result in
4258 it measuring the opposite angle than what it currently measures.
4260 _pt = self.__p1
4261 self.startChange('inverted')
4262 self.__p1 = self.__p2
4263 self.__p2 = _pt
4264 self.endChange('inverted')
4265 self.sendMessage('inverted')
4266 self.calcDimValues()
4267 self.modified()
4269 def calculate(self):
4270 """Find the value of the angle measured by this AngularDimension.
4272 calculate()
4274 _vx, _vy = self.__vp.getCoords()
4275 _p1x, _p1y = self.__p1.getCoords()
4276 _p2x, _p2y = self.__p2.getCoords()
4277 _a1 = _rtd * math.atan2((_p1y - _vy), (_p1x - _vx))
4278 if _a1 < 0.0:
4279 _a1 = _a1 + 360.0
4280 _a2 = _rtd * math.atan2((_p2y - _vy), (_p2x - _vx))
4281 if _a2 < 0.0:
4282 _a2 = _a2 + 360.0
4283 _val = _a2 - _a1
4284 if _a1 > _a2:
4285 _val = _val + 360.0
4286 return _val
4288 def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
4289 """Return whether or not an AngularDimension exists within a region.
4291 isRegion(xmin, ymin, xmax, ymax[, fully])
4293 The four arguments define the boundary of an area, and the
4294 function returns True if the RadialDimension lies within that
4295 area. If the optional argument 'fully' is used and is True,
4296 then the dimensioned circle and the location of the dimension
4297 text must lie within the boundary. Otherwise, the function
4298 returns False.
4300 _xmin = util.get_float(xmin)
4301 _ymin = util.get_float(ymin)
4302 _xmax = util.get_float(xmax)
4303 if _xmax < _xmin:
4304 raise ValueError, "Illegal values: xmax < xmin"
4305 _ymax = util.get_float(ymax)
4306 if _ymax < _ymin:
4307 raise ValueError, "Illegal values: ymax < ymin"
4308 util.test_boolean(fully)
4309 _vx, _vy = self.__vp.getCoords()
4310 _dx, _dy = self.getLocation()
4311 _pxmin, _pymin, _pxmax, _pymax = self.getBounds()
4312 _val = False
4313 if ((_pxmin > _xmax) or
4314 (_pymin > _ymax) or
4315 (_pxmax < _xmin) or
4316 (_pymax < _ymin)):
4317 return False
4318 if _xmin < _dx < _xmax and _ymin < _dy < _ymax:
4319 return True
4321 # bar on vp-p1 line
4323 _ep1, _ep2 = self.__bar1.getEndpoints()
4324 _ex1, _ey1 = _ep1
4325 _ex2, _ey2 = _ep2
4326 if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax):
4327 return True
4329 # bar at vp-p2 line
4331 _ep1, _ep2 = self.__bar2.getEndpoints()
4332 _ex1, _ey1 = _ep1
4333 _ex2, _ey2 = _ep2
4334 if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax):
4335 return True
4337 # dimension crossarc
4339 _val = False
4340 _r = self.__crossarc.getRadius()
4341 _d1 = math.hypot((_xmin - _vx), (_ymin - _vy))
4342 _d2 = math.hypot((_xmin - _vx), (_ymax - _vy))
4343 _d3 = math.hypot((_xmax - _vx), (_ymax - _vy))
4344 _d4 = math.hypot((_xmax - _vx), (_ymin - _vy))
4345 _dmin = min(_d1, _d2, _d3, _d4)
4346 _dmax = max(_d1, _d2, _d3, _d4)
4347 if _xmin < _vx < _xmax and _ymin < _vy < _ymax:
4348 _dmin = -1e-10
4349 else:
4350 if _vx > _xmax and _ymin < _vy < _ymax:
4351 _dmin = _vx - _xmax
4352 elif _vx < _xmin and _ymin < _vy < _ymax:
4353 _dmin = _xmin - _vx
4354 elif _vy > _ymax and _xmin < _vx < _xmax:
4355 _dmin = _vy - _ymax
4356 elif _vy < _ymin and _xmin < _vx < _xmax:
4357 _dmin = _ymin - _vy
4358 if _dmin < _r < _dmax:
4359 _da = _rtd * math.atan2((_ymin - _vy), (_xmin - _vx))
4360 if _da < 0.0:
4361 _da = _da + 360.0
4362 _val = self._throughAngle(_da)
4363 if _val:
4364 return _val
4365 _da = _rtd * math.atan2((_ymin - _vy), (_xmax - _vx))
4366 if _da < 0.0:
4367 _da = _da + 360.0
4368 _val = self._throughAngle(_da)
4369 if _val:
4370 return _val
4371 _da = _rtd * math.atan2((_ymax - _vy), (_xmax - _vx))
4372 if _da < 0.0:
4373 _da = _da + 360.0
4374 _val = self._throughAngle(_da)
4375 if _val:
4376 return _val
4377 _da = _rtd * math.atan2((_ymax - _vy), (_xmin - _vx))
4378 if _da < 0.0:
4379 _da = _da + 360.0
4380 _val = self._throughAngle(_da)
4381 return _val
4383 def _throughAngle(self, angle):
4384 """Test if the angular crossarc exists at a certain angle.
4386 _throughAngle()
4388 This method is private to the AngularDimension class.
4390 _crossarc = self.__crossarc
4391 _sa = _crossarc.getStartAngle()
4392 _ea = _crossarc.getEndAngle()
4393 _val = True
4394 if abs(_sa - _ea) > 1e-10:
4395 if _sa > _ea:
4396 if angle > _ea and angle < _sa:
4397 _val = False
4398 else:
4399 if angle > _ea or angle < _sa:
4400 _val = False
4401 return _val
4403 def calcDimValues(self, allpts=True):
4404 """Recalculate the values for dimensional display.
4406 calcDimValues([allpts])
4408 The optional argument 'allpts' is by default True. Calling
4409 this method with the argument set to False will skip the
4410 calculation of any dimension endpoint marker points.
4412 _allpts = allpts
4413 util.test_boolean(_allpts)
4414 _vx, _vy = self.__vp.getCoords()
4415 _p1x, _p1y = self.__p1.getCoords()
4416 _p2x, _p2y = self.__p2.getCoords()
4417 _dx, _dy = self.getLocation()
4418 _offset = self.getOffset()
4419 _ext = self.getExtension()
4420 _bar1 = self.__bar1
4421 _bar2 = self.__bar2
4422 _crossarc = self.__crossarc
4423 _dv1 = math.hypot((_p1x - _vx), (_p1y - _vy))
4424 _dv2 = math.hypot((_p2x - _vx), (_p2y - _vy))
4425 _ddp = math.hypot((_dx - _vx), (_dy - _vy))
4426 _crossarc.setRadius(_ddp)
4428 # first dimension bar
4430 _angle = math.atan2((_p1y - _vy), (_p1x - _vx))
4431 _sine = math.sin(_angle)
4432 _cosine = math.cos(_angle)
4433 _deg = _angle * _rtd
4434 if _deg < 0.0:
4435 _deg = _deg + 360.0
4436 _crossarc.setStartAngle(_deg)
4437 _ex = _vx + (_ddp * _cosine)
4438 _ey = _vy + (_ddp * _sine)
4439 _crossarc.setFirstCrossbarPoint(_ex, _ey)
4440 _crossarc.setFirstEndpoint(_ex, _ey)
4441 if _ddp > _dv1: # dim point is radially further to vp than p1
4442 _x1 = _p1x + (_offset * _cosine)
4443 _y1 = _p1y + (_offset * _sine)
4444 _x2 = _vx + ((_ddp + _ext) * _cosine)
4445 _y2 = _vy + ((_ddp + _ext) * _sine)
4446 else: # dim point is radially closer to vp than p1
4447 _x1 = _p1x - (_offset * _cosine)
4448 _y1 = _p1y - (_offset * _sine)
4449 _x2 = _vx + ((_ddp - _ext) * _cosine)
4450 _y2 = _vy + ((_ddp - _ext) * _sine)
4451 _bar1.setFirstEndpoint(_x1, _y1)
4452 _bar1.setSecondEndpoint(_x2, _y2)
4454 # second dimension bar
4456 _angle = math.atan2((_p2y - _vy), (_p2x - _vx))
4457 _sine = math.sin(_angle)
4458 _cosine = math.cos(_angle)
4459 _deg = _angle * _rtd
4460 if _deg < 0.0:
4461 _deg = _deg + 360.0
4462 _crossarc.setEndAngle(_deg)
4463 _ex = _vx + (_ddp * _cosine)
4464 _ey = _vy + (_ddp * _sine)
4465 _crossarc.setSecondCrossbarPoint(_ex, _ey)
4466 _crossarc.setSecondEndpoint(_ex, _ey)
4467 if _ddp > _dv2: # dim point is radially further to vp than p2
4468 _x1 = _p2x + (_offset * _cosine)
4469 _y1 = _p2y + (_offset * _sine)
4470 _x2 = _vx + ((_ddp + _ext) * _cosine)
4471 _y2 = _vy + ((_ddp + _ext) * _sine)
4472 else: # dim point is radially closers to vp than p2
4473 _x1 = _p2x - (_offset * _cosine)
4474 _y1 = _p2y - (_offset * _sine)
4475 _x2 = _vx + ((_ddp - _ext) * _cosine)
4476 _y2 = _vy + ((_ddp - _ext) * _sine)
4477 _bar2.setFirstEndpoint(_x1, _y1)
4478 _bar2.setSecondEndpoint(_x2, _y2)
4480 # test if the DimString lies outside the measured angle
4481 # and if it does adjust either the crossarc start or end angle
4483 _deg = _rtd * math.atan2((_dy - _vy), (_dx - _vx))
4484 if _deg < 0.0:
4485 _deg = _deg + 360.0
4486 _csa = _crossarc.getStartAngle()
4487 _cea = _crossarc.getEndAngle()
4488 if ((_csa > _cea) and (_cea < _deg < _csa)):
4489 if abs(_csa - _deg) < abs(_deg - _cea): # closer to start
4490 _crossarc.setStartAngle(_deg)
4491 else:
4492 _crossarc.setEndAngle(_deg)
4493 elif ((_cea > _csa) and ((_csa > _deg) or (_cea < _deg))):
4494 if _deg > _cea:
4495 _a1 = _deg - _cea
4496 _a2 = 360.0 - _deg + _csa
4497 else:
4498 _a1 = 360.0 - _cea + _deg
4499 _a2 = _csa - _deg
4500 if abs(_a1) > abs(_a2): # closer to start
4501 _crossarc.setStartAngle(_deg)
4502 else:
4503 _crossarc.setEndAngle(_deg)
4504 else:
4505 pass
4506 if not _allpts:
4507 return
4509 # calculate dimension endpoint marker coordinates
4511 _type = self.getEndpointType()
4512 _crossarc.clearMarkerPoints()
4513 if _type == Dimension.DIM_ENDPT_NONE or _type == Dimension.DIM_ENDPT_CIRCLE:
4514 return
4515 _size = self.getEndpointSize()
4516 _a1 = _bar1.getAngle() - 90.0
4517 _a2 = _bar2.getAngle() - 90.0
4518 # print "a1: %g" % _a1
4519 # print "a2: %g" % _a2
4520 _mp1, _mp2 = _crossarc.getCrossbarPoints()
4521 _x1, _y1 = _mp1
4522 _x2, _y2 = _mp2
4523 # print "x1: %g" % _x1
4524 # print "y1: %g" % _y1
4525 # print "x2: %g" % _x2
4526 # print "y2: %g" % _y2
4527 _sin1 = math.sin(_dtr * _a1)
4528 _cos1 = math.cos(_dtr * _a1)
4529 _sin2 = math.sin(_dtr * _a2)
4530 _cos2 = math.cos(_dtr * _a2)
4531 if _type == Dimension.DIM_ENDPT_ARROW or _type == Dimension.DIM_ENDPT_FILLED_ARROW:
4532 _height = _size/5.0
4533 # p1 -> (x,y) = (size, _height)
4534 _mx = (_cos1 * (-_size) - _sin1 * _height) + _x1
4535 _my = (_sin1 * (-_size) + _cos1 * _height) + _y1
4536 _crossarc.storeMarkerPoint(_mx, _my)
4537 # p2 -> (x,y) = (size, -_height)
4538 _mx = (_cos1 * (-_size) - _sin1 *(-_height)) + _x1
4539 _my = (_sin1 * (-_size) + _cos1 *(-_height)) + _y1
4540 _crossarc.storeMarkerPoint(_mx, _my)
4541 # p3 -> (x,y) = (size, _height)
4542 _mx = (_cos2 * _size - _sin2 * _height) + _x2
4543 _my = (_sin2 * _size + _cos2 * _height) + _y2
4544 _crossarc.storeMarkerPoint(_mx, _my)
4545 # p4 -> (x,y) = (size, -_height)
4546 _mx = (_cos2 * _size - _sin2 *(-_height)) + _x2
4547 _my = (_sin2 * _size + _cos2 *(-_height)) + _y2
4548 _crossarc.storeMarkerPoint(_mx, _my)
4549 elif _type == Dimension.DIM_ENDPT_SLASH:
4550 _angle = 30.0 * _dtr # slope of slash
4551 _height = 0.5 * _size * math.sin(_angle)
4552 _length = 0.5 * _size * math.cos(_angle)
4553 # p1 -> (x,y) = (-_length, -_height)
4554 _mx = (_cos1 * (-_length) - _sin1 * (-_height)) + _x1
4555 _my = (_sin1 * (-_length) + _cos1 * (-_height)) + _y1
4556 _crossarc.storeMarkerPoint(_mx, _my)
4557 # p2 -> (x,y) = (_length, _height)
4558 _mx = (_cos1 * _length - _sin1 * _height) + _x1
4559 _my = (_sin1 * _length + _cos1 * _height) + _y1
4560 _crossarc.storeMarkerPoint(_mx, _my)
4561 # p3 -> (x,y) = (-_length, -_height)
4562 _mx = (_cos2 * (-_length) - _sin2 * (-_height)) + _x2
4563 _my = (_sin2 * (-_length) + _cos2 * (-_height)) + _y2
4564 _crossarc.storeMarkerPoint(_mx, _my)
4565 # p4 -> (x,y) = (_length, _height)
4566 _mx = (_cos2 * _length - _sin2 * _height) + _x2
4567 _my = (_sin2 * _length + _cos2 * _height) + _y2
4568 _crossarc.storeMarkerPoint(_mx, _my)
4569 else:
4570 raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)
4572 def mapCoords(self, x, y, tol=tolerance.TOL):
4573 """Test an x/y coordinate pair hit the dimension lines and arc.
4575 mapCoords(x, y[, tol])
4577 This method has two required parameters:
4579 x: The x-coordinate
4580 y: The y-coordinate
4582 These should both be float values.
4584 There is an optional third parameter, 'tol', giving
4585 the maximum distance from the dimension bars that the
4586 x/y coordinates may sit.
4588 _x = util.get_float(x)
4589 _y = util.get_float(y)
4590 _t = tolerance.toltest(tol)
4592 # test vp-p1 bar
4594 _ep1, _ep2 = self.__bar1.getEndpoints()
4595 _ex1, _ey1 = _ep1
4596 _ex2, _ey2 = _ep2
4597 _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t)
4598 if _mp is not None:
4599 return _mp
4601 # test vp-p2 bar
4603 _ep1, _ep2 = self.__bar2.getEndpoints()
4604 _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t)
4605 if _mp is not None:
4606 return _mp
4608 # test the arc
4610 _vx, _vy = self.__vp.getCoords()
4611 _psep = math.hypot((_vx - _x), (_vy - y))
4612 _dx, _dy = self.getLocation()
4613 _dsep = math.hypot((_vx - _dx), (_vy - _dy))
4614 if abs(_psep - _dsep) < _t:
4615 _crossarc = self.__crossarc
4616 _sa = _crossarc.getStartAngle()
4617 _ea = _crossarc.getEndAngle()
4618 _angle = _rtd * math.atan2((_y - _vy), (_x - _vx))
4619 _val = True
4620 if abs(_sa - _ea) > 1e-10:
4621 if _sa < _ea:
4622 if _angle < _sa or _angle > _ea:
4623 _val = False
4624 else:
4625 if _angle > _ea or _angle < _sa:
4626 _val = False
4627 if _val:
4628 _xoff = _dsep * math.cos(_angle)
4629 _yoff = _dsep * math.sin(_angle)
4630 return (_vx + _xoff), (_vy + _yoff)
4631 return None
4633 def onDimension(self, x, y, tol=tolerance.TOL):
4634 return self.mapCoords(x, y, tol) is not None
4636 def getBounds(self):
4637 """Return the minimal and maximal locations of the dimension
4639 getBounds()
4641 This method overrides the Dimension::getBounds() method
4643 _vx, _vy = self.__vp.getCoords()
4644 _dx, _dy = self.getLocation()
4645 _dxpts = []
4646 _dypts = []
4647 _ep1, _ep2 = self.__bar1.getEndpoints()
4648 _dxpts.append(_ep1[0])
4649 _dypts.append(_ep1[1])
4650 _dxpts.append(_ep2[0])
4651 _dypts.append(_ep2[1])
4652 _ep1, _ep2 = self.__bar2.getEndpoints()
4653 _dxpts.append(_ep1[0])
4654 _dypts.append(_ep1[1])
4655 _dxpts.append(_ep2[0])
4656 _dypts.append(_ep2[1])
4657 _rad = self.__crossarc.getRadius()
4658 if self._throughAngle(0.0):
4659 _dxpts.append((_vx + _rad))
4660 if self._throughAngle(90.0):
4661 _dypts.append((_vy + _rad))
4662 if self._throughAngle(180.0):
4663 _dxpts.append((_vx - _rad))
4664 if self._throughAngle(270.0):
4665 _dypts.append((_vy - _rad))
4666 _xmin = min(_dx, min(_dxpts))
4667 _ymin = min(_dy, min(_dypts))
4668 _xmax = max(_dx, max(_dxpts))
4669 _ymax = max(_dy, max(_dypts))
4670 return _xmin, _ymin, _xmax, _ymax
4672 def clone(self):
4673 _vp = self.__vp
4674 _p1 = self.__p1
4675 _p2 = self.__p2
4676 _x, _y = self.getLocation()
4677 _ds = self.getDimStyle()
4678 _adim = AngularDimension(_vp, _p1, _p2, _x, _y, _ds)
4679 _adim.copyDimValues(self)
4680 return _adim
4682 def __movePoint(self, p, *args):
4683 _alen = len(args)
4684 if _alen < 2:
4685 raise ValueError, "Invalid argument count: %d" % _alen
4686 if ((p is not self.__vp) and
4687 (p is not self.__p1) and
4688 (p is not self.__p2)):
4689 raise ValueError, "Unexpected dimension point: " + `p`
4690 _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
4691 self.calcDimValues()
4692 self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
4694 def __pointChangePending(self, p, *args):
4695 _alen = len(args)
4696 if _alen < 1:
4697 raise ValueError, "Invalid argument count: %d" % _alen
4698 if args[0] == 'moved':
4699 self.startChange('moved')
4701 def __pointChangeComplete(self, p, *args):
4702 _alen = len(args)
4703 if _alen < 1:
4704 raise ValueError, "Invalid argument count: %d" % _alen
4705 if args[0] == 'moved':
4706 self.endChange('moved')
4708 def sendsMessage(self, m):
4709 if m in AngularDimension.__messages:
4710 return True
4711 return super(AngularDimension, self).sendsMessage(m)
4714 # DimString history class
4717 class DimStringLog(text.TextBlockLog):
4718 __setops = {
4719 'prefix_changed' : DimString.setPrefix,
4720 'suffix_changed' : DimString.setSuffix,
4721 'units_changed' : DimString.setUnits,
4722 'precision_changed' : DimString.setPrecision,
4723 'print_zero_changed' : DimString.setPrintZero,
4724 'print_decimal_changed' : DimString.setPrintDecimal,
4725 'dimension_changed' : DimString.setDimension,
4728 __getops = {
4729 'prefix_changed' : DimString.getPrefix,
4730 'suffix_changed' : DimString.getSuffix,
4731 'units_changed' : DimString.getUnits,
4732 'precision_changed' : DimString.getPrecision,
4733 'print_zero_changed' : DimString.getPrintZero,
4734 'print_decimal_changed' : DimString.getPrintDecimal,
4735 'dimension_changed' : DimString.getDimension,
4738 def __init__(self, obj):
4739 if not isinstance(obj, DimString):
4740 raise TypeError, "Invalid DimString type: " + `type(obj)`
4741 super(DimStringLog, self).__init__(obj)
4742 _ds = self.getObject()
4743 _ds.connect('prefix_changed', self._prefixChanged)
4744 _ds.connect('suffix_changed', self._suffixChanged)
4745 _ds.connect('units_changed', self._unitsChanged)
4746 _ds.connect('precision_changed', self._precisionChanged)
4747 _ds.connect('print_zero_changed', self._printZeroChanged)
4748 _ds.connect('print_decimal_changed', self._printDecimalChanged)
4749 _ds.connect('dimension_changed', self._dimensionChanged)
4751 def _prefixChanged(self, ds, *args):
4752 # print "prefixChanged() ..."
4753 _alen = len(args)
4754 if _alen < 1:
4755 raise ValueError, "Invalid argument count: %d" % _alen
4756 _prefix = args[0]
4757 if not isinstance(_prefix, types.StringTypes):
4758 raise TypeError, "Invalid prefix type: " + `type(_prefix)`
4759 # print "old prefix: %s" % _prefix
4760 self.saveUndoData('prefix_changed', _prefix)
4762 def _suffixChanged(self, ds, *args):
4763 # print "suffixChanged() ..."
4764 _alen = len(args)
4765 if _alen < 1:
4766 raise ValueError, "Invalid argument count: %d" % _alen
4767 _suffix = args[0]
4768 if not isinstance(_suffix, types.StringTypes):
4769 raise TypeError, "Invalid suffix type " + `type(_suffix)`
4770 # print "old suffix: %s" % _suffix
4771 self.saveUndoData('suffix_changed', _suffix)
4773 def _unitsChanged(self, ds, *args):
4774 # print "unitsChanged() ..."
4775 _alen = len(args)
4776 if _alen < 1:
4777 raise ValueError, "Invalid argument count: %d" % _alen
4778 _units = args[0]
4779 if not isinstance(_units, int):
4780 raise TypeError, "Invalid unit type: " + `type(_units)`
4781 # print "old units: %d" % _units
4782 self.saveUndoData('units_changed', _units)
4784 def _precisionChanged(self, ds, *args):
4785 # print "precisionChanged() ..."
4786 _alen = len(args)
4787 if _alen < 1:
4788 raise ValueError, "Invalid argument count: %d" % _alen
4789 _prec = args[0]
4790 if not isinstance(_prec, int):
4791 raise TypeError, "Invalid precision type: " + `type(_prec)`
4792 # print "old precision: %d" % _prec
4793 self.saveUndoData('precision_changed', _prec)
4795 def _printZeroChanged(self, ds, *args):
4796 _alen = len(args)
4797 if _alen < 1:
4798 raise ValueError, "Invalid argument count: %d" % _alen
4799 _flag = args[0]
4800 util.test_boolean(_flag)
4801 self.saveUndoData('print_zero_changed', _flag)
4803 def _printDecimalChanged(self, ds, *args):
4804 _alen = len(args)
4805 if _alen < 1:
4806 raise ValueError, "Invalid argument count: %d" % _alen
4807 _flag = args[0]
4808 util.test_boolean(_flag)
4809 self.saveUndoData('print_decimal_changed', _flag)
4811 def _dimensionChanged(self, ds, *args):
4812 _alen = len(args)
4813 if _alen < 1:
4814 raise ValueError, "Invalid argument count: %d" % _alen
4815 _dim = args[0]
4816 if not isinstance(_dim, Dimension):
4817 raise TypeError, "Invalid dimension type: " + `type(_dim)`
4818 self.saveUndoData('dimension_changed', _dim.getID())
4820 def execute(self, undo, *args):
4821 util.test_boolean(undo)
4822 _alen = len(args)
4823 if len(args) == 0:
4824 raise ValueError, "No arguments to execute()"
4825 _obj = self.getObject()
4826 _op = args[0]
4827 if (_op == 'prefix_changed' or
4828 _op == 'suffix_changed' or
4829 _op == 'units_changed' or
4830 _op == 'precision_changed' or
4831 _op == 'print_zero_changed' or
4832 _op == 'print_decimal_changed'):
4833 if len(args) < 2:
4834 raise ValueError, "Invalid argument count: %d" % _alen
4835 _val = args[1]
4836 _get = DimStringLog.__getops[_op]
4837 _sdata = _get(_obj)
4838 self.ignore(_op)
4839 try:
4840 _set = DimStringLog.__setops[_op]
4841 if undo:
4842 _obj.startUndo()
4843 try:
4844 _set(_obj, _val)
4845 finally:
4846 _obj.endUndo()
4847 else:
4848 _obj.startRedo()
4849 try:
4850 _set(_obj, _val)
4851 finally:
4852 _obj.endRedo()
4853 finally:
4854 self.receive(_op)
4855 self.saveData(undo, _op, _sdata)
4856 elif _op == 'dimension_changed':
4857 pass # fixme
4858 else:
4859 super(DimStringLog, self).execute(undo, *args)
4862 # Dimension history class
4865 class DimLog(entity.EntityLog):
4866 __setops = {
4867 'offset_changed' : Dimension.setOffset,
4868 'extension_changed' : Dimension.setExtension,
4869 'endpoint_type_changed' : Dimension.setEndpointType,
4870 'endpoint_size_changed' : Dimension.setEndpointSize,
4871 'dual_mode_changed' : Dimension.setDualDimMode,
4872 'dual_mode_offset_changed' : Dimension.setDualModeOffset,
4873 'position_changed' : Dimension.setPosition,
4874 'position_offset_changed' : Dimension.setPositionOffset,
4875 'thickness_changed' : Dimension.setThickness,
4876 'scale_changed' : Dimension.setScale,
4877 'dia_mode_changed' : RadialDimension.setDiaMode,
4880 __getops = {
4881 'offset_changed' : Dimension.getOffset,
4882 'extension_changed' : Dimension.getExtension,
4883 'endpoint_type_changed' : Dimension.getEndpointType,
4884 'endpoint_size_changed' : Dimension.getEndpointSize,
4885 'dual_mode_changed' : Dimension.getDualDimMode,
4886 'dual_mode_offset_changed' : Dimension.getDualModeOffset,
4887 'position_changed' : Dimension.getPosition,
4888 'position_offset_changed' : Dimension.getPositionOffset,
4889 'thickness_changed' : Dimension.getThickness,
4890 'scale_changed' : Dimension.getScale,
4891 'dia_mode_changed' : RadialDimension.getDiaMode,
4894 def __init__(self, dim):
4895 if not isinstance(dim, Dimension):
4896 raise TypeError, "Invalid dimension type: " + `type(dim)`
4897 super(DimLog, self).__init__(dim)
4898 _ds1, _ds2 = dim.getDimstrings()
4899 _ds1.connect('modified', self._dimstringChanged)
4900 _ds2.connect('modified', self._dimstringChanged)
4901 dim.connect('offset_changed', self._offsetChanged)
4902 dim.connect('extension_changed', self._extensionChanged)
4903 # dim.connect('dimstyle_changed', self._dimstyleChanged)
4904 dim.connect('endpoint_type_changed', self._endpointTypeChanged)
4905 dim.connect('endpoint_size_changed', self._endpointSizeChanged)
4906 dim.connect('dual_mode_changed', self._dualModeChanged)
4907 dim.connect('dual_mode_offset_changed', self._dualModeOffsetChanged)
4908 dim.connect('position_changed', self._positionChanged)
4909 dim.connect('position_offset_changed', self._positionOffsetChanged)
4910 dim.connect('color_changed', self._colorChanged)
4911 dim.connect('thickness_changed', self._thicknessChanged)
4912 dim.connect('scale_changed', self._scaleChanged)
4913 dim.connect('location_changed', self._locationChanged)
4914 if not isinstance(dim, RadialDimension):
4915 dim.connect('point_changed', self._pointChanged)
4916 if isinstance(dim, RadialDimension):
4917 dim.connect('dia_mode_changed', self._diaModeChanged)
4918 dim.connect('dimobj_changed', self._dimObjChanged)
4919 if isinstance(dim, AngularDimension):
4920 dim.connect('inverted', self._dimInverted)
4921 # dim.connect('moved', self._moveDim)
4923 def detatch(self):
4924 _dim = self.getObject()
4925 super(DimLog, self).detatch()
4926 _ds1, _ds2 = _dim.getDimstrings()
4927 _ds1.disconnect(self)
4928 _log = _ds1.getLog()
4929 if _log is not None:
4930 _log.detatch()
4931 _ds1.setLog(None)
4932 _ds2.disconnect(self)
4933 _log = _ds2.getLog()
4934 if _log is not None:
4935 _log.detatch()
4936 _ds2.setLog(None)
4938 def _moveDim(self, dim, *args):
4939 _alen = len(args)
4940 if _alen < 4:
4941 raise ValueError, "Invalid argument count: %d" % _alen
4942 _xmin = util.get_float(args[0])
4943 _ymin = util.get_float(args[1])
4944 _xmax = util.get_float(args[2])
4945 _ymax = util.get_float(args[3])
4946 self.saveUndoData('moved', _xmin, _ymin, _xmax, _ymax)
4948 def _dimstringChanged(self, dstr, *args):
4949 _dim = self.getObject()
4950 _ds1, _ds2 = _dim.getDimstrings()
4951 if dstr is _ds1:
4952 _dstr = 'ds1'
4953 elif dstr is _ds2:
4954 _dstr = 'ds2'
4955 else:
4956 raise ValueError, "Unexpected Dimstring: " + `dstr`
4957 self.saveUndoData('dimstring_changed', _dstr)
4958 _dim.modified()
4960 def _endpointTypeChanged(self, dim, *args):
4961 _alen = len(args)
4962 if _alen < 1:
4963 raise ValueError, "Invalid argument count: %d" % _alen
4964 _ep = args[0]
4965 if (_ep != Dimension.DIM_ENDPT_NONE and
4966 _ep != Dimension.DIM_ENDPT_ARROW and
4967 _ep != Dimension.DIM_ENDPT_FILLED_ARROW and
4968 _ep != Dimension.DIM_ENDPT_SLASH and
4969 _ep != Dimension.DIM_ENDPT_CIRCLE):
4970 raise ValueError, "Invalid endpoint value: '%s'" % str(_ep)
4971 self.saveUndoData('endpoint_type_changed', _ep)
4973 def _endpointSizeChanged(self, dim, *args):
4974 _alen = len(args)
4975 if _alen < 1:
4976 raise ValueError, "Invalid argument count: %d" % _alen
4977 _size = args[0]
4978 if not isinstance(_size, float):
4979 raise TypeError, "Unexpected type for size: " + `type(_size)`
4980 if _size < 0.0:
4981 raise ValueError, "Invalid endpoint size: %g" % _size
4982 self.saveUndoData('endpoint_size_changed', _size)
4984 def _dualModeChanged(self, dim, *args):
4985 _alen = len(args)
4986 if _alen < 1:
4987 raise ValueError, "Invalid argument count: %d" % _alen
4988 _flag = args[0]
4989 util.test_boolean(_flag)
4990 self.saveUndoData('dual_mode_changed', _flag)
4992 def _dualModeOffsetChanged(self, dim, *args):
4993 _alen = len(args)
4994 if _alen < 1:
4995 raise ValueError, "Invalid argument count: %d" % _alen
4996 _offset = args[0]
4997 if not isinstance(_offset, float):
4998 raise TypeError, "Unexpected type for flag: " + `type(_offset)`
4999 if _offset < 0.0:
5000 raise ValueError, "Invalid dual mode offset: %g" % _offset
5001 self.saveUndoData('dual_mode_offset_changed', _offset)
5003 def _positionChanged(self, dim, *args):
5004 _alen = len(args)
5005 if _alen < 1:
5006 raise ValueError, "Invalid argument count: %d" % _alen
5007 _pos = args[0]
5008 if (_pos != Dimension.DIM_TEXT_POS_SPLIT and
5009 _pos != Dimension.DIM_TEXT_POS_ABOVE and
5010 _pos != Dimension.DIM_TEXT_POS_BELOW):
5011 raise ValueError, "Invalid position: '%s'" % str(_pos)
5012 self.saveUndoData('position_changed', _pos)
5014 def _positionOffsetChanged(self, dim, *args):
5015 _alen = len(args)
5016 if _alen < 1:
5017 raise ValueError, "Invalid argument count: %d" % _alen
5018 _offset = args[0]
5019 if not isinstance(_offset, float):
5020 raise TypeError, "Unexpected type for offset: " + `type(_offset)`
5021 if _offset < 0.0:
5022 raise ValueError, "Invalid position offset: %g" % _offset
5023 self.saveUndoData('position_offset_changed', _offset)
5025 def _thicknessChanged(self, dim, *args):
5026 _alen = len(args)
5027 if _alen < 1:
5028 raise ValueError, "Invalid argument count: %d" % _alen
5029 _t = args[0]
5030 if not isinstance(_t, float):
5031 raise TypeError, "Unexpected type for thickness" + `type(_t)`
5032 if _t < 0.0:
5033 raise ValueError, "Invalid thickness: %g" % _t
5034 self.saveUndoData('thickness_changed', _t)
5036 def _scaleChanged(self, dim, *args):
5037 _alen = len(args)
5038 if _alen < 1:
5039 raise ValueError, "Invalid argument count: %d" % _alen
5040 _s = args[0]
5041 if not isinstance(_s, float):
5042 raise TypeError, "Unexpected type for scale" + `type(_s)`
5043 if not _s > 0.0:
5044 raise ValueError, "Invalid scale: %g" % _s
5045 self.saveUndoData('scale_changed', _s)
5047 def _colorChanged(self, dim, *args):
5048 _alen = len(args)
5049 if _alen < 1:
5050 raise ValueError, "Invalid argument count: %d" % _alen
5051 _color = args[0]
5052 if not isinstance(_color, color.Color):
5053 raise TypeError, "Invalid color: " + str(_color)
5054 self.saveUndoData('color_changed', _color.getColors())
5056 def _offsetChanged(self, dim, *args):
5057 _alen = len(args)
5058 if _alen < 1:
5059 raise ValueError, "Invalid argument count: %d" % _alen
5060 _offset = args[0]
5061 if not isinstance(_offset, float):
5062 raise TypeError, "Unexpected type for offset: " + `type(_offset)`
5063 if _offset < 0.0:
5064 raise ValueError, "Invalid offset: %g" % _offset
5065 self.saveUndoData('offset_changed', _offset)
5067 def _extensionChanged(self, dim, *args):
5068 _alen = len(args)
5069 if _alen < 1:
5070 raise ValueError, "Invalid argument count: %d" % _alen
5071 _extlen = args[0]
5072 if not isinstance(_extlen, float):
5073 raise TypeError, "Unexpected type for length: " + `type(_extlen)`
5074 if _extlen < 0.0:
5075 raise ValueError, "Invalid extension length: %g" % _extlen
5076 self.saveUndoData('extension_changed', _extlen)
5078 def _diaModeChanged(self, dim, *args): # RadialDimensions
5079 _alen = len(args)
5080 if _alen < 1:
5081 raise ValueError, "Invalid argument count: %d" % _alen
5082 _flag = args[0]
5083 util.test_boolean(_flag)
5084 self.saveUndoData('dia_mode_changed', _flag)
5086 def _dimInverted(self, dim, *args): # AngularDimensions
5087 self.saveUndoData('inverted')
5089 def _dimstyleChanged(self, dim, *args):
5090 _alen = len(args)
5091 if _alen < 2:
5092 raise ValueError, "Invalid argument count: %d" % _alen
5093 _ds = args[0]
5094 if not isinstance(_ds, DimStyle):
5095 raise TypeError, "Invalid DimStyle type: " + `type(_ds)`
5096 _opts = args[1]
5097 if not isinstance(_opts, dict):
5098 raise TypeError, "Invalid option type: " + `type(_opts)`
5099 _data = {}
5100 _data['dimstyle'] = _ds.getValues()
5101 _data.update(_opts)
5102 self.saveUndoData('dimstyle_changed', _data)
5104 def _locationChanged(self, dim, *args):
5105 _alen = len(args)
5106 if _alen < 2:
5107 raise ValueError, "Invalid argument count: %d" % _alen
5108 _x = args[0]
5109 if not isinstance(_x, float):
5110 raise TypeError, "Unexpected type for x location" + `type(_x)`
5111 _y = args[1]
5112 if not isinstance(_y, float):
5113 raise TypeError, "Unexpected type for y location" + `type(_y)`
5114 self.saveUndoData('location_changed', _x, _y)
5116 def _pointChanged(self, dim, *args):
5117 _alen = len(args)
5118 if _alen < 2:
5119 raise ValueError, "Invalid argument count: %d" % _alen
5120 _op = args[0]
5121 if not isinstance(_op, point.Point):
5122 raise TypeError, "Unexpected type for old point" + `type(_op)`
5123 _np = args[1]
5124 if not isinstance(_np, point.Point):
5125 raise TypeError, "Unexpected type for new point" + `type(_np)`
5126 _ol = _op.getParent()
5127 if _ol is None:
5128 raise RuntimeError, "Invalid Parent for replaced Point" + `_op`
5129 _olid = _ol.getID()
5130 _oid = _op.getID()
5131 _nl = _np.getParent()
5132 if _nl is None:
5133 raise RuntimeError, "Invalid Parent for new Point" + `_np`
5134 _nlid = _nl.getID()
5135 _nid = _np.getID()
5136 self.saveUndoData('point_changed', _olid, _oid, _nlid, _nid)
5138 def _dimObjChanged(self, dim, *args):
5139 _alen = len(args)
5140 if _alen < 2:
5141 raise ValueError, "Invalid argument count: %d" % _alen
5142 _oo = args[0]
5143 if not isinstance(_oo, (circle.Circle, arc.Arc)):
5144 raise TypeError, "Unexpected type for old object" + `type(_oo)`
5145 _no = args[1]
5146 if not isinstance(_no, (circle.Circle, arc.Arc)):
5147 raise TypeError, "Unexpected type for new object" + `type(_no)`
5148 _ol = _oo.getParent()
5149 if _ol is None:
5150 raise RuntimeError, "Invalid Parent for replaced object" + `_oo`
5151 _olid = _ol.getID()
5152 _oid = _oo.getID()
5153 _nl = _no.getParent()
5154 if _nl is None:
5155 raise RuntimeError, "Invalid Parent for new object" + `_no`
5156 _nlid = _nl.getID()
5157 _nid = _no.getID()
5158 self.saveUndoData('dimobj_changed', _olid, _oid, _nlid, _nid)
5160 def execute(self, undo, *args):
5161 util.test_boolean(undo)
5162 _alen = len(args)
5163 if len(args) == 0:
5164 raise ValueError, "No arguments to execute()"
5165 _dim = self.getObject()
5166 _image = None
5167 _layer = _dim.getParent()
5168 if _layer is not None:
5169 _image = _layer.getParent()
5170 _op = args[0]
5171 if (_op == 'offset_changed' or
5172 _op == 'extension_changed' or
5173 _op == 'endpoint_type_changed' or
5174 _op == 'endpoint_size_changed' or
5175 _op == 'dual_mode_changed' or
5176 _op == 'dual_mode_offset_changed' or
5177 _op == 'dia_mode_changed' or
5178 _op == 'thickness_changed' or
5179 _op == 'scale_changed' or
5180 _op == 'position_changed' or
5181 _op == 'position_offset_changed'):
5182 if len(args) < 2:
5183 raise ValueError, "Invalid argument count: %d" % _alen
5184 _val = args[1]
5185 _get = DimLog.__getops[_op]
5186 _sdata = _get(_dim)
5187 self.ignore(_op)
5188 try:
5189 _set = DimLog.__setops[_op]
5190 if undo:
5191 _dim.startUndo()
5192 try:
5193 _set(_dim, _val)
5194 finally:
5195 _dim.endUndo()
5196 else:
5197 _dim.startRedo()
5198 try:
5199 _set(_dim, _val)
5200 finally:
5201 _dim.endRedo()
5202 finally:
5203 self.receive(_op)
5204 self.saveData(undo, _op, _sdata)
5205 elif _op == 'color_changed':
5206 if _image is None:
5207 raise RuntimeError, "Dimension not stored in an Image"
5208 if _alen < 2:
5209 raise ValueError, "Invalid argument count: %d" % _alen
5210 _sdata = _dim.getColor().getColors()
5211 self.ignore(_op)
5212 try:
5213 _color = None
5214 for _c in _image.getImageEntities('color'):
5215 if _c.getColors() == args[1]:
5216 _color = _c
5217 break
5218 if undo:
5219 _dim.startUndo()
5220 try:
5221 _dim.setColor(_color)
5222 finally:
5223 _dim.endUndo()
5224 else:
5225 _dim.startRedo()
5226 try:
5227 _dim.setColor(_color)
5228 finally:
5229 _dim.endRedo()
5230 finally:
5231 self.receive(_op)
5232 self.saveData(undo, _op, _sdata)
5233 elif _op == 'location_changed':
5234 if _alen < 3:
5235 raise ValueError, "Invalid argument count: %d" % _alen
5236 _x = args[1]
5237 if not isinstance(_x, float):
5238 raise TypeError, "Unexpected type for 'x': " + `type(_x)`
5239 _y = args[2]
5240 if not isinstance(_y, float):
5241 raise TypeError, "Unexpected type for 'y': " + `type(_y)`
5242 _dx, _dy = _dim.getLocation()
5243 self.ignore(_op)
5244 try:
5245 if undo:
5246 _dim.startUndo()
5247 try:
5248 _dim.setLocation(_x, _y)
5249 finally:
5250 _dim.endUndo()
5251 else:
5252 _dim.startRedo()
5253 try:
5254 _dim.setLocation(_x, _y)
5255 finally:
5256 _dim.endRedo()
5257 finally:
5258 self.receive(_op)
5259 self.saveData(undo, _op, _dx, _dy)
5260 elif _op == 'point_changed':
5261 if _image is None:
5262 raise RuntimeError, "Dimension not stored in an Image"
5263 if _alen < 5:
5264 raise ValueError, "Invalid argument count: %d" % _alen
5265 _olid = args[1]
5266 _oid = args[2]
5267 _nlid = args[3]
5268 _nid = args[4]
5269 if undo:
5270 _lid = _olid
5271 else:
5272 _lid = _nlid
5273 _parent = None
5274 _layers = [_image.getTopLayer()]
5275 while len(_layers):
5276 _layer = _layers.pop()
5277 if _lid == _layer.getID():
5278 _parent = _layer
5279 break
5280 _layers.extend(_layer.getSublayers())
5281 if _parent is None:
5282 raise RuntimeError, "Parent layer of old point not found"
5283 self.ignore(_op)
5284 try:
5285 _vp = None
5286 _p1 = _dim.getP1()
5287 _p2 = _dim.getP2()
5288 if isinstance(_dim, AngularDimension):
5289 _vp = _dim.getVertexPoint()
5290 if undo:
5291 _pt = _parent.getObject(_oid)
5292 if _pt is None or not isinstance(_pt, point.Point):
5293 raise ValueError, "Old endpoint missing: id=%d" % _oid
5294 _dim.startUndo()
5295 try:
5296 if _p1.getID() == _nid:
5297 _dim.setP1(_pt)
5298 elif _p2.getID() == _nid:
5299 _dim.setP2(_pt)
5300 elif _vp is not None and _vp.getID() == _nid:
5301 _dim.setVertexPoint(_pt)
5302 else:
5303 raise ValueError, "Unexpected point ID: %d" % _nid
5304 finally:
5305 _dim.endUndo()
5306 else:
5307 _pt = _parent.getObject(_nid)
5308 if _pt is None or not isinstance(_pt, point.Point):
5309 raise ValueError, "New endpoint missing: id=%d" % _nid
5310 _dim.startRedo()
5311 try:
5312 if _p1.getID() == _oid:
5313 _dim.setP1(_pt)
5314 elif _p2.getID() == _oid:
5315 _dim.setP2(_pt)
5316 elif _vp is not None and _vp.getID() == _oid:
5317 _dim.setVertexPoint(_pt)
5318 else:
5319 raise ValueError, "Unexpected point ID: %d" % _oid
5320 finally:
5321 _dim.endRedo()
5322 finally:
5323 self.receive(_op)
5324 self.saveData(undo, _op, _olid, _oid, _nlid, _nid)
5325 elif _op == 'dimobj_changed':
5326 if _image is None:
5327 raise RuntimeError, "Dimension not stored in an Image"
5328 if _alen < 5:
5329 raise ValueError, "Invalid argument count: %d" % _alen
5330 _olid = args[1]
5331 _oid = args[2]
5332 _nlid = args[3]
5333 _nid = args[4]
5334 if undo:
5335 _lid = _olid
5336 else:
5337 _lid = _nlid
5338 _parent = None
5339 _layers = [_image.getTopLayer()]
5340 while len(_layers):
5341 _layer = _layers.pop()
5342 if _lid == _layer.getID():
5343 _parent = _layer
5344 break
5345 _layers.extend(_layer.getSublayers())
5346 if _parent is None:
5347 raise RuntimeError, "Parent layer of old object not found"
5348 self.ignore(_op)
5349 try:
5350 if undo:
5351 _oo = _parent.getObject(_oid)
5352 if _oo is None or not isinstance(_oo, (circle.Circle,
5353 arc.Arc)):
5354 raise ValueError, "Old object missing: id=%d" % _oid
5355 _dim.startUndo()
5356 try:
5357 _dim.setDimCircle(_oo)
5358 finally:
5359 _dim.endUndo()
5360 else:
5361 _no = _parent.getObject(_nid)
5362 if _no is None or not isinstance(_no, (circle.Circle,
5363 arc.Arc)):
5364 raise ValueError, "New object missing: id=%d" % _nid
5365 _dim.startRedo()
5366 try:
5367 _dim.setDimCircle(_no)
5368 finally:
5369 _dim.endRedo()
5370 finally:
5371 self.receive(_op)
5372 self.saveData(undo, _op, _olid, _oid, _nlid, _nid)
5373 elif _op == 'dimstring_changed':
5374 if _alen < 2:
5375 raise ValueError, "Invalid argument count: %d" % _alen
5376 _dstr = args[1]
5377 _ds1, _ds2 = _dim.getDimstrings()
5378 self.ignore('modified')
5379 try:
5380 if _dstr == 'ds1':
5381 if undo:
5382 _ds1.undo()
5383 else:
5384 _ds1.redo()
5385 elif _dstr == 'ds2':
5386 if undo:
5387 _ds2.undo()
5388 else:
5389 _ds2.redo()
5390 else:
5391 raise ValueError, "Unexpected dimstring key: " + str(_dstr)
5392 finally:
5393 self.receive('modified')
5394 self.saveData(undo, _op, _dstr)
5395 if not undo:
5396 _dim.modified()
5397 elif _op == 'inverted': # AngularDimensions only
5398 self.ignore(_op)
5399 try:
5400 if undo:
5401 _dim.startUndo()
5402 try:
5403 _dim.invert()
5404 finally:
5405 _dim.endUndo()
5406 else:
5407 _dim.startRedo()
5408 try:
5409 _dim.invert()
5410 finally:
5411 _dim.endRedo()
5412 finally:
5413 self.receive(_op)
5414 self.saveData(undo, _op)
5415 else:
5416 super(DimLog, self).execute(undo, *args)