#
# Copyright (c) 2002, 2003, 2004 Art Haas
#
# This file is part of PythonCAD.
#
# PythonCAD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PythonCAD is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# generic dimension classes
#

import math
import sys
import types

import text
import point
import circle
import arc
import color
import layer
import units
import util
import tolerance
import options
import baseobject
import entity
import logger

_dtr = math.pi/180.0
_rtd = 180.0/math.pi

class DimString(text.TextFormat):
    """A class for the visual presentation of the dimensional value.

The DimString class is used to present the numerical display for
the dimension. A DimString object is derived from the text.TextFormat
class, so it shares all of that classes methods and attributes.

A DimString class has the following methods

{get/set}Prefix(): Get/Set the text preceding the dimension value.
{get/set}Suffix(): Get/Set the text following the dimension value.
{get/set}Units(): Define what units the dimension will be in.
{get/set}Precision(): Get/Set how many fractional digits are displayed.
{get/set}PrintZero(): Get/Set whether or not a dimension less than one
                      unit long should have a leading 0 printed
{get/set}PrintDecimal(): Get/Set whether or not a dimension with 0
                         fractional digits prints out the decimal point.
    """

    messages = {
        'attribute_changed' : True,
        }

    def __init__(self, **kwargs):
        """Instatiate a DimString object

ds = DimString()

By default, a DimString object has the following values ...

prefix: Empty string
suffix: Empty string
unit: Millimeters
precision: 3 decimal places
print zero: True
print decimal: True
        """
        text.TextFormat.__init__(self)
        self.__prefix = u''
        self.__suffix = u''
        self.__unit = units.Unit(units.MILLIMETERS)
        self.__precision = 3
        self.__print_zero = True
        self.__print_decimal = True
        self.mute()
        for _k, _v in kwargs.items():
            if _k == 'prefix':
                self.setPrefix(_v)
            elif _k == 'suffix':
                self.setSuffix(_v)
            elif _k == 'unit':
                self.setUnits(_v)
            elif _k == 'precision':
                self.setPrecision(_v)
            elif _k == 'print_zero':
                self.setPrintZero(_v)
            elif _k == 'print_decimal':
                self.setPrintDecimal(_v)
            else:
                raise KeyError, "Unknown keyword argument: %s" % _k
        self.reset()
        self.unmute()

    def getValues(self):
        return (self.__prefix, self.__suffix, self.__unit.getStringUnit(),
                self.__precision, self.__print_zero, self.__print_decimal)
                
    def getPrefix(self):
        """Return the prefix for the DimString object.

getPrefix()
        """
        return self.__prefix

    def setPrefix(self, prefix=u''):
        """Set the prefix for the DimString object.

setPrefix([prefix])

Invoking this method without arguments sets the prefix
to an empty string. When an argument is passed, the argument
should be a Unicode string.
        """
        if self.isLocked():
            raise RuntimeError, "Setting prefix not allowed - object locked."
        _p = prefix
        if not isinstance(_p, unicode):
            _p = unicode(prefix)
        _op = self.__prefix
        if _op != _p:
            self.__prefix = _p
            self.sendMessage('attribute_changed', 'prefix', _op)
            self.modified()

    prefix = property(getPrefix, setPrefix, None, "Dimension string prefix")

    def getSuffix(self):
        """Return the suffix for the DimString object.

getSuffix()
        """
        return self.__suffix

    def setSuffix(self, suffix=u''):
        """Set the suffix for the DimString object.

setSuffix([suffix])

Invoking this method without arguments sets the suffix
to an empty string. When an argument is passed, the argument
should be a Unicode string.
        """
        if self.isLocked():
            raise RuntimeError, "Setting suffix not allowed - object locked."
        _s = suffix
        if not isinstance(_s, unicode):
            _s = unicode(suffix)
        _os = self.__suffix
        if _os != _s:
            self.__suffix = _s
            self.sendMessage('attribute_changed', 'suffix', _os)
            self.modified()

    suffix = property(getSuffix, setSuffix, None, "Dimension string suffix")

    def getPrecision(self):
        """Return the number of decimal points used for the DimString.

getPrecision()
        """
        return self.__precision

    def setPrecision(self, p=3):
        """Set the number of decimal points used for the DimString.

setPrecision([p])

The valid range of p is 0 <= p <= 15. Invoking this method without
arguments sets the precision to 3.
        """
        if self.isLocked():
            raise RuntimeError, "Setting precision not allowed - object locked."
        _p = p
        if not isinstance(_p, int):
            _p = int(p)
        if _p < 0 or _p > 15:
            raise ValueError, "Invalid precision: %d" % _p
        _op = self.__precision
        if _op != _p:
            self.__precision = _p
            self.sendMessage('attribute_changed', 'precision', _p)
            self.modified()

    precision = property(getPrecision, setPrecision, None,
                         "Dimension precision")

    def getUnits(self):
        """Return the current units used in the DimString().

getUnits()
        """
        return self.__unit.getUnit()

    def setUnits(self, unit=units.MILLIMETERS):
        """The the units for the DimString.

setUnits([unit])

The value units are given in the units module. Invoking this
method without arguments sets the units to millimeters.
        """
        _u = self.__unit.getUnit()
        self.__unit.setUnit(unit)
        self.sendMessage('attribute_changed', 'units', _u)
        self.modified()

    units = property(getUnits, setUnits, None, "Dimensional units.")

    def getPrintZero(self):
        """Return whether or not a leading 0 is printed for the DimString.

getPrintZero()
        """
        return self.__print_zero

    def setPrintZero(self, pz=True):
        """Set whether or not a leading 0 is printed for the DimString.

setPrintZero([pz])

Invoking this method without arguments sets the value to True.
If called with an argument, the argument should be either True
or False.
        """
        if pz is not True and pz is not False:
            raise ValueError, "Invalid zero print flag: " + `pz`
        _flag = self.__print_zero
        if _flag is not pz:
            self.__print_zero = pz
            self.sendMessage('attribute_changed', 'print_zero', _flag)
            self.modified()

    print_zero = property(getPrintZero, setPrintZero, None,
                          "Print a leading 0 for decimal dimensions")

    def getPrintDecimal(self):
        """Return whether or not the DimString will print a trailing decimal.

getPrintDecimal()
        """
        return self.__print_decimal

    def setPrintDecimal(self, pd=True):
        """Set whether or not the DimString will print a trailing decimal.

setPrintDecimal([pd])

Invoking this method without arguments sets the value to True.
If called with an argument, the argument should be either True
or False.
        """
        if pd is not True and pd is not False:
            raise ValueError, "Invalid print decimal flag: " + `pd`
        _flag = self.__print_decimal
        if _flag is not pd:
            self.__print_decimal = pd
            self.sendMessage('attribute_changed', 'print_decimal', _flag)
            self.modified()

    print_decimal = property(getPrintDecimal, setPrintDecimal, None,
                             "Print a decimal point after the dimension value")

    def formatDimension(self, dist):
        """Return a formatted numerical value for a dimension.

formatDimension(dist)

The argument "dist" should be a float value representing the
distance in millimeters. The returned value will have the
prefix prepended and suffix appended to the numerical value
that has been formatted with the precision.
        """
        _d = dist
        if not isinstance(_d, float):
            _d = float(dist)
        _d = abs(_d)
        _fmtstr = u"%%#.%df" % self.__precision
        _dstr = _fmtstr % self.__unit.fromMillimeters(_d)
        if _d < 1.0 and self.__print_zero is False:
            _dstr = _dstr[1:]
        if _dstr.endswith('.') and self.__print_decimal is False:
            _dstr = _dstr[:-1]
        return self.__prefix + _dstr + self.__suffix

    def sendsMessage(self, m):
        if m in DimString.messages:
            return True
        return text.TextFormat.sendsMessage(self, m)

class DimBar(entity.Entity):
    """The class for the dimension bar.

A dimension bar leads from the point the dimension references
out to, and possibly beyond, the point where the dimension
text bar the DimBar to another DimBar. Linear,
horizontal, vertical, and angular dimension will have two
dimension bars; radial dimensions have none.

The DimBar class has the following methods:

getEndpoints(): Get the x/y position of the DimBar start and end
{get/set}FirstEndpoint(): Get/Set the starting x/y position of the DimBar.
{get/set}SecondEndpoint(): Get/Set the ending x/y position of the DimBar.
getAngle(): Get the angle at which the DimBar slopes
getSinCosValues(): Get trig values used for transformation calculations.
    """

    messages = {
        'attribute_changed' : True,
        }
    def __init__(self, x1=0.0, y1=0.0, x2=0.0, y2=0.0, **kw):
        """Initialize a DimBar.

db = DimBar([x1, y1, x2, y2])

By default all the arguments are 0.0. Any arguments passed to this
method should be float values.
        """
        _x1 = x1
        if not isinstance(_x1, float):
            _x1 = float(x1)
        _y1 = y1
        if not isinstance(_y1, float):
            _y1 = float(y1)
        _x2 = x2
        if not isinstance(_x2, float):
            _x2 = float(x2)
        _y2 = y2
        if not isinstance(_y2, float):
            _y2 = float(y2)
        entity.Entity.__init__(self, **kw)
        self.__ex1 = _x1
        self.__ey1 = _y1
        self.__ex2 = _x2
        self.__ey2 = _y2

    def getEndpoints(self):
        """Return the coordinates of the DimBar endpoints.

getEndpoints()

This method returns two tuples, each containing two float values.
The first tuple gives the x/y coordinates of the DimBar start,
the second gives the coordinates of the DimBar end.
        """
        _ep1 = (self.__ex1, self.__ey1)
        _ep2 = (self.__ex2, self.__ey2)
        return _ep1, _ep2

    def setFirstEndpoint(self, x, y):
        """Set the starting coordinates for the DimBar

setFirstEndpoint(x, y)

Arguments x and y should be float values.
        """
        if self.isLocked():
            raise RuntimeError, "Setting endpoint not allowed - object locked."
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _sx = self.__ex1
        _sy = self.__ey1
        if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
            self.__ex1 = _x
            self.__ey1 = _y
            self.sendMessage('attribute_changed', 'endpoint', _sx, _sy,
                             self.__ex2, self.__ey2)
            self.modified()

    def getFirstEndpoint(self):
        """Return the starting coordinates of the DimBar.

getFirstEndpoint()

This method returns a tuple giving the x/y coordinates.
        """
        return self.__ex1, self.__ey1

    def setSecondEndpoint(self, x, y):
        """Set the ending coordinates for the DimBar

setSecondEndpoint(x, y)

Arguments x and y should be float values.
        """
        if self.isLocked():
            raise RuntimeError, "Setting endpoint not allowed - object locked."
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _sx = self.__ex2
        _sy = self.__ey2
        if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
            self.__ex2 = _x
            self.__ey2 = _y
            self.sendMessage('attribute_changed', 'endpoint',
                             self.__ex1, self.__ey1, _sx, _sy)
            self.modified()

    def getSecondEndpoint(self):
        """Return the ending coordinates of the DimBar.

getSecondEndpoint()

This method returns a tuple giving the x/y coordinates.
        """
        return self.__ex2, self.__ey2

    def getAngle(self):
        """Return the angle at which the DimBar lies.

getAngle()

This method returns a float value giving the angle of inclination
of the DimBar.

The value returned will be a positive value less than 360.0.
        """
        _x1 = self.__ex1
        _y1 = self.__ey1
        _x2 = self.__ex2
        _y2 = self.__ey2
        if abs(_x2 - _x1) < 1e-10 and abs(_y2 - _y1) < 1e-10:
            raise ValueError, "Endpoints are equal"
        if abs(_x2 - _x1) < 1e-10: # vertical
            if _y2 > _y1:
                _angle = 90.0
            else:
                _angle = 270.0
        elif abs(_y2 - _y1) < 1e-10: # horizontal
            if _x2 > _x1:
                _angle = 0.0
            else:
                _angle = 180.0
        else:
            _angle = _rtd * math.atan2((_y2 - _y1), (_x2 - _x1))
            if _angle < 0.0:
                _angle = _angle + 360.0
        return _angle

    def getSinCosValues(self):
        """Return sin()/cos() values based on the DimBar slope

getSinCosValues()

This method returns a tuple of two floats. The first value is
the sin() value, the second is the cos() value.
        """
        _x1 = self.__ex1
        _y1 = self.__ey1
        _x2 = self.__ex2
        _y2 = self.__ey2
        if abs(_x2 - _x1) < 1e-10: # vertical
            _cosine = 0.0
            if _y2 > _y1:
                _sine = 1.0
            else:
                _sine = -1.0
        elif abs(_y2 - _y1) < 1e-10: # horizontal
            _sine = 0.0
            if _x2 > _x1:
                _cosine = 1.0
            else:
                _cosine = -1.0
        else:
            _angle = math.atan2((_y2 - _y1), (_x2 - _x1))
            _sine = math.sin(_angle)
            _cosine = math.cos(_angle)
        return _sine, _cosine

    def sendsMessage(self, m):
        if m in DimBar.messages:
            return True
        return entity.Entity.sendsMessage(self, m)

class DimCrossbar(DimBar):
    """The class for the Dimension crossbar.

The DimCrossbar class is drawn between two DimBar objects for
horizontal, vertical, and generic linear dimensions. The dimension
text is place over the DimCrossbar object. Arrow heads, circles, or
slashes can be drawn at the intersection of the DimCrossbar and
the DimBar if desired. These objects are called markers.

The DimCrossbar class is derived from the DimBar class so it shares
all the methods of that class. In addition the DimCrossbar class has
the following methods:

{set/get}FirstCrossbarPoint(): Set/Get the initial location of the crossbar.
{set/get}SecondCrossbarPoint(): Set/Get the ending location of the crossbar.
getCrossbarPoints(): Get the location of the crossbar endpoints.
clearMarkerPoints(): Delete the stored coordintes of the dimension markers.
storeMarkerPoint(): Save a coordinate pair of the dimension marker.
getMarkerPoints(): Return the coordinates of the dimension marker.
    """
    messages = {}
    def __init__(self, **kw):
        """Initialize a DimCrossbar object.

dcb = DimCrossbar()

This method takes no arguments.
        """
        DimBar.__init__(self, **kw)
        self.__mx1 = 0.0
        self.__my1 = 0.0
        self.__mx2 = 0.0
        self.__my2 = 0.0
        self.__mpts = []

    def setFirstCrossbarPoint(self, x, y):
        """Store the initial endpoint of the DimCrossbar.

setFirstCrossbarPoint(x, y)

Arguments x and y should be floats.
        """
        if self.isLocked():
            raise RuntimeError, "Setting crossbar point not allowed - object locked."
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _sx = self.__mx1
        _sy = self.__my1
        if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
            self.__mx1 = _x
            self.__my1 = _y
            self.sendMessage('attribute_changed', 'barpoint', _sx, _sy,
                             self.__mx2, self.__my2)
            self.modified()

    def getFirstCrossbarPoint(self):
        """Return the initial coordinates of the DimCrossbar.

getFirstCrossbarPoint()

This method returns a tuple of two floats giving the x/y coordinates.
        """
        return self.__mx1, self.__my1

    def setSecondCrossbarPoint(self, x, y):
        """Store the terminal endpoint of the DimCrossbar.

setSecondCrossbarPoint(x, y)

Arguments x and y should be floats.
        """
        if self.isLocked():
            raise RuntimeError, "Setting crossbar point not allowed - object locked"
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _sx = self.__mx2
        _sy = self.__my2
        if abs(_sx - _x) > 1e-10 or abs(_sy - _y) > 1e-10:
            self.__mx2 = _x
            self.__my2 = _y
            self.sendMessage('attribute_changed', 'barpoint',
                             self.__mx1, self.__my1, _sx, _sy)
            self.modified()

    def getSecondCrossbarPoint(self):
        """Return the terminal coordinates of the DimCrossbar.

getSecondCrossbarPoint()

This method returns a tuple of two floats giving the x/y coordinates.
        """
        return self.__mx2, self.__my2

    def getCrossbarPoints(self):
        """Return the endpoints of the DimCrossbar.

getCrossbarPoints()

This method returns two tuples, each tuple containing two float
values giving the x/y coordinates.
        """
        _mp1 = (self.__mx1, self.__my1)
        _mp2 = (self.__mx2, self.__my2)
        return _mp1, _mp2

    def clearMarkerPoints(self):
        """Delete the stored location of any dimension markers.

clearMarkerPoints()
        """
        del self.__mpts[:]

    def storeMarkerPoint(self, x, y):
        """Save a coordinate pair of the current dimension marker.

storeMarkerPoint(x, y)

Arguments "x" and "y" should be floats. Each time this method is invoked
the list of stored coordinates is appended with the values given as
arguments.
        """
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        self.__mpts.append((_x, _y))

    def getMarkerPoints(self):
        """Return the stored marker coordinates.

getMarkerPoints()

This method returns a list of coordinates stored with storeMarkerPoint().
Each item in the list is a tuple holding two float values - the x/y
coordinate of the point.
        """
        return self.__mpts[:]

    def sendsMessage(self, m):
        if m in DimCrossbar.messages:
            return True
        return DimBar.sendsMessage(self, m)

class DimCrossarc(DimCrossbar):
    """The class for specialized crossbars for angular dimensions.

The DimCrossarc class is meant to be used only with angular dimensions.
As an angular dimension has two DimBar objects that are connected
with an arc. The DimCrossarc class is derived from the DimCrossbar
class so it shares all the methods of that class. The DimCrossarc
class has the following additional methods:

{get/set}Radius(): Get/Set the radius of the arc.
{get/set}StartAngle(): Get/Set the arc starting angle.
{get/set}EndAngle(): Get/Set the arc finishing angle.
    """

    messages = {
    'arcpoint_changed' : True,
    'radius_changed' : True,
    'start_angle_changed' : True,
    'end_angle_changed' : True,
    }
    def __init__(self, radius=0.0, start=0.0, end=0.0, **kw):
        """Initialize a DimCrossarc object.

dca = DimCrossarc([radius, start, end])

By default the arguments are all 0.0. Any arguments passed to
this method should be floats.
        """
        DimCrossbar.__init__(self, **kw)
        _r = radius
        if not isinstance(_r, float):
            _r = float(radius)
        if _r < 0.0:
            raise ValueError, "Invalid radius: %g" % _r
        _start = util.make_c_angle(start)
        _end = util.make_c_angle(end)
        self.__radius = _r
        self.__start = _start
        self.__end = _end

    def getRadius(self):
        """Return the radius of the arc.

getRadius()

This method returns a float value.
        """
        return self.__radius

    def setRadius(self, radius):
        """Set the radius of the arc.

setRadius(radius)

Argument "radius" should be a float value greater than 0.0.
        """
        if self.isLocked():
            raise RuntimeError, "Setting radius not allowed - object locked."
        _r = radius
        if not isinstance(_r, float):
            _r = float(radius)
        if _r < 0.0:
            raise ValueError, "Invalid radius: %g" % _r
        _sr = self.__radius
        if abs(_sr - _r) > 1e-10:
            self.__radius = _r
            self.sendMessage('radius_changed', _sr)
            self.modified()

    def getStartAngle(self):
        """Return the arc starting angle.

getStartAngle()

This method returns a float.
        """
        return self.__start

    def setStartAngle(self, angle):
        """Set the starting angle of the arc.

setStartAngle(angle)

Argument angle should be a float value.
        """
        if self.isLocked():
            raise RuntimeError, "Setting start angle not allowed - object locked."
        _sa = self.__start
        _angle = util.make_c_angle(angle)
        if abs(_sa - _angle) > 1e-10:
            self.__start = _angle
            self.sendMessage('start_angle_changed', _sa)
            self.modified()

    def getEndAngle(self):
        """Return the arc ending angle.

getEndAngle()

This method returns a float.
        """
        return self.__end

    def setEndAngle(self, angle):
        """Set the ending angle of the arc.

setEndAngle(angle)

Argument angle should be a float value.
        """
        if self.isLocked():
            raise RuntimeError, "Setting end angle not allowed - object locked."
        _ea = self.__end
        _angle = util.make_c_angle(angle)
        if abs(_ea - _angle) > 1e-10:
            self.__end = _angle
            self.sendMessage('end_angle_changed', _ea)
            self.modified()

    def getAngle(self):
        pass # override the DimBar::getAngle() method

    def getSinCosValues(self):
        pass # override the DimBar::getSinCosValues() method

class Dimension(entity.Entity):
    """The base class for Dimensions

A Dimension object is meant to be a base class for specialized
dimensions.

Every Dimension object holds two DimString objects, so any
dimension can be displayed with two separate formatting options
and units.

A Dimension has the following methods

{get/set}DimStyle(): Get/Set the DimStyle used for this Dimension.
applyDimStyle(): Set all the Dimension attributes to the values given
                 in the DimStyle
getPrimaryDimstring(): Return the DimString used for formatting the
                       Primary dimension.
getSecondaryDimstring(): Return the DimString used for formatting the
                         Secondary dimension.
{get/set}EndpointType(): Get/Set the type of endpoints used in the Dimension
{get/set}EndpointSize(): Get/Set the size of the dimension endpoints
{get/set}DualDimMode(): Get/Set whether or not to display both the Primary
                        and Secondary DimString objects
{get/set}Offset(): Get/Set how far from the dimension endpoints to draw
                   dimension lines at the edges of the dimension.
{get/set}Extension(): Get/Set how far past the dimension crossbar line
                      to draw.
{get/set}Position(): Get/Set where the dimensional values are placed on the
                     dimension cross bar.
{get/set}Color(): Get/Set the color used to draw the dimension lines.
{get/set}Location(): Get/Set where to draw the dimensional values.
getDimensions(): Return the formatted dimensional values in this Dimension.
inRegion(): Return if the dimension is visible within some are.
calcDimValues(): Calculate the dimension lines endpoints.
mapCoords(): Return the coordinates on the dimension within some point.
onDimension(): Test if an x/y coordinate pair hit the dimension lines.
getBounds(): Return the minma and maximum locations of the dimension.
    """
    #
    # Endpoint
    #
    NO_ENDPOINT = 0
    ARROW = 1
    FILLED_ARROW = 2
    SLASH = 3
    CIRCLE = 4

    #
    # Dimension position on dimline
    #
    SPLIT = 0
    ABOVE = 1
    BELOW = 2

    __defcolor = color.Color(255,165,0)

    messages = {
    'attribute_changed' : True,
    'offset_changed' : True,
    'extension_changed' : True,
    'moved' : True,
    }
    def __init__(self, ds, x, y, **kw):
        """Initialize a Dimension object

dim = Dimension(ds)

A Dimension object requires a DimStyle object to construct
itself. The DimStyle object provides a collection of values
used to format the two DimString objects and the various other
parts of a dimension.
        """
        entity.Entity.__init__(self, **kw)
        self.__dimstring1 = DimString()
        self.__dimstring2 = DimString()
        if not isinstance(ds, DimStyle):
            raise TypeError, "Invalid DimStyle: " + `ds`
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        self.__dimstyle = ds
        self.__ds1 = DimString()
        self.__ds2 = DimString()
        self.__ddm = False # dual-dimension mode
        self.__offset = 0.0
        self.__extension = 0.0
        self.__position = Dimension.SPLIT
        self.__eptype = Dimension.NO_ENDPOINT
        self.__epsize = 0.0
        self.__color = Dimension.__defcolor
        self.__dimloc = (_x, _y)
        self.mute()
        try:
            for _k, _v in kw.items():
                if _k == 'dimstyle':
                    self.setDimStyle(_v)
                elif _k == 'dual_mode':
                    self.setDualDimMode(_v)
                elif _k == 'offset':
                    self.setOffset(_v)
                elif _k == 'extension':
                    self.setExtension(_v)
                elif _k == 'position':
                    self.setPosition(_v)
                elif _k == 'endpoint_type':
                    self.setEndpointType(_v)
                elif _k == 'endpoint_size':
                    self.setEndpointSize(_v)
                elif _k == 'color':
                    self.setColor(_v)
                elif _k.startswith('s1:') or _k.startswith('s2:'):
                    _ds = self.__ds1
                    if _k.startswith('s2:'):
                        _ds = self.__ds2
                    _ds.mute()
                    try:
                        if _k == 'prefix':
                            _ds.setPrefix(_v)
                        elif _k == 'suffix':
                            _ds.setSuffix(_v)
                        elif _k == 'unit':
                            _ds.setUnit(_v)
                        elif _k == 'precision':
                            _ds.setPrecision(_v)
                        elif _k == 'print_zero':
                            _ds.setPrintZero(_v)
                        elif _k == 'print_decimal':
                            _ds.setPrintDecimal(_v)
                        else:
                            raise KeyError, "Unknown keyword: %s" % _k
                    finally:
                        _ds.unmute()
                else:
                    pass
            self.reset()
        finally:
            self.unmute()

    def getValues(self):
        _pids = entity.Entity.getValues(self)
        _vals = {}
        _vals['offset'] = self.__offset
        _vals['extension'] = self.__extension
        _vals['position'] = self.__position
        _vals['endtype'] = self.__eptype
        _vals['endsize'] = self.__epsize
        _vals['color'] = str(self.__color)
        _vals['dualmode'] = self.__ddm
        _vals['ds1'] = self.__dimstring1.getValues()
        _vals['ds2'] = self.__dimstring2.getValues()
        _vals['dsname'] = self.__dimstyle.getName()
        return _pids, _vals

    def getDimStyle(self):
        """Return the DimStyle used in this Dimension.

getDimStyle()
        """
        return self.__dimstyle

    def setDimStyle(self, ds):
        """Set the DimStyle used for this Dimension.

setDimStyle(ds)

After setting the DimStyle, the values stored in it
are applied to the DimensionObject.
        """
        if self.isLocked():
            raise RuntimeError, "Changing dimstyle not allowed - object locked."
        if not isinstance(ds, DimStyle):
            raise TypeError, "Invalid DimStyle: " + `ds`
        _sds = self.__dimstyle
        if ds is not _sds:
            self.__dimstyle = ds
            self.sendMessage('attribute_changed', 'style', _sds)
            self.modified()

    dimstyle = property(getDimStyle, setDimStyle, None,
                        "Dimension DimStyle object.")

    def applyDimStyle(self):
        """Apply the values in the DimStyle to the Dimension.

applyDimStyle()
        """
        _ds = self.__dimstyle
        self.setOffset(_ds.getValue('DIM_OFFSET'))
        self.setExtension(_ds.getValue('DIM_EXTENSION'))
        self.setPosition(_ds.getValue('DIM_POSITION'))
        self.setEndpointType(_ds.getValue('DIM_ENDPOINT'))
        self.setEndpointSize(_ds.getValue('DIM_ENDPOINT_SIZE'))
        self.setColor(_ds.getValue('DIM_COLOR'))
        self.setDualDimMode(_ds.getValue('DIM_DUAL_MODE'))
        #
        # primary dimension string
        #
        _pds = self.getPrimaryDimstring()
        _pds.setPrefix(_ds.getValue('DIM_PRIMARY_PREFIX'))
        _pds.setSuffix(_ds.getValue('DIM_PRIMARY_SUFFIX'))
        _pds.setPrecision(_ds.getValue('DIM_PRIMARY_PRECISION'))
        _pds.setUnits(_ds.getValue('DIM_PRIMARY_UNITS'))
        _pds.setPrintZero(_ds.getValue('DIM_PRIMARY_LEADING_ZERO'))
        _pds.setPrintDecimal(_ds.getValue('DIM_PRIMARY_TRAILING_DECIMAL'))
        _pds.setFamily(_ds.getValue('DIM_PRIMARY_FONT_FAMILY'))
        _pds.setSize(_ds.getValue('DIM_PRIMARY_FONT_SIZE'))
        _pds.setWeight(_ds.getValue('DIM_PRIMARY_FONT_WEIGHT'))
        _pds.setStyle(_ds.getValue('DIM_PRIMARY_FONT_STYLE'))
        _pds.setColor(_ds.getValue('DIM_PRIMARY_FONT_COLOR'))
        #
        # secondary dimension string
        #
        _sds = self.getSecondaryDimstring()
        _sds.setPrefix(_ds.getValue('DIM_SECONDARY_PREFIX'))
        _sds.setSuffix(_ds.getValue('DIM_SECONDARY_SUFFIX'))
        _sds.setPrecision(_ds.getValue('DIM_SECONDARY_PRECISION'))
        _sds.setUnits(_ds.getValue('DIM_SECONDARY_UNITS'))
        _sds.setPrintZero(_ds.getValue('DIM_SECONDARY_LEADING_ZERO'))
        _sds.setPrintDecimal(_ds.getValue('DIM_SECONDARY_TRAILING_DECIMAL'))
        _sds.setFamily(_ds.getValue('DIM_SECONDARY_FONT_FAMILY'))
        _sds.setSize(_ds.getValue('DIM_SECONDARY_FONT_SIZE'))
        _sds.setWeight(_ds.getValue('DIM_SECONDARY_FONT_WEIGHT'))
        _sds.setStyle(_ds.getValue('DIM_SECONDARY_FONT_STYLE'))
        _sds.setColor(_ds.getValue('DIM_SECONDARY_FONT_COLOR'))
        self.modified()

    def getEndpointType(self):
        """Return what type of endpoints the Dimension uses.

getEndpointType()
        """
        return self.__eptype

    def setEndpointType(self, e):
        """Set what type of endpoints the Dimension will use.

setEndpointType(e)

The argument "e" should be one of the following

dimension.NO_ENDPOINT => no special marking at the dimension crossbar ends
dimension.ARROW => an arrowhead at the dimension crossbar ends
dimension.FILLED_ARROW => a filled arrohead at the dimension crossbar ends
dimension.SLASH => a slash mark at the dimension crossbar ends
dimension.CIRCLE => a filled circle at the dimension crossbar ends
        """
        if self.isLocked():
            raise RuntimeError, "Changing endpoint type allowed - object locked."
        if (e != Dimension.NO_ENDPOINT and
            e != Dimension.ARROW and
            e != Dimension.FILLED_ARROW and
            e != Dimension.SLASH and
            e != Dimension.CIRCLE):
            raise ValueError, "Invalid endpoint value: '%s'" % str(e)
        _et = self.__eptype
        if _et != e:
            self.__eptype = e
            self.sendMessage('attribute_changed', 'endpoint_type', _et)
            self.modified()

    endpoint = property(getEndpointType, setEndpointType,
                        None, "Dimension endpoint type.")

    def getEndpointSize(self):
        """Return the size of the Dimension endpoints.

getEndpointSize()
        """
        return self.__epsize

    def setEndpointSize(self, size):
        """Set the size of the Dimension endpoints.

setEndpointSize(size)

The argument size should be a float greater than or
equal to 0.0
        """
        if self.isLocked():
            raise RuntimeError, "Changing endpoint type allowed - object locked."
        _size = size
        if not isinstance(_size, float):
            _size = float(size)
        if _size < 0.0:
            raise ValueError, "Invalid endpoint size: %g" % _size
        _es = self.__epsize
        if abs(_es - _size) > 1e-10:
            self.__epsize = _size
            self.sendMessage('attribute_changed', 'endpoint_size', _es)
            self.modified()

    def getDimstrings(self):
        """Return both primary and secondry dimstrings.

getDimstrings()        
        """
        return self.__ds1, self.__ds2
    
    def getPrimaryDimstring(self):
        """ Return the DimString used for formatting the primary dimension.

getPrimaryDimstring()
        """
        return self.__ds1

    def getSecondaryDimstring(self):
        """Return the DimString used for formatting the secondary dimension.

getSecondaryDimstring()
        """
        return self.__ds2

    def getDualDimMode(self):
        """Return if the Dimension is displaying primary and secondary values.

getDualDimMode(self)
        """
        return self.__ddm

    def setDualDimMode(self, mode=True):
        """Set the Dimension to display both primary and secondary values.

setDualDimMode([mode])

Invoking this method without arguments will set the Dimension to
display primary and secondary dimensions. Otherwise the argument
should be True or False.
        """
        if self.isLocked():
            raise RuntimeError, "Changing dual mode not allowed - object locked."
        if mode is not True and mode is not False:
            raise ValueError, "Invalid dual dimension mode: '%s'" % str(mode)
        _ddm = self.__ddm
        if _ddm is not mode:
            self.__ddm = mode
            self.sendMessage('attribute_changed', 'dual_mode', _ddm)
            self.modified()

    dual_mode = property(getDualDimMode, setDualDimMode, None,
                         "Display both primary and secondary dimensions")

    def getOffset(self):
        """Return the current offset value for the Dimension.

getOffset()
        """
        return self.__offset

    def setOffset(self, offset=0.0):
        """Set the offset value for the Dimension.

setOffset([offset])

Calling this method without arguments sets the value to 0.
If the argument "offset" is supplied, it should be a positive
float value.
        """
        if self.isLocked():
            raise RuntimeError, "Setting offset not allowed - object locked."
        _o = offset
        if not isinstance(_o, float):
            _o = float(offset)
        if _o < 0.0:
            raise ValueError, "Invalid dimension offset length: %g" % _o
        _off = self.__offset
        if abs(_off - _o) > 1e-10:
            _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
            self.__offset = _o
            self.calcDimValues()
            self.sendMessage('offset_changed', _off)
            self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
            self.modified()

    offset = property(getOffset, setOffset, None, "Dimension offset.")

    def getExtension(self):
        """Get the extension length of the Dimension.

getExtension()
        """
        return self.__extension

    def setExtension(self, ext=0.0):
        """Set the extension length of the Dimension.

setExtension([ext])

If this method is called without arguments, the extension
length is set to 0. If the argument "ext" is supplied, it
should be a positive float value.
        """
        if self.isLocked():
            raise RuntimeError, "Setting extension not allowed - object locked."
        _e = ext
        if not isinstance(_e, float):
            _e = float(ext)
        if _e < 0.0:
            raise ValueError, "Invalid dimension extension length: %g" % _e
        _ext = self.__extension
        if abs(_ext - _e) > 1e-10:
            _dxmin, _dymin, _dxmax, _dymax = self.getBounds()            
            self.__extension = _e
            self.calcDimValues()
            self.sendMessage('extension_changed', _ext)
            self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
            self.modified()

    extension = property(getExtension, setExtension, None,
                         "Dimension extension length.")

    def getPosition(self):
        """Return how the dimension text intersects the crossbar.

getPosition()
        """
        return self.__position

    def setPosition(self, pos):
        """Set where the dimension text should be placed at the crossbar.

setPosition(pos)

The choices are:

dimension.SPLIT => In the middle of the crossbar.
dimension.ABOVE => Beyond the crossbar from the dimensioned objects.
dimension.BELOW => Between the crossbar and the dimensioned objects.
        """
        if self.isLocked():
            raise RuntimeError, "Setting position not allowed - object locked."
        if (pos != Dimension.SPLIT and
            pos != Dimension.ABOVE and
            pos != Dimension.BELOW):
            raise ValueError, "Invalid dimension text position: '%s'" % str(pos)
        _dp = self.__position
        if _dp != pos:
            self.__position = pos
            self.sendMessage('position_changed', _dp)
            self.modified()

    position = property(getPosition, setPosition, None,
                        "Dimension text position")

    def getColor(self):
        """Return the color of the dimension lines.

getColor()
        """
        return self.__color

    def setColor(self, c):
        """Set the color of the dimension lines.

setColor(c)
        """
        if self.isLocked():
            raise RuntimeError, "Setting object color not allowed - object locked."
        _c = c
        if not isinstance(_c, color.Color):
            _c = color.Color(c)
        _oc = self.__color
        if _oc != _c:
            self.__color = _c
            self.sendMessage('attribute_changed', 'color', _oc)
            self.modified()

    color = property(getColor, setColor, None, "Dimension Color")

    def getLocation(self):
        """Return the location of the dimensional text values.

getLocation()
        """
        return self.__dimloc

    def setLocation(self, x, y):
        """Set the location of the dimensional text values.

setLocation(x, y)

The "x" and "y" arguments should be float values. The text is
centered around that point.
        """
        if self.isLocked():
            raise RuntimeError, "Setting location not allowed - object locked."
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _ox, _oy = self.__dimloc
        if abs(_ox - _x) > 1e-10 or abs(_oy - _y) > 1e-10:
            _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
            self.__dimloc = (_x, _y)
            self.calcDimValues()
            self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
            self.modified()

    location = property(getLocation, setLocation, None,
                        "Dimension location")

    def getDimensions(self, dimlen):
        """Return the formatted dimensional values.

getDimensions(dimlen)

The argument "dimlen" should be the length in millimeters.

This method returns a list of the primary and secondary
dimensional values.
        """
        _dl = dimlen
        if not isinstance(dimlen, float):
            _dl = float(dimlen)
        dims = []
        dims.append(self.__ds1.formatDimension(_dl))
        dims.append(self.__ds2.formatDimension(_dl))
        return dims

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display

calcDimValues([allpts])

This method is meant to be overriden by subclasses.
        """
        pass
    
    def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
        """Return whether or not a Dimension exists with a region.

isRegion(xmin, ymin, xmax, ymax[, fully])

The first four arguments define the boundary. The optional
fifth argument "fully" indicates whether or not the Dimension
must be completely contained within the region or just pass
through it.

This method should be overriden in classes derived from Dimension.
        """
        return False

    def getBounds(self):
        """Return the minimal and maximal locations of the dimension

getBounds()

This method returns a tuple of four values - xmin, ymin, xmax, ymax.
These values give the mimimum and maximum coordinates of the dimension
object.

This method should be overriden in classes derived from Dimension.
        """
        _xmin = _ymin = -float(sys.maxint)
        _xmax = _ymax = float(sys.maxint)
        return _xmin, _ymin, _xmax, _ymax

    def sendsMessage(self, m):
        if m in Dimension.messages:
            return True
        return entity.Entity.sendsMessage(self, m)

class LinearDimension(Dimension):
    """A class for Linear dimensions.

The LinearDimension class is derived from the Dimension
class, so it shares all of those methods and attributes.
A LinearDimension should be used to display the absolute
distance between two Point objects.

A LinearDimension object has the following methods:

{get/set}P1(): Get/Set the first Point for the LinearDimension.
{get/set}P2(): Get/Set the second Point for the LinearDimension.
getDimPoints(): Return the two Points used in this dimension.
getDimLayers(): Return the two Layers holding the Points.
getDimXPoints(): Get the x-coordinates of the dimension bar positions.
getDimYPoints(): Get the y-coordinates of the dimension bar positions.
getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
calcMarkerPoints(): Calculate the coordinates of any dimension marker objects.
    """

    messages = {
    'point_changed' : True,
    }

    def __init__(self, l1, p1, l2, p2, x, y, ds, **kw):
        """Instantiate a LinearDimension object.

ldim = LinearDimension(l1, p1, l2, p2, x, y, ds)

l1: A Layer holding Point p1
p1: A Point contained in Layer l1
l2: A Layer holding Point p2
p2: A Point contained in Layer l2
x: The x-coordinate of the dimensional text
y: The y-coordinate of the dimensional text
ds: The DimStyle used for this Dimension.
        """
        Dimension.__init__(self, ds, x, y, **kw)
        if not isinstance(l1, layer.Layer):
            raise TypeError, "Invalid layer: " + `l1`
        if not isinstance(p1, point.Point):
            raise TypeError, "Invalid point: " + `p1`
        _lp = l1.findObject(p1)
        if _lp is not p1:
            raise ValueError, "Point p1 not found in layer!"
        if not isinstance(l2, layer.Layer):
            raise TypeError, "Invalid layer: " + `l2`
        if not isinstance(p2, point.Point):
            raise TypeError, "Invalid point: " + `p2`
        _lp = l2.findObject(p2)
        if _lp is not p2:
            raise ValueError, "Point p2 not found in layer!"
        self.__l1 = l1
        self.__p1 = p1
        self.__l2 = l2
        self.__p2 = p2
        self.__bar1 = DimBar()
        self.__bar2 = DimBar()
        self.__crossbar = DimCrossbar()
        self.applyDimStyle()
        p1.storeUser(self)
        p1.connect('moved', self, LinearDimension.movePoint)
        p2.storeUser(self)
        p2.connect('moved', self, LinearDimension.movePoint)
        self.calcDimValues()

    def __eq__(self, ldim):
        """Test two LinearDimension objects for equality.
        """
        if not isinstance(ldim, LinearDimension):
            return False
        _l1, _l2 = ldim.getDimLayers()
        _p1, _p2 = ldim.getDimPoints()
        if (self.__l1 is _l1 and
            self.__l2 is _l2 and
            self.__p1 == _p1 and
            self.__p2 == _p2):
            return True
        if (self.__l1 is _l2 and
            self.__l2 is _l1 and
            self.__p1 == _p2 and
            self.__p2 == _p1):
            return True
        return False

    def __ne__(self, ldim):
        """Test two LinearDimension objects for equality.
        """
        if not isinstance(ldim, LinearDimension):
            return True
        _l1, _l2 = ldim.getDimLayers()
        _p1, _p2 = ldim.getDimPoints()
        if (self.__l1 is _l1 and
            self.__l2 is _l2 and
            self.__p1 == _p1 and
            self.__p2 == _p2):
            return False
        if (self.__l1 is _l2 and
            self.__l2 is _l1 and
            self.__p1 == _p2 and
            self.__p2 == _p1):
            return False
        return True

    def getValues(self):
        _x, _y = self.getLocation()
        _data = (self.__l1.getID(), self.__p1.getID(),
                 self.__l2.getID(), self.__p2.getID(), _x, _y)
        return ('ldim',) + Dimension.getValues(self) + _data
        
    def getP1(self):
        """Return the first Point of a LinearDimension.

getP1()
        """
        return self.__p1

    def setP1(self, l, p):
        """Set the first Point of a LinearDimension.

setP1(l, p)

There are two required arguments for this method

l: The layer holding the Point p
p: A point in Layer l
        """
        if self.isLocked():
            raise RuntimeError, "Setting point not allowed - object locked."
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(p, point.Point):
            raise TypeError, "Invalid point: " + `p`
        _lp = l.findObject(p)
        if _lp is not p:
            raise ValueError, "Point not found in layer!"
        _pt = self.__p1
        if _pt is not p:
            _pt.disconnect(self)
            _pt.freeUser(self)
            self.__l1 = l
            self.__p1 = p
            self.sendMessage('point_changed', _pt, p)
            p.storeUser(self)
            p.connect('moved', self, LinearDimension.movePoint)
            if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10:
                _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
                self.calcDimValues()
                self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
            self.modified()

    p1 = property(getP1, None, None, "Dimension first point.")

    def getP2(self):
        """Return the second point of a LinearDimension.

getP2()
        """
        return self.__p2

    def setP2(self, l, p):
        """Set the second Point of a LinearDimension.

setP2(l, p)

There are two required arguments for this method

l: The layer holding the Point p
p: A point in Layer l
        """
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(p, point.Point):
            raise TypeError, "Invalid point: " + `p`
        _lp = l.findObject(p)
        if _lp is not p:
            raise ValueError, "Point not found in layer!"
        _pt = self.__p2
        if _pt is not p:
            _pt.disconnect(self)
            _pt.freeUser(self)
            self.__l2 = l
            self.__p2 = p
            self.sendMessage('point_changed', _pt, p)
            p.storeUser(self)
            p.connect('moved', self, LinearDimension.movePoint)
            if abs(_pt.x - p.x) > 1e-10 or abs(_pt.y - p.y) > 1e-10:
                _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
                self.calcDimValues()                
                self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
            self.modified()

    p2 = property(getP2, None, None, "Dimension second point.")

    def getDimPoints(self):
        """Return both points used in the LinearDimension.

getDimPoints()

The two points are returned in a tuple.
        """
        return self.__p1, self.__p2

    def getDimBars(self):
        """Return the dimension boundary bars.

getDimBars()
        """
        return self.__bar1, self.__bar2

    def getDimCrossbar(self):
        """Return the dimension crossbar.

getDimCrossbar()
        """
        return self.__crossbar

    def getDimLayers(self):
        """Return both layers used in the LinearDimension.

getDimLayers()

The two layers are returned in a tuple.
        """
        return self.__l1, self.__l2

    def calculate(self):
        """Determine the length of this LinearDimension.

calculate()
        """
        return self.__p1 - self.__p2

    def applyDimStyle(self):
        """Apply the values in the DimStyle to the LinearDimension.

applyDimStyle()

This method extends the Dimension::applyDimStyle() method.
        """
        Dimension.applyDimStyle(self)
        self.modified()

    def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
        """Return whether or not a LinearDimension exists within a region.

isRegion(xmin, ymin, xmax, ymax[, fully])

The four arguments define the boundary of an area, and the
function returns True if the LinearDimension lies within that
area. If the optional argument fully is used and is True,
then the dimension points and the location of the dimension
text must lie within the boundary. Otherwise, the function
returns False.
        """
        _xmin = xmin
        if not isinstance(_xmin, float):
            _xmin = float(xmin)
        _ymin = ymin
        if not isinstance(_ymin, float):
            _ymin = float(ymin)
        _xmax = xmax
        if not isinstance(_xmax, float):
            _xmax = float(xmax)
        if _xmax < _xmin:
            raise ValueError, "Illegal values: xmax < xmin"
        _ymax = ymax
        if not isinstance(_ymax, float):
            _ymax = float(ymax)
        if _ymax < _ymin:
            raise ValueError, "Illegal values: ymax < ymin"
        if fully is not True and fully is not False:
            raise ValueError, "Invalid flag: " + `fully`
        _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
        if ((_dxmin > _xmax) or
            (_dymin > _ymax) or
            (_dxmax < _xmin) or
            (_dymax < _ymin)):
            return False
        if fully:
            if ((_dxmin > _xmin) and
                (_dymin > _ymin) and
                (_dxmax < _xmax) and
                (_dymax < _ymax)):
                return True
            return False
        _dx, _dy = self.getLocation()
        if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text
            return True
        #
        # bar at p1
        #
        _ep1, _ep2 = self.__bar1.getEndpoints()
        _x1, _y1 = _ep1
        _x2, _y2 = _ep2
        if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax):
            return True
        #
        # bar at p2
        #
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _x1, _y1 = _ep1
        _x2, _y2 = _ep2
        if util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax):
            return True
        #
        # crossbar
        #
        _ep1, _ep2 = self.__crossbar.getEndpoints()
        _x1, _y1 = _ep1
        _x2, _y2 = _ep2
        return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax)

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display.

calcDimValues([allpts])

This method calculates where the points for the dimension
bars and crossbar are located. The argument "allpts" is
optional. By default it is True. If the argument is set to
False, then the coordinates of the dimension marker points
will not be calculated.
        """
        _allpts = allpts
        if _allpts is not True and _allpts is not False:
            raise ValueError, "Invalid argument: " + str(allpts)
        _p1, _p2 = self.getDimPoints()
        _bar1 = self.__bar1
        _bar2 = self.__bar2
        _crossbar = self.__crossbar
        _p1x, _p1y = _p1.getCoords()
        _p2x, _p2y = _p2.getCoords()
        _dx, _dy = self.getLocation()
        _offset = self.getOffset()
        _ext = self.getExtension()
        #
        # see comp.graphics.algorithms.faq section on calcuating
        # the distance between a point and line for info about
        # the following equations ...
        #
        _dpx = _p2x - _p1x
        _dpy = _p2y - _p1y
        _rnum = ((_dx - _p1x) * _dpx) + ((_dy - _p1y) * _dpy)
        _snum = ((_p1y - _dy) * _dpx) - ((_p1x - _dx) * _dpy)
        _den = pow(_dpx, 2) + pow(_dpy, 2)
        _r = _rnum/_den
        _s = _snum/_den
        _sep = abs(_s) * math.sqrt(_den)
        if abs(_dpx) < 1e-10: # vertical
            if _p2y > _p1y:
                _slope = math.pi/2.0
            else:
                _slope = -math.pi/2.0
        elif abs(_dpy) < 1e-10: # horizontal
            if _p2x > _p1x:
                _slope = 0.0
            else:
                _slope = -math.pi
        else:
            _slope = math.atan2(_dpy, _dpx)
        if _s < 0.0: # dim point left of p1-p2 line
            _angle = _slope + (math.pi/2.0)
        else: # dim point right of p1-p2 line (or on it)
            _angle = _slope - (math.pi/2.0)
        _sin_angle = math.sin(_angle)
        _cos_angle = math.cos(_angle)
        _x = _p1x + (_offset * _cos_angle)
        _y = _p1y + (_offset * _sin_angle)
        _bar1.setFirstEndpoint(_x, _y)
        if _r < 0.0:
            _px = _p1x + (_r * _dpx)
            _py = _p1y + (_r * _dpy)
            _x = _px + (_sep * _cos_angle)
            _y = _py + (_sep * _sin_angle)
        else:
            _x = _p1x + (_sep * _cos_angle)
            _y = _p1y + (_sep * _sin_angle)
        _crossbar.setFirstEndpoint(_x, _y)
        _x = _p1x + (_sep * _cos_angle)
        _y = _p1y + (_sep * _sin_angle)
        _crossbar.setFirstCrossbarPoint(_x, _y)
        _x = _p1x + ((_sep + _ext) * _cos_angle)
        _y = _p1y + ((_sep + _ext) * _sin_angle)
        _bar1.setSecondEndpoint(_x, _y)
        _x = _p2x + (_offset * _cos_angle)
        _y = _p2y + (_offset * _sin_angle)
        _bar2.setFirstEndpoint(_x, _y)
        if _r > 1.0:
            _px = _p1x + (_r * _dpx)
            _py = _p1y + (_r * _dpy)
            _x = _px + (_sep * _cos_angle)
            _y = _py + (_sep * _sin_angle)
        else:
            _x = _p2x + (_sep * _cos_angle)
            _y = _p2y + (_sep * _sin_angle)
        _crossbar.setSecondEndpoint(_x, _y)
        _x = _p2x + (_sep * _cos_angle)
        _y = _p2y + (_sep * _sin_angle)
        _crossbar.setSecondCrossbarPoint(_x, _y)
        _x = _p2x + ((_sep + _ext) * _cos_angle)
        _y = _p2y + ((_sep + _ext) * _sin_angle)
        _bar2.setSecondEndpoint(_x, _y)
        if _allpts:
            self.calcMarkerPoints()

    def calcMarkerPoints(self):
        """Calculate and store the dimension endpoint markers coordinates.

calcMarkerPoints()
        """
        _type = self.getEndpointType()
        _crossbar = self.__crossbar
        _crossbar.clearMarkerPoints()
        if _type == Dimension.NO_ENDPOINT or _type == Dimension.CIRCLE:
            return
        _size = self.getEndpointSize()
        _p1, _p2 = _crossbar.getCrossbarPoints()
        _x1, _y1 = _p1
        _x2, _y2 = _p2
        # print "x1: %g" % _x1
        # print "y1: %g" % _y1
        # print "x2: %g" % _x2
        # print "y2: %g" % _y2
        _sine, _cosine = _crossbar.getSinCosValues()
        if _type == Dimension.ARROW or _type == Dimension.FILLED_ARROW:
            _height = _size/5.0
            # p1 -> (x,y) = (size, _height)
            _mx = (_cosine * _size - _sine * _height) + _x1
            _my = (_sine * _size + _cosine * _height) + _y1
            _crossbar.storeMarkerPoint(_mx, _my)
            # p2 -> (x,y) = (size, -_height)
            _mx = (_cosine * _size - _sine *(-_height)) + _x1
            _my = (_sine * _size + _cosine *(-_height)) + _y1
            _crossbar.storeMarkerPoint(_mx, _my)
            # p3 -> (x,y) = (-size, _height)
            _mx = (_cosine * (-_size) - _sine * _height) + _x2
            _my = (_sine * (-_size) + _cosine * _height) + _y2
            _crossbar.storeMarkerPoint(_mx, _my)
            # p4 -> (x,y) = (-size, -_height)
            _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2
            _my = (_sine * (-_size) + _cosine *(-_height)) + _y2
            _crossbar.storeMarkerPoint(_mx, _my)
        elif _type == Dimension.SLASH:
            _angle = 30.0 * _dtr # slope of slash
            _height = 0.5 * _size * math.sin(_angle)
            _length = 0.5 * _size * math.cos(_angle)
            # p1 -> (x,y) = (-_length, -_height)
            _sx1 = (_cosine * (-_length) - _sine * (-_height))
            _sy1 = (_sine * (-_length) + _cosine * (-_height))
            # p2 -> (x,y) = (_length, _height)
            _sx2 = (_cosine * _length - _sine * _height)
            _sy2 = (_sine * _length + _cosine * _height)
            #
            # shift the calculate based on the location of the
            # marker point
            #
            _mx = _sx1 + _x2
            _my = _sy1 + _y2
            _crossbar.storeMarkerPoint(_mx, _my)
            _mx = _sx2 + _x2
            _my = _sy2 + _y2
            _crossbar.storeMarkerPoint(_mx, _my)
            _mx = _sx1 + _x1
            _my = _sy1 + _y1
            _crossbar.storeMarkerPoint(_mx, _my)
            _mx = _sx2 + _x1
            _my = _sy2 + _y1
            _crossbar.storeMarkerPoint(_mx, _my)
        else:
            raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)

    def mapCoords(self, x, y, tol=tolerance.TOL):
        """Test an x/y coordinate pair if it could lay on the dimension.

mapCoords(x, y[, tol])

This method has two required parameters:

x: The x-coordinate
y: The y-coordinate

These should both be float values.

There is an optional third parameter tol giving the maximum distance
from the dimension bars that the x/y coordinates may lie.
        """
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _t = tolerance.toltest(tol)
        _ep1, _ep2 = self.__bar1.getEndpoints()
        #
        # test p1 bar
        #
        _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)
        if _mp is not None:
            return _mp
        #
        # test p2 bar
        #
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _mp = util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)
        if _mp is not None:
            return _mp
        #
        # test crossbar
        #
        _ep1, _ep2 = self.__crossbar.getEndpoints()
        return util.map_coords(_x, _y, _ep1[0], _ep1[1], _ep2[0], _ep2[1], _t)

    def onDimension(self, x, y, tol=tolerance.TOL):
        return self.mapCoords(x, y, tol) is not None
    
    def getBounds(self):
        """Return the minimal and maximal locations of the dimension

getBounds()

This method overrides the Dimension::getBounds() method
        """
        _dx, _dy = self.getLocation()
        _dxpts = []
        _dypts = []
        _ep1, _ep2 = self.__bar1.getEndpoints()
        _dxpts.append(_ep1[0])
        _dypts.append(_ep1[1])
        _dxpts.append(_ep2[0])
        _dypts.append(_ep2[1])
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _dxpts.append(_ep1[0])
        _dypts.append(_ep1[1])
        _dxpts.append(_ep2[0])
        _dypts.append(_ep2[1])
        _ep1, _ep2 = self.__crossbar.getEndpoints()
        _dxpts.append(_ep1[0])
        _dypts.append(_ep1[1])
        _dxpts.append(_ep2[0])
        _dypts.append(_ep2[1])
        _xmin = min(_dx, min(_dxpts))
        _ymin = min(_dy, min(_dypts))
        _xmax = max(_dx, max(_dxpts))
        _ymax = max(_dy, max(_dypts))
        return _xmin, _ymin, _xmax, _ymax

    def movePoint(self, p, *args):
        _alen = len(args)
        if _alen < 2:
            raise ValueError, "Invalid argument count: %d" % _alen
        if p is not self.__p1 and p is not self.__p2:
            raise ValueError, "Unexpected dimension point: " + `p`
        _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
        self.calcDimValues(True)
        self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
        self.modified()

    def sendsMessage(self, m):
        if m in LinearDimension.messages:
            return True
        return Dimension.sendsMessage(self, m)

class HorizontalDimension(LinearDimension):
    """A class representing Horizontal dimensions.

This class is derived from the LinearDimension class, so
it shares all those attributes and methods of its parent.
    """
    def __init__(self, l1, p1, l2, p2, x, y, ds, **kw):
        """Initialize a Horizontal Dimension.

hdim = HorizontalDimension(l1, p1, l2, p2, x, y, ds)

l1: A Layer holding Point p1
p1: A Point contained in Layer l1
l2: A Layer holding Point p2
p2: A Point contained in Layer l2
x: The x-coordinate of the dimensional text
y: The y-coordinate of the dimensional text
ds: The DimStyle used for this Dimension.
        """
        LinearDimension.__init__(self, l1, p1, l2, p2, x, y, ds, **kw)

    def getValues(self):
        _l1, _l2 = self.getDimLayers()
        _p1, _p2 = self.getDimPoints()
        _x, _y = self.getLocation()
        _data = (_l1.getID(), _p1.getID(), _l2.getID(), _p2.getID(), _x, _y)
        return ('hdim',) + Dimension.getValues(self) + _data

    def calculate(self):
        """Determine the length of this HorizontalDimension.

calculate()
        """
        _p1, _p2 = self.getDimPoints()
        return abs(_p1.x - _p2.x)

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display.

calcDimValues([allpts])

This method overrides the LinearDimension::calcDimValues() method.
        """
        _allpts = allpts
        if _allpts is not True and _allpts is not False:
            raise ValueError, "Invalid argument: " + str(allpts)
        _p1, _p2 = self.getDimPoints()
        _bar1, _bar2 = self.getDimBars()
        _crossbar = self.getDimCrossbar()
        _p1x, _p1y = _p1.getCoords()
        _p2x, _p2y = _p2.getCoords()
        _dx, _dy = self.getLocation()
        _offset = self.getOffset()
        _ext = self.getExtension()
        _crossbar.setFirstEndpoint(_p1x, _dy)
        _crossbar.setSecondEndpoint(_p2x, _dy)
        if _dx < min(_p1x, _p2x) or _dx > max(_p1x, _p2x):
            if _p1x < _p2x:
                if _dx < _p1x:
                    _crossbar.setFirstEndpoint(_dx, _dy)
                if _dx > _p2x:
                    _crossbar.setSecondEndpoint(_dx, _dy)
            else:
                if _dx < _p2x:
                    _crossbar.setSecondEndpoint(_dx, _dy)
                if _dx > _p1x:
                    _crossbar.setFirstEndpoint(_dx, _dy)
        _crossbar.setFirstCrossbarPoint(_p1x, _dy)
        _crossbar.setSecondCrossbarPoint(_p2x, _dy)
        if _dy < min(_p1y, _p2y):
            _bar1.setFirstEndpoint(_p1x, (_p1y - _offset))
            _bar1.setSecondEndpoint(_p1x, (_dy - _ext))
            _bar2.setFirstEndpoint(_p2x, (_p2y - _offset))
            _bar2.setSecondEndpoint(_p2x, (_dy - _ext))
        elif _dy > max(_p1y, _p2y):
            _bar1.setFirstEndpoint(_p1x, (_p1y + _offset))
            _bar1.setSecondEndpoint(_p1x, (_dy + _ext))
            _bar2.setFirstEndpoint(_p2x, (_p2y + _offset))
            _bar2.setSecondEndpoint(_p2x, (_dy + _ext))
        else:
            if _dy > _p1y:
                _bar1.setFirstEndpoint(_p1x, (_p1y + _offset))
                _bar1.setSecondEndpoint(_p1x, (_dy + _ext))
            else:
                _bar1.setFirstEndpoint(_p1x, (_p1y - _offset))
                _bar1.setSecondEndpoint(_p1x, (_dy - _ext))
            if _dy > _p2y:
                _bar2.setFirstEndpoint(_p2x, (_p2y + _offset))
                _bar2.setSecondEndpoint(_p2x, (_dy + _ext))
            else:
                _bar2.setFirstEndpoint(_p2x, (_p2y - _offset))
                _bar2.setSecondEndpoint(_p2x, (_dy - _ext))
        if _allpts:
            self.calcMarkerPoints()

class VerticalDimension(LinearDimension):
    """A class representing Vertical dimensions.

This class is derived from the LinearDimension class, so
it shares all those attributes and methods of its parent.
    """

    def __init__(self, l1, p1, l2, p2, x, y, ds, **kw):
        """Initialize a Vertical Dimension.

vdim = VerticalDimension(l1, p1, l2, p2, x, y, ds)

l1: A Layer holding Point p1
p1: A Point contained in Layer l1
l2: A Layer holding Point p2
p2: A Point contained in Layer l2
x: The x-coordinate of the dimensional text
y: The y-coordinate of the dimensional text
ds: The DimStyle used for this Dimension.
        """
        LinearDimension.__init__(self, l1, p1, l2, p2, x, y, ds, **kw)

    def getValues(self):
        _l1, _l2 = self.getDimLayers()
        _p1, _p2 = self.getDimPoints()
        _x, _y = self.getLocation()
        _data = (_l1.getID(), _p1.getID(), _l2.getID(), _p2.getID(), _x, _y)
        return ('vdim',) + Dimension.getValues(self) + _data

    def calculate(self):
        """Determine the length of this VerticalDimension.

calculate()
        """
        _p1, _p2 = self.getDimPoints()
        return abs(_p1.y - _p2.y)

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display.

calcDimValues([allpts])

This method overrides the LinearDimension::calcDimValues() method.
        """
        _allpts = allpts
        if _allpts is not True and _allpts is not False:
            raise ValueError, "Invalid argument: " + str(allpts)
        _p1, _p2 = self.getDimPoints()
        _bar1, _bar2 = self.getDimBars()
        _crossbar = self.getDimCrossbar()
        _p1x, _p1y = _p1.getCoords()
        _p2x, _p2y = _p2.getCoords()
        _dx, _dy = self.getLocation()
        _offset = self.getOffset()
        _ext = self.getExtension()
        _crossbar.setFirstEndpoint(_dx, _p1y)
        _crossbar.setSecondEndpoint(_dx, _p2y)
        if _dy < min(_p1y, _p2y) or _dy > max(_p1y, _p2y):
            if _p1y < _p2y:
                if _dy < _p1y:
                    _crossbar.setFirstEndpoint(_dx, _dy)
                if _dy > _p2y:
                    _crossbar.setSecondEndpoint(_dx, _dy)
            if _p2y < _p1y:
                if _dy < _p2y:
                    _crossbar.setSecondEndpoint(_dx, _dy)
                if _dy > _p1y:
                    _crossbar.setFirstEndpoint(_dx, _dy)
        _crossbar.setFirstCrossbarPoint(_dx, _p1y)
        _crossbar.setSecondCrossbarPoint(_dx, _p2y)
        if _dx < min(_p1x, _p2x):
            _bar1.setFirstEndpoint((_p1x - _offset), _p1y)
            _bar1.setSecondEndpoint((_dx - _ext), _p1y)
            _bar2.setFirstEndpoint((_p2x - _offset), _p2y)
            _bar2.setSecondEndpoint((_dx - _ext), _p2y)
        elif _dx > max(_p1x, _p2x):
            _bar1.setFirstEndpoint((_p1x + _offset), _p1y)
            _bar1.setSecondEndpoint((_dx + _ext), _p1y)
            _bar2.setFirstEndpoint((_p2x + _offset), _p2y)
            _bar2.setSecondEndpoint((_dx + _ext), _p2y)
        else:
            if _dx > _p1x:
                _bar1.setFirstEndpoint((_p1x + _offset), _p1y)
                _bar1.setSecondEndpoint((_dx + _ext), _p1y)
            else:
                _bar1.setFirstEndpoint((_p1x - _offset), _p1y)
                _bar1.setSecondEndpoint((_dx - _ext), _p1y)
            if _dx > _p2x:
                _bar2.setFirstEndpoint((_p2x + _offset), _p2y)
                _bar2.setSecondEndpoint((_dx + _ext), _p2y)
            else:
                _bar2.setFirstEndpoint((_p2x - _offset), _p2y)
                _bar2.setSecondEndpoint((_dx - _ext), _p2y)
        if _allpts:
            self.calcMarkerPoints()

class RadialDimension(Dimension):
    """A class for Radial dimensions.

The RadialDimension class is derived from the Dimension
class, so it shares all of those methods and attributes.
A RadialDimension should be used to display either the
radius or diamter of a Circle object.

A RadialDimension object has the following methods:

{get/set}DimCircle(): Get/Set the measured circle object.
getDimLayer(): Return the layer containing the measured circle.
{get/set}DiaMode(): Get/Set if the RadialDimension should return diameters.
getDimXPoints(): Get the x-coordinates of the dimension bar positions.
getDimYPoints(): Get the y-coordinates of the dimension bar positions.
getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
getDimCrossbar(): Get the DimCrossbar object of the RadialDimension.
calcDimValues(): Calculate the endpoint of the dimension line.
mapCoords(): Return coordinates on the dimension near some point.
onDimension(): Test if an x/y coordinate pair fall on the dimension line.
    """
    messages = {
    'dimobj_changed' : True,
    'mode_changed' : True,
    }
    def __init__(self, lyr, cir, x, y, ds, **kw):
        """Initialize a RadialDimension object.

rdim = RadialDimension(cl, cir, x, y, ds)

lyr: A Layer object
cir: A Circle or Arc object
x: The x-coordinate of the dimensional text
y: The y-coordinate of the dimensional text
ds: The DimStyle used for this Dimension.
        """
        Dimension.__init__(self, ds, x, y, **kw)
        if not isinstance(lyr, layer.Layer):
            raise TypeError, "Invalid layer: " + `lyr`
        if not isinstance(cir, circle.Circle):
            raise TypeError, "Invalid circle: " + `cir`
        _lc = lyr.findObject(cir)
        if _lc is not cir:
            raise ValueError, "Circle not found in layer!"
        self.__layer = lyr
        self.__circle = cir
        self.__crossbar = DimCrossbar()
        self.__dia_mode = False
        self.applyDimStyle()
        cir.storeUser(self)
        cir.connect('moved', self, RadialDimension.moveCircle)

    def __eq__(self, rdim):
        """Compare two RadialDimensions for equality.
        """
        if not isinstance(rdim, RadialDimension):
            return False
        _val = False
        _rc = rdim.getDimCircle()
        _rl = rdim.getDimLayer()
        if self.__layer is _rl and self.__circle == _rc:
            _val = True
        return _val

    def __ne__(self, rdim):
        """Compare two RadialDimensions for inequality.
        """
        if not isinstance(rdim, RadialDimension):
            return True
        _val = True
        _rc = rdim.getDimCircle()
        _rl = rdim.getDimLayer()
        if self.__layer is _rl and self.__circle == _rc:
            _val = False
        return _val

    def getValues(self):
        _x, _y = self.getLocation()
        _data = (self.__layer.getID(), self.__circle.getID(), _x, _y)
        return ('rdim',) + Dimension.getValues(self) + _data

    def applyDimStyle(self):
        """Apply the values in the DimStyle to the RadialDimension.

applyDimStyle()

This method extends the Dimension::applyDimStyle() method.
        """
        Dimension.applyDimStyle(self)
        _ds = self.getDimStyle()
        _pds = self.getPrimaryDimstring()
        _pds.setPrefix(_ds.getValue('RADIAL_DIM_PRIMARY_PREFIX'))
        _pds.setSuffix(_ds.getValue('RADIAL_DIM_PRIMARY_SUFFIX'))
        _sds = self.getSecondaryDimstring()
        _sds.setPrefix(_ds.getValue('RADIAL_DIM_SECONDARY_PREFIX'))
        _sds.setSuffix(_ds.getValue('RADIAL_DIM_SECONDARY_SUFFIX'))
        self.setDiaMode(_ds.getValue('RADIAL_DIM_DIA_MODE'))

    def getDiaMode(self):
        """Return if the RadialDimension will return diametrical values.

getDiaMode()

This method returns True if the diameter value is returned,
and False otherwise.
        """
        return self.__dia_mode

    def setDiaMode(self, mode=False):
        """Set the RadialDimension to return diametrical values.

setDiaMode([mode])

Calling this method without an argument sets the RadialDimension
to return radial measurements. If the argument "mode" is supplied,
it should be either True or False.

If the RadialDimension is measuring an arc, the returned value
will always be set to return a radius.
        """
        if mode is True or mode is False:
            if not isinstance(self.__circle, arc.Arc):
                _m = self.__dia_mode
                if _m is not mode:
                    self.__dia_mode = mode
                    self.sendMessage('mode_changed', _m)
                    self.calcDimValues()
                    self.modified()
        else:
            raise ValueError, "Invalid diameter mode: " + str(mode)

    dia_mode = property(getDiaMode, setDiaMode, None,
                        "Draw the Dimension as a diameter")

    def getDimLayer(self):
        """Return the Layer object holding the Circle for this RadialDimension.

getDimLayer()
        """
        return self.__layer

    def getDimCircle(self):
        """Return the Circle object this RadialDimension is measuring.

getDimCircle()
        """
        return self.__circle

    def setDimCircle(self, l, c):
        """Set the Circle object measured by this RadialDimension.

setDimCircle(l, c)

The arguments for this method are:

l: A Layer containing circle c
c: A circle contained in layer l
        """
        if self.isLocked():
            raise RuntimeError, "Setting circle/arc not allowed - object locked."
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(c, circle.Circle):
            raise TypeError, "Invalid circle: " + `c`
        _lc = l.findObject(c)
        if _lc is not c:
            raise ValueError, "Circle not found in layer!"
        _circ = self.__circle
        if _circ is not c:
            _circ.disconnect(self)
            _circ.freeUser(self)
            self.__layer = l
            self.__circle = c
            c.storeUser(self)
            self.sendMessage('dimobj_changed', _circ, c)
            self.calcDimValues()
            self.modified()

    circle = property(getDimCircle, None, None,
                      "Radial dimension circle object.")

    def getDimCrossbar(self):
        """Get the DimCrossbar object used by the RadialDimension.

getDimCrossbar()
        """
        return self.__crossbar

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display.

calcDimValues([allpts])

The optional argument "allpts" is by default True. Calling
this method with the argument set to False will skip the
calculation of any dimension endpoint marker points.
        """
        _allpts = allpts
        if _allpts is not True and _allpts is not False:
            raise ValueError, "Invalid argument: " + str(allpts)
        _c = self.__circle
        _dimbar = self.__crossbar
        _cx, _cy = _c.getCenter().getCoords()
        _rad = _c.getRadius()
        _dx, _dy = self.getLocation()
        _dia_mode = self.__dia_mode
        _sep = math.hypot((_dx - _cx), (_dy - _cy))
        _angle = math.atan2((_dy - _cy), (_dx - _cx))
        _sx = _rad * math.cos(_angle)
        _sy = _rad * math.sin(_angle)
        if isinstance(_c, arc.Arc):
            assert _dia_mode is False, "dia_mode for arc radial dimension"
            _sa = _c.getStartAngle()
            _ea = _c.getEndAngle()
            _angle = _rtd * _angle
            if _angle < 0.0:
                _angle = _angle + 360.0
            if not _c.throughAngle(_angle):
                _ep1, _ep2 = _c.getEndpoints()
                if _angle < _sa:
                    _sa = _dtr * _sa
                    _sx = _rad * math.cos(_sa)
                    _sy = _rad * math.sin(_sa)
                    if _sep > _rad:
                        _dx = _cx + (_sep * math.cos(_sa))
                        _dy = _cy + (_sep * math.sin(_sa))
                if _angle > _ea:
                    _ea = _dtr * _ea
                    _sx = _rad * math.cos(_ea)
                    _sy = _rad * math.sin(_ea)
                    if _sep > _rad:
                        _dx = _cx + (_sep * math.cos(_ea))
                        _dy = _cy + (_sep * math.sin(_ea))
        if _dia_mode:
            _dimbar.setFirstEndpoint((_cx - _sx), (_cy - _sy))
            _dimbar.setFirstCrossbarPoint((_cx - _sx), (_cy - _sy))
        else:
            _dimbar.setFirstEndpoint(_cx, _cy)
            _dimbar.setFirstCrossbarPoint(_cx, _cy)
        if _sep > _rad:
            _dimbar.setSecondEndpoint(_dx, _dy)
        else:
            _dimbar.setSecondEndpoint((_cx + _sx), (_cy + _sy))
        _dimbar.setSecondCrossbarPoint((_cx + _sx), (_cy + _sy))
        if not _allpts:
            return
        #
        # calculate dimension endpoint marker coordinates
        #
        _type = self.getEndpointType()
        _dimbar.clearMarkerPoints()
        if _type == Dimension.NO_ENDPOINT or _type == Dimension.CIRCLE:
            return
        _size = self.getEndpointSize()
        _x1, _y1 = _dimbar.getFirstCrossbarPoint()
        _x2, _y2 = _dimbar.getSecondCrossbarPoint()
        _sine, _cosine = _dimbar.getSinCosValues()
        if _type == Dimension.ARROW or _type == Dimension.FILLED_ARROW:
            _height = _size/5.0
            # p1 -> (x,y) = (size, _height)
            _mx = (_cosine * _size - _sine * _height) + _x1
            _my = (_sine * _size + _cosine * _height) + _y1
            _dimbar.storeMarkerPoint(_mx, _my)
            # p2 -> (x,y) = (size, -_height)
            _mx = (_cosine * _size - _sine *(-_height)) + _x1
            _my = (_sine * _size + _cosine *(-_height)) + _y1
            _dimbar.storeMarkerPoint(_mx, _my)
            # p3 -> (x,y) = (-size, _height)
            _mx = (_cosine * (-_size) - _sine * _height) + _x2
            _my = (_sine * (-_size) + _cosine * _height) + _y2
            _dimbar.storeMarkerPoint(_mx, _my)
            # p4 -> (x,y) = (-size, -_height)
            _mx = (_cosine * (-_size) - _sine *(-_height)) + _x2
            _my = (_sine * (-_size) + _cosine *(-_height)) + _y2
            _dimbar.storeMarkerPoint(_mx, _my)
        elif _type == Dimension.SLASH:
            _angle = 30.0 * _dtr # slope of slash
            _height = 0.5 * _size * math.sin(_angle)
            _length = 0.5 * _size * math.cos(_angle)
            # p1 -> (x,y) = (-_length, -_height)
            _sx1 = (_cosine * (-_length) - _sine * (-_height))
            _sy1 = (_sine * (-_length) + _cosine * (-_height))
            # p2 -> (x,y) = (_length, _height)
            _sx2 = (_cosine * _length - _sine * _height)
            _sy2 = (_sine * _length + _cosine * _height)
            #
            # shift the calculate based on the location of the
            # marker point
            #
            _mx = _sx1 + _x1
            _my = _sy1 + _y1
            _dimbar.storeMarkerPoint(_mx, _my)
            _mx = _sx2 + _x1
            _my = _sy2 + _y1
            _dimbar.storeMarkerPoint(_mx, _my)
            _mx = _sx1 + _x2
            _my = _sy1 + _y2
            _dimbar.storeMarkerPoint(_mx, _my)
            _mx = _sx2 + _x2
            _my = _sy2 + _y2
            _dimbar.storeMarkerPoint(_mx, _my)
        else:
            raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)

    def calculate(self):
        """Return the radius or diamter of this RadialDimension.

calculate()

By default, a RadialDimension will return the radius of the
circle. The setDiaMode() method can be called to set the
returned value to corresponed to a diameter.
        """
        _val = self.__circle.getRadius()
        if self.__dia_mode is True:
            _val = _val * 2.0
        return _val

    def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
        """Return whether or not a RadialDimension exists within a region.

isRegion(xmin, ymin, xmax, ymax[, fully])

The four arguments define the boundary of an area, and the
function returns True if the RadialDimension lies within that
area. If the optional argument "fully" is used and is True,
then the dimensioned circle and the location of the dimension
text must lie within the boundary. Otherwise, the function
returns False.
        """
        _xmin = xmin
        if not isinstance(_xmin, float):
            _xmin = float(xmin)
        _ymin = ymin
        if not isinstance(_ymin, float):
            _ymin = float(ymin)
        _xmax = xmax
        if not isinstance(_xmax, float):
            _xmax = float(xmax)
        if _xmax < _xmin:
            raise ValueError, "Illegal values: xmax < xmin"
        _ymax = ymax
        if not isinstance(_ymax, float):
            _ymax = float(ymax)
        if _ymax < _ymin:
            raise ValueError, "Illegal values: ymax < ymin"
        if fully is not True and fully is not False:
            raise ValueError, "Invalid flag: " + `fully`
        _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
        if ((_dxmin > _xmax) or
            (_dymin > _ymax) or
            (_dxmax < _xmin) or
            (_dymax < _ymin)):
            return False
        if fully:
            if ((_dxmin > _xmin) and
                (_dymin > _ymin) and
                (_dxmax < _xmax) and
                (_dymax < _ymax)):
                return True
            return False
        _dx, _dy = self.getLocation()
        if _xmin < _dx < _xmax and _ymin < _dy < _ymax: # dim text
            return True
        _p1, _p2 = self.__crossbar.getEndpoints()
        _x1, _y1 = _p1
        _x2, _y2 = _p2
        return util.in_region(_x1, _y1, _x2, _y2, _xmin, _ymin, _xmax, _ymax)

    def mapCoords(self, x, y, tol=tolerance.TOL):
        """Test an x/y coordinate pair if it could lay on the dimension.

mapCoords(x, y[, tol])

This method has two required parameters:

x: The x-coordinate
y: The y-coordinate

These should both be float values.

There is an optional third parameter, "tol", giving
the maximum distance from the dimension bars that the
x/y coordinates may sit.
        """
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _t = tolerance.toltest(tol)
        _p1, _p2 = self.__crossbar.getEndpoints()
        return util.map_coords(_x, _y, _p1[0], _p1[0], _p2[0], _p2[1], _t)

    def onDimension(self, x, y, tol=tolerance.TOL):
        return self.mapCoords(x, y, tol) is not None

    def getBounds(self):
        """Return the minimal and maximal locations of the dimension

getBounds()

This method overrides the Dimension::getBounds() method
        """
        _ep1, _ep2 = self.__crossbar.getEndpoints()
        _ex1, _ey1 = _ep1
        _ex2, _ey2 = _ep2
        _xmin = min(_ex1, _ex2)
        _ymin = min(_ey1, _ey2)
        _xmax = max(_ex1, _ex2)
        _ymax = max(_ey1, _ey2)
        return _xmin, _ymin, _xmax, _ymax

    def moveCircle(self, circ, *args):
        _alen = len(args)
        if _alen < 3:
            raise ValueError, "Invalid argument count: %d" % _alen
        if circ is not self.__circle:
            raise ValueError, "Unexpected sender: " + `circ`
        _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
        self.calcDimValues(True)
        self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)

    def sendsMessage(self, m):
        if m in RadialDimension.messages:
            return True
        return Dimension.sendsMessage(self, m)

class AngularDimension(Dimension):
    """A class for Angular dimensions.

The AngularDimension class is derived from the Dimension
class, so it shares all of those methods and attributes.

AngularDimension objects have the following methods:

{get/set}VertexPoint(): Get/Set the vertex point for the AngularDimension.
{get/set}P1(): Get/Set the first Point for the AngularDimension.
{get/set}P2(): Get/Set the second Point for the AngularDimension.
getDimPoints(): Return the two Points used in this dimension.
getDimLayers(): Return the two Layers holding the Points.
getDimXPoints(): Get the x-coordinates of the dimension bar positions.
getDimYPoints(): Get the y-coordinates of the dimension bar positions.
getDimAngles(): Get the angles at which the dimension bars should be drawn.
getDimMarkerPoints(): Get the locaiton of the dimension endpoint markers.
calcDimValues(): Calculate the endpoint of the dimension line.
mapCoords(): Return coordinates on the dimension near some point.
onDimension(): Test if an x/y coordinate pair fall on the dimension line.
invert(): Switch the endpoints used to measure the dimension
    """

    messages = {
    'point_changed' : True,
    'inverted' : True,
    }
    def __init__(self, vl, vp, l1, p1, l2, p2, x, y, ds, **kw):
        """Initialize an AngularDimension object.

adim = AngularDimension(vl, vp, l1, p1, l2, p2, x, y, ds)

vl: A Layer holding Point vp
vp: A Point contained in layer vl
l1: A Layer holding Point p1
p1: A Point contained in Layer l1
l2: A Layer holding Point p2
p2: A Point contained in Layer l2
x: The x-coordinate of the dimensional text
y: The y-coordinate of the dimensional text
ds: The DimStyle used for this Dimension.
        """
        Dimension.__init__(self, ds, x, y, **kw)
        if not isinstance(vl, layer.Layer):
            raise TypeError, "Invalid layer: " + `vl`
        if not isinstance(vp, point.Point):
            raise TypeError, "Invalid point: " + `vp`
        _lp = vl.findObject(vp)
        if _lp is not vp:
            raise ValueError, "Vertex Point not found in layer!"
        if not isinstance(l1, layer.Layer):
            raise TypeError, "Invalid layer: " + `l1`
        if not isinstance(p1, point.Point):
            raise TypeError, "Invalid point: " + `p1`
        _lp = l1.findObject(p1)
        if _lp is not p1:
            raise ValueError, "Point p1 not found in layer!"
        if not isinstance(l2, layer.Layer):
            raise TypeError, "Invalid layer: " + `l2`
        if not isinstance(p2, point.Point):
            raise TypeError, "Invalid point: " + `p2`
        _lp = l2.findObject(p2)
        if _lp is not p2:
            raise ValueError, "Point p2 not found in layer!"
        self.__vl = vl
        self.__vp = vp
        self.__l1 = l1
        self.__p1 = p1
        self.__l2 = l2
        self.__p2 = p2
        self.__bar1 = DimBar()
        self.__bar2 = DimBar()
        self.__crossarc = DimCrossarc()
        self.applyDimStyle()        
        vp.storeUser(self)
        vp.connect('moved', self, AngularDimension.movePoint)
        p1.storeUser(self)
        p1.connect('moved', self, AngularDimension.movePoint)
        p2.storeUser(self)
        p2.connect('moved', self, AngularDimension.movePoint)

    def __eq__(self, adim):
        """Compare two AngularDimensions for equality.
        """
        if not isinstance(adim, AngularDimension):
            return False
        _val = False
        _vl, _l1, _l2 = adim.getDimLayers()
        _vp, _p1, _p2 = adim.getDimPoints()
        if (self.__vl is _vl and
            self.__vp == _vp and
            self.__l1 is _l1 and
            self.__p1 == _p1 and
            self.__l2 is _l2 and
            self.__p2 == _p2):
            _val = True
        return _val

    def __ne__(self, adim):
        """Compare two AngularDimensions for inequality.
        """
        if not isinstance(adim, AngularDimension):
            return True
        _val = True
        _vl, _l1, _l2 = adim.getDimLayers()
        _vp, _p1, _p2 = adim.getDimPoints()
        if (self.__vl is _vl and
            self.__vp == _vp and
            self.__l1 is _l1 and
            self.__p1 == _p1 and
            self.__l2 is _l2 and
            self.__p2 == _p2):
            _val = False
        return _val

    def getValues(self):
        _x, _y = self.getLocation()
        _data = (self.__vl.getID(), self.__vp.getID(),
                 self.__l1.getID(), self.__p1.getID(),
                 self.__l2.getID(), self.__p2.getID(),
                 _x, _y)
        return ('adim',) + Dimension.getValues(self) + _data

    def applyDimStyle(self):
        """Set the values in the AngularDimension's DimStyle.

applyDimStyle()

This method extends the Dimension::applyDimStyle() method.
        """
        Dimension.applyDimStyle(self)
        _ds = self.getDimStyle()
        _pds = self.getPrimaryDimstring()
        _pds.setPrefix(_ds.getValue('ANGULAR_DIM_PRIMARY_PREFIX'))
        _pds.setSuffix(_ds.getValue('ANGULAR_DIM_PRIMARY_SUFFIX'))
        _sds = self.getSecondaryDimstring()
        _sds.setPrefix(_ds.getValue('ANGULAR_DIM_SECONDARY_PREFIX'))
        _sds.setSuffix(_ds.getValue('ANGULAR_DIM_SECONDARY_SUFFIX'))

    def getDimLayers(self):
        """Return the layers used in an AngularDimension.

getDimLayers()
        """
        return self.__vl, self.__l1, self.__l2

    def getDimPoints(self):
        """Return the points used in an AngularDimension.

getDimPoints()
        """
        return self.__vp, self.__p1, self.__p2

    def getVertexPoint(self):
        """Return the vertex point used in an AngularDimension.

getVertexPoint()
        """
        return self.__vp

    def setVertexPoint(self, l, p):
        """Set the vertex point for an AngularDimension.

setVertexPoint(l, p)

There are two required arguments for this method

l: The layer holding the Point p
p: A point in Layer l
        """
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(p, point.Point):
            raise TypeError, "Invalid point: " + `p`
        _lp = l.findObject(p)
        if _lp is not p:
            raise ValueError, "Point not found in layer!"
        _vp = self.__vp
        if _vp is not p:
            _vp.disconnect(self)
            _vp.freeUser(self)
            self.__vl = l
            self.__vp = p
            p.storeUser(self)
            p.connect('moved', self, AngularDimension.movePoint)
            self.sendMessage('point_changed', _vp, p)
            self.calcDimValues()
            if abs(_vp.x - p.x) > 1e-10 or abs(_vp.y - p.y) > 1e-10:
                _x1, _y1 = self.__p1.getCoords()
                _x2, _y2 = self.__p2.getCoords()
                _dx, _dy = self.getLocation()
                self.sendMessage('moved', _vp.x, _vp.y, _x1, _y1,
                                 _x2, _y2, _dx, _dy)
            self.modified()

    vp = property(getVertexPoint, None, None,
                  "Angular Dimension vertex point.")

    def getP1(self):
        """Return the first angle point used in an AngularDimension.

getP1()
        """
        return self.__p1

    def setP1(self, l, p):
        """Set the first Point for an AngularDimension.

setP1(l, p)

There are two required arguments for this method

l: The layer holding the Point p
p: A point in Layer l
        """
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(p, point.Point):
            raise TypeError, "Invalid point: " + `p`
        _lp = l.findObject(p)
        if _lp is not p:
            raise ValueError, "Point not found in layer!"
        _p1 = self.__p1
        if _p1 is not p:
            _p1.disconnect(self)
            _p1.freeUser(self)
            self.__l1 = l
            self.__p1 = p
            p.storeUser(self)
            p.connect('moved', self, AngularDimension.movePoint)
            self.sendMessage('point_changed', _p1, p)
            self.calcDimValues()
            if abs(_p1.x - p.x) > 1e-10 or abs(_p1.y - p.y) > 1e-10:
                _vx, _vy = self.__vp.getCoords()
                _x2, _y2 = self.__p2.getCoords()
                _dx, _dy = self.getLocation()
                self.sendMessage('moved', _vx, _vy, _p1.x, _p1.y,
                                 _x2, _y2, _dx, _dy)
            self.modified()

    p1 = property(getP1, None, None, "Dimension first point.")

    def getP2(self):
        """Return the second angle point used in an AngularDimension.

getP2()
        """
        return self.__p2

    def setP2(self, l, p):
        """Set the second Point for an AngularDimension.

setP2(l, p)

There are two required arguments for this method

l: The layer holding the Point p
p: A point in Layer l
        """
        if not isinstance(l, layer.Layer):
            raise TypeError, "Invalid layer: " + `l`
        if not isinstance(p, point.Point):
            raise TypeError, "Invalid point: " + `p`
        _lp = l.findObject(p)
        if _lp is not p:
            raise ValueError, "Point not found in layer!"
        _p2 = self.__p2
        if _p2 is not p:
            _p2.disconnect(self)
            _p2.freeUser(self)
            self.__l2 = l
            self.__p2 = p
            p.storeUser(self)
            p.connect('moved', self, AngularDimension.movePoint)
            self.sendMessage('point_changed', _p2, p)
            self.calcDimValues()
            if abs(_p2.x - p.x) > 1e-10 or abs(_p2.y - p.y) > 1e-10:
                _vx, _vy = self.__vp.getCoords()
                _x1, _y1 = self.__p1.getCoords()
                _dx, _dy = self.getLocation()
                self.sendMessage('moved', _vx, _vy, _x1, _y1,
                                 _p2.x, _p2.y, _dx, _dy)
            self.modified()


    p2 = property(getP2, None, None, "Dimension second point.")

    def getDimAngles(self):
        """Get the array of dimension bar angles.

geDimAngles()
        """
        _angle1 = self.__bar1.getAngle()
        _angle2 = self.__bar2.getAngle()
        return _angle1, _angle2

    def getDimRadius(self):
        """Get the radius of the dimension crossarc.

getDimRadius()
        """
        return self.__crossarc.getRadius()

    def getDimBars(self):
        """Return the dimension boundary bars.

getDimBars()
        """
        return self.__bar1, self.__bar2

    def getDimCrossarc(self):
        """Get the DimCrossarc object used by the AngularDimension.

getDimCrossarc()
        """
        return self.__crossarc

    def invert(self):
        """Switch the endpoints used in this object.

invert()

Invoking this method on an AngularDimension will result in
it measuring the opposite angle than what it currently measures.
        """
        _ltemp = self.__l1
        _ptemp = self.__p1
        self.__l1 = self.__l2
        self.__p1 = self.__p2
        self.__l2 = _ltemp
        self.__p2 = _ptemp
        self.sendMessage('inverted')
        self.modified()

    def calculate(self):
        """Find the value of the angle measured by this AngularDimension.

calculate()
        """
        _vx, _vy = self.__vp.getCoords()
        _p1x, _p1y = self.__p1.getCoords()
        _p2x, _p2y = self.__p2.getCoords()
        _a1 = _rtd * math.atan2((_p1y - _vy), (_p1x - _vx))
        if _a1 < 0.0:
            _a1 = _a1 + 360.0
        _a2 = _rtd * math.atan2((_p2y - _vy), (_p2x - _vx))
        if _a2 < 0.0:
            _a2 = _a2 + 360.0
        _val = _a2 - _a1
        if _a1 > _a2:
            _val = _val + 360.0
        return _val

    def inRegion(self, xmin, ymin, xmax, ymax, fully=False):
        """Return whether or not an AngularDimension exists within a region.

isRegion(xmin, ymin, xmax, ymax[, fully])

The four arguments define the boundary of an area, and the
function returns True if the RadialDimension lies within that
area. If the optional argument "fully" is used and is True,
then the dimensioned circle and the location of the dimension
text must lie within the boundary. Otherwise, the function
returns False.
        """
        _xmin = xmin
        if not isinstance(_xmin, float):
            _xmin = float(xmin)
        _ymin = ymin
        if not isinstance(_ymin, float):
            _ymin = float(ymin)
        _xmax = xmax
        if not isinstance(_xmax, float):
            _xmax = float(xmax)
        if _xmax < _xmin:
            raise ValueError, "Illegal values: xmax < xmin"
        _ymax = ymax
        if not isinstance(_ymax, float):
            _ymax = float(ymax)
        if _ymax < _ymin:
            raise ValueError, "Illegal values: ymax < ymin"
        if fully is not True and fully is not False:
            raise ValueError, "Invalid flag: " + `fully`
        _vx, _vy = self.__vp.getCoords()
        _dx, _dy = self.getLocation()
        _pxmin, _pymin, _pxmax, _pymax = self.getBounds()
        _val = False
        if ((_pxmin > _xmax) or
            (_pymin > _ymax) or
            (_pxmax < _xmin) or
            (_pymax < _ymin)):
            return False
        if _xmin < _dx < _xmax and _ymin < _dy < _ymax:
            return True
        #
        # bar on vp-p1 line
        #
        _ep1, _ep2 = self.__bar1.getEndpoints()
        _ex1, _ey1 = _ep1
        _ex2, _ey2 = _ep2
        if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax):
            return True
        #
        # bar at vp-p2 line
        #
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _ex1, _ey1 = _ep1
        _ex2, _ey2 = _ep2
        if util.in_region(_ex1, _ey1, _ex2, _ey2, _xmin, _ymin, _xmax, _ymax):
            return True
        #
        # dimension crossarc
        #
        _val = False
        _r = self.__crossarc.getRadius()
        _d1 = math.hypot((_xmin - _vx), (_ymin - _vy))
        _d2 = math.hypot((_xmin - _vx), (_ymax - _vy))
        _d3 = math.hypot((_xmax - _vx), (_ymax - _vy))
        _d4 = math.hypot((_xmax - _vx), (_ymin - _vy))
        _dmin = min(_d1, _d2, _d3, _d4)
        _dmax = max(_d1, _d2, _d3, _d4)
        if _xmin < _vx < _xmax and _ymin < _vy < _ymax:
            _dmin = -1e-10
        else:
            if _vx > _xmax and _ymin < _vy < _ymax:
                _dmin = _vx - _xmax
            elif _vx < _xmin and _ymin < _vy < _ymax:
                _dmin = _xmin - _vx
            elif _vy > _ymax and _xmin < _vx < _xmax:
                _dmin = _vy - _ymax
            elif _vy < _ymin and _xmin < _vx < _xmax:
                _dmin = _ymin - _vy
        if _dmin < _r < _dmax:
            _da = _rtd * math.atan2((_ymin - _vy), (_xmin - _vx))
            if _da < 0.0:
                _da = _da + 360.0
            _val = self._throughAngle(_da)
            if _val:
                return _val
            _da = _rtd * math.atan2((_ymin - _vy), (_xmax - _vx))
            if _da < 0.0:
                _da = _da + 360.0
            _val = self._throughAngle(_da)
            if _val:
                return _val
            _da = _rtd * math.atan2((_ymax - _vy), (_xmax - _vx))
            if _da < 0.0:
                _da = _da + 360.0
            _val = self._throughAngle(_da)
            if _val:
                return _val
            _da = _rtd * math.atan2((_ymax - _vy), (_xmin - _vx))
            if _da < 0.0:
                _da = _da + 360.0
            _val = self._throughAngle(_da)
        return _val

    def _throughAngle(self, angle):
        """Test if the angular crossarc exists at a certain angle.

_throughAngle()

This method is private to the AngularDimension class.
        """
        _crossarc = self.__crossarc
        _sa = _crossarc.getStartAngle()
        _ea = _crossarc.getEndAngle()
        _val = True
        if abs(_sa - _ea) > 1e-10:
            if _sa > _ea:
                if angle > _ea and angle < _sa:
                    _val = False
            else:
                if angle > _ea or angle < _sa:
                    _val = False
        return _val

    def calcDimValues(self, allpts=True):
        """Recalculate the values for dimensional display.

calcDimValues([allpts])

The optional argument "allpts" is by default True. Calling
this method with the argument set to False will skip the
calculation of any dimension endpoint marker points.
        """
        _allpts = allpts
        if _allpts is not True and _allpts is not False:
            raise ValueError, "Invalid argument: " + str(allpts)
        _vx, _vy = self.__vp.getCoords()
        _p1x, _p1y = self.__p1.getCoords()
        _p2x, _p2y = self.__p2.getCoords()
        _dx, _dy = self.getLocation()
        _offset = self.getOffset()
        _ext = self.getExtension()
        _bar1 = self.__bar1
        _bar2 = self.__bar2
        _crossarc = self.__crossarc
        _dv1 = math.hypot((_p1x - _vx), (_p1y - _vy))
        _dv2 = math.hypot((_p2x - _vx), (_p2y - _vy))
        _ddp = math.hypot((_dx - _vx), (_dy - _vy))
        _crossarc.setRadius(_ddp)
        #
        # first dimension bar
        #
        _angle = math.atan2((_p1y - _vy), (_p1x - _vx))
        _sine = math.sin(_angle)
        _cosine = math.cos(_angle)
        _deg = _angle * _rtd
        if _deg < 0.0:
            _deg = _deg + 360.0
        _crossarc.setStartAngle(_deg)
        _ex = _vx + (_ddp * _cosine)
        _ey = _vy + (_ddp * _sine)
        _crossarc.setFirstCrossbarPoint(_ex, _ey)
        _crossarc.setFirstEndpoint(_ex, _ey)
        if _ddp > _dv1: # dim point is radially further to vp than p1
            _x1 = _p1x + (_offset * _cosine)
            _y1 = _p1y + (_offset * _sine)
            _x2 = _vx + ((_ddp + _ext) * _cosine)
            _y2 = _vy + ((_ddp + _ext) * _sine)
        else: # dim point is radially closer to vp than p1
            _x1 = _p1x - (_offset * _cosine)
            _y1 = _p1y - (_offset * _sine)
            _x2 = _vx + ((_ddp - _ext) * _cosine)
            _y2 = _vy + ((_ddp - _ext) * _sine)
        _bar1.setFirstEndpoint(_x1, _y1)
        _bar1.setSecondEndpoint(_x2, _y2)
        #
        # second dimension bar
        #
        _angle = math.atan2((_p2y - _vy), (_p2x - _vx))
        _sine = math.sin(_angle)
        _cosine = math.cos(_angle)
        _deg = _angle * _rtd
        if _deg < 0.0:
            _deg = _deg + 360.0
        _crossarc.setEndAngle(_deg)
        _ex = _vx + (_ddp * _cosine)
        _ey = _vy + (_ddp * _sine)
        _crossarc.setSecondCrossbarPoint(_ex, _ey)
        _crossarc.setSecondEndpoint(_ex, _ey)
        if _ddp > _dv2: # dim point is radially further to vp than p2
            _x1 = _p2x + (_offset * _cosine)
            _y1 = _p2y + (_offset * _sine)
            _x2 = _vx + ((_ddp + _ext) * _cosine)
            _y2 = _vy + ((_ddp + _ext) * _sine)
        else: # dim point is radially closers to vp than p2
            _x1 = _p2x - (_offset * _cosine)
            _y1 = _p2y - (_offset * _sine)
            _x2 = _vx + ((_ddp - _ext) * _cosine)
            _y2 = _vy + ((_ddp - _ext) * _sine)
        _bar2.setFirstEndpoint(_x1, _y1)
        _bar2.setSecondEndpoint(_x2, _y2)
        if not _allpts:
            return
        #
        # calculate dimension endpoint marker coordinates
        #
        _type = self.getEndpointType()
        _crossarc.clearMarkerPoints()
        if _type == Dimension.NO_ENDPOINT or _type == Dimension.CIRCLE:
            return
        _size = self.getEndpointSize()
        _a1 = _bar1.getAngle() - 90.0
        _a2 = _bar2.getAngle() - 90.0
        # print "a1: %g" % _a1
        # print "a2: %g" % _a2
        _mp1, _mp2 = _crossarc.getCrossbarPoints()
        _x1, _y1 = _mp1
        _x2, _y2 = _mp2
        # print "x1: %g" % _x1
        # print "y1: %g" % _y1
        # print "x2: %g" % _x2
        # print "y2: %g" % _y2
        _sin1 = math.sin(_dtr * _a1)
        _cos1 = math.cos(_dtr * _a1)
        _sin2 = math.sin(_dtr * _a2)
        _cos2 = math.cos(_dtr * _a2)
        if _type == Dimension.ARROW or _type == Dimension.FILLED_ARROW:
            _height = _size/5.0
            # p1 -> (x,y) = (size, _height)
            _mx = (_cos1 * (-_size) - _sin1 * _height) + _x1
            _my = (_sin1 * (-_size) + _cos1 * _height) + _y1
            _crossarc.storeMarkerPoint(_mx, _my)
            # p2 -> (x,y) = (size, -_height)
            _mx = (_cos1 * (-_size) - _sin1 *(-_height)) + _x1
            _my = (_sin1 * (-_size) + _cos1 *(-_height)) + _y1
            _crossarc.storeMarkerPoint(_mx, _my)
            # p3 -> (x,y) = (size, _height)
            _mx = (_cos2 * _size - _sin2 * _height) + _x2
            _my = (_sin2 * _size + _cos2 * _height) + _y2
            _crossarc.storeMarkerPoint(_mx, _my)
            # p4 -> (x,y) = (size, -_height)
            _mx = (_cos2 * _size - _sin2 *(-_height)) + _x2
            _my = (_sin2 * _size + _cos2 *(-_height)) + _y2
            _crossarc.storeMarkerPoint(_mx, _my)
        elif _type == Dimension.SLASH:
            _angle = 30.0 * _dtr # slope of slash
            _height = 0.5 * _size * math.sin(_angle)
            _length = 0.5 * _size * math.cos(_angle)
            # p1 -> (x,y) = (-_length, -_height)
            _mx = (_cos1 * (-_length) - _sin1 * (-_height)) + _x1
            _my = (_sin1 * (-_length) + _cos1 * (-_height)) + _y1
            _crossarc.storeMarkerPoint(_mx, _my)
            # p2 -> (x,y) = (_length, _height)
            _mx = (_cos1 * _length - _sin1 * _height) + _x1
            _my = (_sin1 * _length + _cos1 * _height) + _y1
            _crossarc.storeMarkerPoint(_mx, _my)
            # p3 -> (x,y) = (-_length, -_height)
            _mx = (_cos2 * (-_length) - _sin2 * (-_height)) + _x2
            _my = (_sin2 * (-_length) + _cos2 * (-_height)) + _y2
            _crossarc.storeMarkerPoint(_mx, _my)
            # p4 -> (x,y) = (_length, _height)
            _mx = (_cos2 * _length - _sin2 * _height) + _x2
            _my = (_sin2 * _length + _cos2 * _height) + _y2
            _crossarc.storeMarkerPoint(_mx, _my)
        else:
            raise ValueError, "Unexpected endpoint type: '%s'" % str(_type)

    def mapCoords(self, x, y, tol=tolerance.TOL):
        """Test an x/y coordinate pair hit the dimension lines and arc.

mapCoords(x, y[, tol])

This method has two required parameters:

x: The x-coordinate
y: The y-coordinate

These should both be float values.

There is an optional third parameter, "tol", giving
the maximum distance from the dimension bars that the
x/y coordinates may sit.
        """
        _x = x
        if not isinstance(_x, float):
            _x = float(x)
        _y = y
        if not isinstance(_y, float):
            _y = float(y)
        _t = tolerance.toltest(tol)
        #
        # test vp-p1 bar
        #
        _ep1, _ep2 = self.__bar1.getEndpoints()
        _ex1, _ey1 = _ep1
        _ex2, _ey2 = _ep2
        _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t)
        if _mp is not None:
            return _mp
        #
        # test vp-p2 bar
        #
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _mp = util.map_coords(_x, _y, _ex1, _ey1, _ex2, _ey2, _t)
        if _mp is not None:
            return _mp
        #
        # test the arc
        #
        _vx, _vy = self.__vp.getCoords()
        _psep = math.hypot((_vx - _x), (_vy - y))
        _dx, _dy = self.getLocation()
        _dsep = math.hypot((_vx - _dx), (_vy - _dy))
        _val = False
        if abs(_psep - _dsep) < _t:
            _crossarc = self.__crossarc
            _sa = _crossarc.getStartAngle()
            _ea = _crossarc.getEndAngle()
            _angle = _rtd * math.atan2((_vy - _y), (_vx - _x))
            _val = True
            if abs(_sa - _ea) > 1e-10:
                if _sa < _ea:
                    if _angle < _sa or  _angle > _ea:
                        _val = False
                else:
                    if _angle > _ea or _angle < _sa:
                        _val = False
        return _val

    def onDimension(self, x, y, tol=tolerance.TOL):
        return self.mapCoords(x, y, tol) is not None

    def getBounds(self):
        """Return the minimal and maximal locations of the dimension

getBounds()

This method overrides the Dimension::getBounds() method
        """
        _vx, _vy = self.__vp.getCoords()
        _dx, _dy = self.getLocation()
        _dxpts = []
        _dypts = []
        _ep1, _ep2 = self.__bar1.getEndpoints()
        _dxpts.append(_ep1[0])
        _dypts.append(_ep1[1])
        _dxpts.append(_ep2[0])
        _dypts.append(_ep2[1])
        _ep1, _ep2 = self.__bar2.getEndpoints()
        _dxpts.append(_ep1[0])
        _dypts.append(_ep1[1])
        _dxpts.append(_ep2[0])
        _dypts.append(_ep2[1])
        _rad = self.__crossarc.getRadius()
        if self._throughAngle(0.0):
            _dxpts.append((_vx + _rad))
        if self._throughAngle(90.0):
            _dypts.append((_vy + _rad))
        if self._throughAngle(180.0):
            _dxpts.append((_vx - _rad))
        if self._throughAngle(270.0):
            _dypts.append((_vy - _rad))
        _xmin = min(_dx, min(_dxpts))
        _ymin = min(_dy, min(_dypts))
        _xmax = max(_dx, max(_dxpts))
        _ymax = max(_dy, max(_dypts))
        return _xmin, _ymin, _xmax, _ymax

    def movePoint(self, p, *args):
        _alen = len(args)
        if _alen < 2:
            raise ValueError, "Invalid argument count: %d" % _alen
        if ((p is not self.__vp) and
            (p is not self.__p1) and
            (p is not self.__p2)):
            raise ValueError, "Unexpected dimension point: " + `p`
        _dxmin, _dymin, _dxmax, _dymax = self.getBounds()
        self.calcDimValues()
        self.sendMessage('moved', _dxmin, _dymin, _dxmax, _dymax)
        self.modified()

    def sendsMessage(self, m):
        if m in AngularDimension.messages:
            return True
        return Dimension.sendsMessage(self, m)

#
# class stuff for dimension styles
#

class DimStyle(object):
    """A class storing preferences for Dimensions

The DimStyle class stores a set of dimension parameters
that will be used when creating dimensions when the
particular style is active.

A DimStyle object has the following methods:

getName(): Return the name of the DimStyle.
getOption(): Return a single value in the DimStyle.
getOptions(): Return all the options in the DimStyle.
getValue(): Return the value of one of the DimStyle options.

getOption() and getValue() are synonymous.
    """

    #
    # the default values for the DimStyle class
    #

    __defaults = {
        'DIM_PRIMARY_FONT_FAMILY' : 'Sans',
        'DIM_PRIMARY_FONT_SIZE' : 12,
        'DIM_PRIMARY_FONT_WEIGHT' : text.NORMAL,
        'DIM_PRIMARY_FONT_STYLE' : text.NORMAL,
        'DIM_PRIMARY_FONT_COLOR' : color.Color(255,255,255),
        'DIM_PRIMARY_PREFIX' : u'',
        'DIM_PRIMARY_SUFFIX' : u'',
        'DIM_PRIMARY_PRECISION' : 3,
        'DIM_PRIMARY_UNITS' : units.MILLIMETERS,
        'DIM_PRIMARY_LEADING_ZERO' : True,
        'DIM_PRIMARY_TRAILING_DECIMAL' : True,
        'DIM_SECONDARY_FONT_FAMILY' : 'Sans',
        'DIM_SECONDARY_FONT_SIZE' : 12,
        'DIM_SECONDARY_FONT_WEIGHT' : text.NORMAL,
        'DIM_SECONDARY_FONT_STYLE' : text.NORMAL,
        'DIM_SECONDARY_FONT_COLOR' : color.Color(255,255,255),
        'DIM_SECONDARY_PREFIX' : u'',
        'DIM_SECONDARY_SUFFIX' : u'',
        'DIM_SECONDARY_PRECISION' : 3,
        'DIM_SECONDARY_UNITS' : units.MILLIMETERS,
        'DIM_SECONDARY_LEADING_ZERO' : True,
        'DIM_SECONDARY_TRAILING_DECIMAL' : True,
        'DIM_OFFSET' : 1.0,
        'DIM_EXTENSION' : 1.0,
        'DIM_COLOR' : color.Color(255,165,0),
        'DIM_POSITION' : Dimension.SPLIT,
        'DIM_ENDPOINT' : Dimension.NO_ENDPOINT,
        'DIM_ENDPOINT_SIZE' : 1.0,
        'DIM_DUAL_MODE' : False,
        'RADIAL_DIM_PRIMARY_PREFIX' : u'',
        'RADIAL_DIM_PRIMARY_SUFFIX' : u'',
        'RADIAL_DIM_SECONDARY_PREFIX' : u'',
        'RADIAL_DIM_SECONDARY_SUFFIX' : u'',
        'RADIAL_DIM_DIA_MODE' : False,
        'ANGULAR_DIM_PRIMARY_PREFIX' : u'',
        'ANGULAR_DIM_PRIMARY_SUFFIX' : u'',
        'ANGULAR_DIM_SECONDARY_PREFIX' : u'',
        'ANGULAR_DIM_SECONDARY_SUFFIX' : u'',
        }

    def __init__(self, name, keywords={}):
        """Instantiate a DimStyle object.

ds = DimStyle(name, keywords)

The argument "name" should be a unicode name, and the
"keyword" argument should be a dict. The keys should
be the same keywords used to set option values, such
as DIM_OFFSET, DIM_EXTENSION, etc, and the value corresponding
to each key should be set appropriately.
        """
        _n = name
        if not isinstance(_n, types.StringTypes):
            raise TypeError, "Invalid DimStyle name: "+ `_n`
        if isinstance(_n, str):
            _n = unicode(_n)
        if not isinstance(keywords, dict):
            raise TypeError, "Invalid keywords argument: " + `keywords`
        self.__opts = baseobject.ConstDict(str)
        self.__name = _n
        for _kw in keywords:
            _val = keywords[_kw]
            if _kw not in DimStyle.__defaults:
                raise KeyError, "Unknown DimStyle keyword: " + _kw
            _valid = options.test_option(_kw, _val)
            self.__opts[_kw] = _val

    def __eq__(self, obj):
        """Test a DimStyle object for equality with another DimStyle.
        """
        if not isinstance(obj, DimStyle):
            return False
        return self.__opts == obj.__opts

    def __ne__(self, obj):
        """Test a DimStyle object for inequality with another DimStyle.
        """
        if not isinstance(obj, DimStyle):
            return True
        return self.__opts != obj.__opts

    def getName(self):
        """Return the name of the DimStyle.

getName()
        """
        return self.__name

    name = property(getName, None, None, "DimStyle name.")

    def getOptions(self):
        """Return all the options stored within the DimStyle.

getOptions()
        """
        _keys = self.__opts.keys()
        for _key in DimStyle.__defaults:
            if _key not in self.__opts:
                _keys.append(_key)
        return _keys

    def getOption(self, key):
        """Return the value of a particular option in the DimStyle.

getOption(key)

The key should be one of the strings returned from getOptions. If
there is no value found in the DimStyle for the key, the value None
is returned.
        """
        if key in self.__opts:
            _val = self.__opts[key]
        elif key in DimStyle.__defaults:
            _val = DimStyle.__defaults[key]
        else:
            raise KeyError, "Unexpected DimStyle keyword: " + key
        return _val

    def getValue(self, key):
        """Return the value of a particular option in the DimStyle.

getValue(key)

The key should be one of the strings returned from getOptions. This
method raises a KeyError exception if the key is not found.

        """
        if key in self.__opts:
            _val = self.__opts[key]
        elif key in DimStyle.__defaults:
            _val = DimStyle.__defaults[key]
        else:
            raise KeyError, "Unexpected DimStyle keyword: " + key
        return _val

dimstyle_defaults = {
    'DIM_PRIMARY_FONT_FAMILY' : 'Sans',
    'DIM_PRIMARY_FONT_SIZE' : 12,
    'DIM_PRIMARY_FONT_WEIGHT' : text.NORMAL,
    'DIM_PRIMARY_FONT_STYLE' : text.NORMAL,
    'DIM_PRIMARY_FONT_COLOR' : color.Color(255,255,255),
    'DIM_PRIMARY_PREFIX' : u'',
    'DIM_PRIMARY_SUFFIX' : u'',
    'DIM_PRIMARY_PRECISION' : 3,
    'DIM_PRIMARY_UNITS' : units.MILLIMETERS,
    'DIM_PRIMARY_LEADING_ZERO' : True,
    'DIM_PRIMARY_TRAILING_DECIMAL': True,
    'DIM_SECONDARY_FONT_FAMILY' : 'Sans',
    'DIM_SECONDARY_FONT_SIZE' : 12,
    'DIM_SECONDARY_FONT_WEIGHT' : text.NORMAL,
    'DIM_SECONDARY_FONT_STYLE' : text.NORMAL,
    'DIM_SECONDARY_FONT_COLOR' : color.Color(255,255,255),
    'DIM_SECONDARY_PREFIX' : u'',
    'DIM_SECONDARY_SUFFIX' : u'',
    'DIM_SECONDARY_PRECISION' : 3,
    'DIM_SECONDARY_UNITS' : units.MILLIMETERS,
    'DIM_SECONDARY_LEADING_ZERO' : True,
    'DIM_SECONDARY_TRAILING_DECIMAL': True,
    'DIM_OFFSET' : 1.0,
    'DIM_EXTENSION': 1.0,
    'DIM_COLOR' : color.Color(255,165,0),
    'DIM_POSITION': Dimension.SPLIT,
    'DIM_ENDPOINT': Dimension.NO_ENDPOINT,
    'DIM_ENDPOINT_SIZE' : 1.0,
    'DIM_DUAL_MODE' : False,
    'RADIAL_DIM_PRIMARY_PREFIX' : u'',
    'RADIAL_DIM_PRIMARY_SUFFIX' : u'',
    'RADIAL_DIM_SECONDARY_PREFIX' : u'',
    'RADIAL_DIM_SECONDARY_SUFFIX' : u'',
    'RADIAL_DIM_DIA_MODE' : False,
    'ANGULAR_DIM_PRIMARY_PREFIX' : u'',
    'ANGULAR_DIM_PRIMARY_SUFFIX' : u'',
    'ANGULAR_DIM_SECONDARY_PREFIX' : u'',
    'ANGULAR_DIM_SECONDARY_SUFFIX' : u'',
    }

#
# Dimension history class
#

class DimLog(logger.Logger):
    def __init__(self, d):
        if not isinstance(s, Dimension):
            raise TypeError, "Invalid dimension: " + `d`
        logger.Logger.__init__(self)
        self.__d = d
        _ds1, _ds2 = d.getDimstrings()
        _ds1.connect('attribute_changed', self, DimLog.dsAttrChange)
        _ds2.connect('attribute_changed', self, DimLog.dsAttrChange)
        d.connect('attribute_changed', self, DimLog.AttrChange)
        d.connect('offset_changed', self, DimLog.offsetChange)
        d.connect('extension_changed', self, DimLog.extChange)
        # d.connect('moved', self, DimLog.moveDim)

    def moveDim(self, d, *args):
        _alen = len(args)
        if _alen < 4:
            raise ValueError, "Invalid argument count: %d" % _alen
        _xmin = args[0]
        if not isinstance(_xmin, float):
            _xmin = float(args[0])
        _ymin = args[1]
        if not isinstance(_ymin, float):
            _ymin = float(args[1])
        _xmax = args[2]
        if not isinstance(_xmax, float):
            _xmax = float(args[2])
        _ymax = args[3]
        if not isinstance(_ymax, float):
            _ymax = float(args[3])
        self.saveUndoData('moved', _xmin, _ymin, _xmax, _ymax)

    def dsAttrChange(self, ds, *args):
        _ds1, _ds2 = d.getDimStrings()
        if ds is _ds1:
            _dstr = 'ds1'
        elif ds is _ds2:
            _dstr = 'ds2'
        else:
            raise ValueError, "Unexpected Dimstring: " + `ds`
        _alen = len(args)
        if _alen < 2:
            raise ValueError, "Invalid argument count: %d" % _alen
        _attr = args[0]
        _val = args[1]
        if _attr == 'prefix' or _attr == 'suffix':
            if not isinstance(_val, unicode):
                _val = unicode(args[1])
        elif _attr == 'precision' or _attr == 'units':
            if not isinstance(_val, int):
                _val = int(args[1])
        elif _attr == 'print_zero' or _attr == 'print_decimal':
            if _val is not True and _val is not False:
                raise TypeError, "Invalid boolean: " + str(_val)
        else:
            raise ValueError, "Unexpected attribute: %s" % str(_attr)
        self.saveUndoData('attr_changed', _dstr, _attr, _val)
            
    def attrChanged(self, d, *args):
        _alen = len(args)
        if _alen < 2:
            raise ValueError, "Invalid argument count: %d" % _alen
        _attr = args[0]
        _val = args[1]
        if _attr == 'endpoint_size':
            if not isinstance(_val, float):
                _val = float(args[1])
        elif _attr == 'endpoint_type':
            if not isinstance(_val, float):
                _val = float(args[1])
        elif _attr == 'dual_mode':
            if _val is not True and _val is not False:
                raise ValueError, "Invalid value: " + str(_val)
        elif _attr == 'color':
            if not isinstance(_val, color.Color):
                raise TypeError, "Invalid color: " + str(_val)
            _col = _val
            _val = (_col.r, _col.g, col.b)
        elif _attr == 'style':
            if not isinstance(_val, DimStyle):
                raise TypeError, "Invalid DimStyle: " + `_attr`
            pass # fixme
        else:
            raise ValueError, "Unknown attribute: %s" % str(_attr)
        self.saveUndoData('attr_changed', None, _attr, _val)

    def offsetChange(self, d, *args):
        _alen = len(args)
        if _alen < 1:
            raise ValueError, "Invalid argument count: %d" % _alen
        _offset = args[0]
        if not isinstance(_offset, float):
            _offset = float(args[0])
        self.saveUndoData('offset_changed', _offset)

    def extChange(self, d, *args):
        _alen = len(args)
        if _alen < 1:
            raise ValueError, "Invalid argument count: %d" % _alen
        _extlen = args[0]
        if not isinstance(_extlen, float):
            _extlen = float(args[0])
        self.saveUndoData('ext_changed', _extlen)
        

    def execute(self, undo, *args):
        if undo is not True and undo is not False:
            raise ValueError, "Invalid undo value: " + str(undo)
        _alen = len(args)
        if len(args) == 0:
            raise ValueError, "No arguments to execute()"
        _d = self.__d
        _op = args[0]
        if _op == 'offset_changed':
            if len(args) < 2:
                raise ValueError, "Invalid argument count: %d" % _alen
            _off = args[1]
            if not isinstance(_off, float):
                _off = float(args[1])
            _cof = _d.getDimOffset()
            self.ignore('offset_changed')
            try:
                if undo:
                    self.saveUndoData('offset_changed', _cof)
                    _d.startUndo(True)
                    try:
                        _d.setDimOffset(_off)
                    finally:
                        _d.endUndo()
                    if abs(_cof - _off) > 1e-10:
                        pass # _d.sendMessage('moved')
                else:
                    self.saveRedoData('size_change', _cof)
                    _d.setDimOffset(_off)
            finally:
                self.receive('offset_changed')
        elif _op == 'ext_changed':
            if len(args) < 2:
                raise ValueError, "Invalid argument count: %d" % _alen
            _ext = args[1]
            if not isinstance(_ext, float):
                _ext = float(args[1])
            _cext = _d.getDimExtension()
            self.ignore('extension_changed')
            try:
                if undo:
                    self.saveUndoData('ext_changed', _d.getDimExtension())
                    _d.startUndo(True)
                    try:
                        _d.setDimExtension(_ext)
                    finally:
                        _d.endUndo()
                    if abs(_cext - _ext) > 1e-10:
                        pass # _d.sendMessage('moved')
                else:
                    self.saveRedoData('ext_changed', _d.getDimExtension())
                    _d.setDimExtension(_ext)
            finally:
                self.receive('extension_changed')
        elif _op == 'attr_changed':
            if len(args) < 4:
                raise ValueError, "Invalid argument count: %d" % _alen
            _ds = args[1]
            _attr = args[2]
            _val = args[3]
            #
            # add more error checking ...
            #
            _method = None
            _obj = _d
            if _ds is None: # base dimension
                if _attr == 'endpoint_size':
                    _method = Dimension.setEndpointSize
                elif _attr == 'endpoint_type':
                    _method = Dimension.setEndpointType
                elif _attr == 'dual_mode':
                    _method = Dimension.setDualDimMode
                elif _attr == 'color':
                    _method = Dimension.setColor
                else:
                    raise ValueError, "Unexpected attr: %s" % _attr
            else:
                _ds1, _ds2 = _d.getDimstrings()
                if _ds == 'ds1':
                    _obj = _ds1
                elif _ds == 'ds2':
                    _obj = _ds2
                else:
                    raise ValueError, "Unexpected dstr: %s" % _dstr
                if _attr == 'prefix':
                    DimString.setPrefix
                elif _attr == 'suffix':
                    DimString.setSuffix
                elif _attr == 'precision':
                    DimString.setPrecision
                elif _attr == 'units':
                    DimString.setUnits
                elif _attr == 'print_zero':
                    DimString.setPrintZero
                elif _attr == 'print_decimal':
                    DimString.setPrintDecimal
                else:
                    raise ValueError, "Unexpected attr: %s" % _attr
            self.ignore('attribute_changed')
            try:
                if undo:
                    self.saveUndoData('attr_changed', _ds, _attr, _val)
                    _obj.startUndo(True)
                    try:
                        _method(_obj, _val)
                    finally:
                        _obj.endUndo()
                else:
                    self.saveRedoData('attr_changed', _ds, _attr, _val)
                    _method(_obj, _val)
            finally:
                self.receive('attribute_changed')
        else:
            raise ValueError, "Unexpected operation: %s" % _op
            
