Forward port recent changes on the 2.8 branch to HEAD

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@46083 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2007-05-16 23:39:42 +00:00
parent f6342fb5e6
commit 0b0849b5a5
87 changed files with 3807 additions and 1586 deletions

View File

@@ -0,0 +1,170 @@
"""
A Bounding Box object and assorted utilities , subclassed from a numpy array
"""
import numpy as N
class BBox(N.ndarray):
"""
A Bounding Box object:
Takes Data as an array. Data is any python sequence that can be turned into a
2x2 numpy array of floats:
[[MinX, MinY ],
[MaxX, MaxY ]]
It is a subclass of numpy.ndarray, so for the most part it can be used as
an array, and arrays that fit the above description can be used in its place.
Usually created by the factory functions:
asBBox
and
fromPoints
"""
def __new__(subtype, data):
"""
Takes Data as an array. Data is any python sequence that can be turned into a
2x2 numpy array of floats:
[[MinX, MinY ],
[MaxX, MaxY ]]
You don't usually call this directly. BBox objects are created with the factory functions:
asBBox
and
fromPoints
"""
arr = N.array(data, N.float)
arr.shape = (2,2)
if arr[0,0] > arr[1,0] or arr[0,1] > arr[1,1]:
# note: zero sized BB OK.
raise ValueError("BBox values not aligned: \n minimum values must be less that maximum values")
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
def Overlaps(self, BB):
"""
Overlap(BB):
Tests if the given Bounding Box overlaps with this one.
Returns True is the Bounding boxes overlap, False otherwise
If they are just touching, returns True
"""
if ( (self[1,0] >= BB[0,0]) and (self[0,0] <= BB[1,0]) and
(self[1,1] >= BB[0,1]) and (self[0,1] <= BB[1,1]) ):
return True
else:
return False
def Inside(self, BB):
"""
Inside(BB):
Tests if the given Bounding Box is entirely inside this one.
Returns True if it is entirely inside, or touching the
border.
Returns False otherwise
"""
if ( (BB[0,0] >= self[0,0]) and (BB[1,0] <= self[1,0]) and
(BB[0,1] >= self[0,1]) and (BB[1,1] <= self[1,1]) ):
return True
else:
return False
def Merge(self, BB):
"""
Joins this bounding box with the one passed in, maybe making this one bigger
"""
if BB[0,0] < self[0,0]: self[0,0] = BB[0,0]
if BB[0,1] < self[0,1]: self[0,1] = BB[0,1]
if BB[1,0] > self[1,0]: self[1,0] = BB[1,0]
if BB[1,1] > self[1,1]: self[1,1] = BB[1,1]
### This could be used for a make BB from a bunch of BBs
#~ def _getboundingbox(bboxarray): # lrk: added this
#~ # returns the bounding box of a bunch of bounding boxes
#~ upperleft = N.minimum.reduce(bboxarray[:,0])
#~ lowerright = N.maximum.reduce(bboxarray[:,1])
#~ return N.array((upperleft, lowerright), N.float)
#~ _getboundingbox = staticmethod(_getboundingbox)
## Save the ndarray __eq__ for internal use.
Array__eq__ = N.ndarray.__eq__
def __eq__(self, BB):
"""
__eq__(BB) The equality operator
A == B if and only if all the entries are the same
"""
return N.all(self.Array__eq__(BB))
def asBBox(data):
"""
returns a BBox object.
If object is a BBox, it is returned unaltered
If object is a numpy array, a BBox object is returned that shares a
view of the data with that array
"""
if isinstance(data, BBox):
return data
arr = N.asarray(data, N.float)
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
def fromPoints(Points):
"""
fromPoints (Points).
reruns the bounding box of the set of points in Points. Points can
be any python object that can be turned into a numpy NX2 array of Floats.
If a single point is passed in, a zero-size Bounding Box is returned.
"""
Points = N.asarray(Points, N.float).reshape(-1,2)
arr = N.vstack( (Points.min(0), Points.max(0)) )
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
def fromBBArray(BBarray):
"""
Builds a BBox object from an array of Bounding Boxes.
The resulting Bounding Box encompases all the included BBs.
The BBarray is in the shape: (Nx2x2) where BBarray[n] is a 2x2 array that represents a BBox
"""
#upperleft = N.minimum.reduce(BBarray[:,0])
#lowerright = N.maximum.reduce(BBarray[:,1])
# BBarray = N.asarray(BBarray, N.float).reshape(-1,2)
# arr = N.vstack( (BBarray.min(0), BBarray.max(0)) )
BBarray = N.asarray(BBarray, N.float).reshape(-1,2,2)
arr = N.vstack( (BBarray[:,0,:].min(0), BBarray[:,1,:].max(0)) )
return asBBox(arr)
#return asBBox( (upperleft, lowerright) ) * 2

View File

@@ -0,0 +1,354 @@
"""
Test code for the BBox Object
"""
import unittest
from BBox import *
class testCreator(unittest.TestCase):
def testCreates(self):
B = BBox(((0,0),(5,5)))
self.failUnless(isinstance(B, BBox))
def testType(self):
B = N.array(((0,0),(5,5)))
self.failIf(isinstance(B, BBox))
def testDataType(self):
B = BBox(((0,0),(5,5)))
self.failUnless(B.dtype == N.float)
def testShape(self):
B = BBox((0,0,5,5))
self.failUnless(B.shape == (2,2))
def testShape2(self):
self.failUnlessRaises(ValueError, BBox, (0,0,5) )
def testShape3(self):
self.failUnlessRaises(ValueError, BBox, (0,0,5,6,7) )
def testArrayConstruction(self):
A = N.array(((4,5),(10,12)), N.float_)
B = BBox(A)
self.failUnless(isinstance(B, BBox))
def testMinMax(self):
self.failUnlessRaises(ValueError, BBox, (0,0,-1,6) )
def testMinMax2(self):
self.failUnlessRaises(ValueError, BBox, (0,0,1,-6) )
def testMinMax(self):
# OK to have a zero-sized BB
B = BBox(((0,0),(0,5)))
self.failUnless(isinstance(B, BBox))
def testMinMax2(self):
# OK to have a zero-sized BB
B = BBox(((10.0,-34),(10.0,-34.0)))
self.failUnless(isinstance(B, BBox))
def testMinMax3(self):
# OK to have a tiny BB
B = BBox(((0,0),(1e-20,5)))
self.failUnless(isinstance(B, BBox))
def testMinMax4(self):
# Should catch tiny difference
self.failUnlessRaises(ValueError, BBox, ((0,0), (-1e-20,5)) )
class testAsBBox(unittest.TestCase):
def testPassThrough(self):
B = BBox(((0,0),(5,5)))
C = asBBox(B)
self.failUnless(B is C)
def testPassThrough2(self):
B = (((0,0),(5,5)))
C = asBBox(B)
self.failIf(B is C)
def testPassArray(self):
# Different data type
A = N.array( (((0,0),(5,5))) )
C = asBBox(A)
self.failIf(A is C)
def testPassArray2(self):
# same data type -- should be a view
A = N.array( (((0,0),(5,5))), N.float_ )
C = asBBox(A)
A[0,0] = -10
self.failUnless(C[0,0] == A[0,0])
class testIntersect(unittest.TestCase):
def testSame(self):
B = BBox(((-23.5, 456),(56, 532.0)))
C = BBox(((-23.5, 456),(56, 532.0)))
self.failUnless(B.Overlaps(C) )
def testUpperLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (0, 12),(10, 32.0) ) )
self.failUnless(B.Overlaps(C) )
def testUpperRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (12, 12),(25, 32.0) ) )
self.failUnless(B.Overlaps(C) )
def testLowerRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (12, 5),(25, 15) ) )
self.failUnless(B.Overlaps(C) )
def testLowerLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 5),(8.5, 15) ) )
self.failUnless(B.Overlaps(C) )
def testBelow(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 5),(8.5, 9.2) ) )
self.failIf(B.Overlaps(C) )
def testAbove(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 25.001),(8.5, 32) ) )
self.failIf(B.Overlaps(C) )
def testLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (4, 8),(4.95, 32) ) )
self.failIf(B.Overlaps(C) )
def testRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (17.1, 8),(17.95, 32) ) )
self.failIf(B.Overlaps(C) )
def testInside(self):
B = BBox( ( (-15, -25),(-5, -10) ) )
C = BBox( ( (-12, -22), (-6, -8) ) )
self.failUnless(B.Overlaps(C) )
def testOutside(self):
B = BBox( ( (-15, -25),(-5, -10) ) )
C = BBox( ( (-17, -26), (3, 0) ) )
self.failUnless(B.Overlaps(C) )
def testTouch(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (15, 8),(17.95, 32) ) )
self.failUnless(B.Overlaps(C) )
def testCorner(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (15, 25),(17.95, 32) ) )
self.failUnless(B.Overlaps(C) )
def testZeroSize(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (15, 25),(15, 25) ) )
self.failUnless(B.Overlaps(C) )
def testZeroSize2(self):
B = BBox( ( (5, 10),(5, 10) ) )
C = BBox( ( (15, 25),(15, 25) ) )
self.failIf(B.Overlaps(C) )
def testZeroSize3(self):
B = BBox( ( (5, 10),(5, 10) ) )
C = BBox( ( (0, 8),(10, 12) ) )
self.failUnless(B.Overlaps(C) )
def testZeroSize4(self):
B = BBox( ( (5, 1),(10, 25) ) )
C = BBox( ( (8, 8),(8, 8) ) )
self.failUnless(B.Overlaps(C) )
class testEquality(unittest.TestCase):
def testSame(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(B == C)
def testIdentical(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(B == B)
def testNotSame(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (1.0, 2.0), (5.0, 10.1) ) )
self.failIf(B == C)
def testWithArray(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = N.array( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(B == C)
def testWithArray2(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = N.array( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(C == B)
def testWithArray2(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = N.array( ( (1.01, 2.0), (5.0, 10.0) ) )
self.failIf(C == B)
class testInside(unittest.TestCase):
def testSame(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(B.Inside(C))
def testPoint(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (3.0, 4.0), (3.0, 4.0) ) )
self.failUnless(B.Inside(C))
def testPointOutside(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (-3.0, 4.0), (0.10, 4.0) ) )
self.failIf(B.Inside(C))
def testUpperLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (0, 12),(10, 32.0) ) )
self.failIf(B.Inside(C) )
def testUpperRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (12, 12),(25, 32.0) ) )
self.failIf(B.Inside(C) )
def testLowerRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (12, 5),(25, 15) ) )
self.failIf(B.Inside(C) )
def testLowerLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 5),(8.5, 15) ) )
self.failIf(B.Inside(C) )
def testBelow(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 5),(8.5, 9.2) ) )
self.failIf(B.Inside(C) )
def testAbove(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (-10, 25.001),(8.5, 32) ) )
self.failIf(B.Inside(C) )
def testLeft(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (4, 8),(4.95, 32) ) )
self.failIf(B.Inside(C) )
def testRight(self):
B = BBox( ( (5, 10),(15, 25) ) )
C = BBox( ( (17.1, 8),(17.95, 32) ) )
self.failIf(B.Inside(C) )
class testFromPoints(unittest.TestCase):
def testCreate(self):
Pts = N.array( ((5,2),
(3,4),
(1,6),
), N.float_ )
B = fromPoints(Pts)
#B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
self.failUnless(B[0,0] == 1.0 and
B[0,1] == 2.0 and
B[1,0] == 5.0 and
B[1,1] == 6.0
)
def testCreateInts(self):
Pts = N.array( ((5,2),
(3,4),
(1,6),
) )
B = fromPoints(Pts)
self.failUnless(B[0,0] == 1.0 and
B[0,1] == 2.0 and
B[1,0] == 5.0 and
B[1,1] == 6.0
)
def testSinglePoint(self):
Pts = N.array( (5,2), N.float_ )
B = fromPoints(Pts)
self.failUnless(B[0,0] == 5.0 and
B[0,1] == 2.0 and
B[1,0] == 5.0 and
B[1,1] == 2.0
)
def testListTuples(self):
Pts = [ (3, 6.5),
(13, 43.2),
(-4.32, -4),
(65, -23),
(-0.0001, 23.432),
]
B = fromPoints(Pts)
self.failUnless(B[0,0] == -4.32 and
B[0,1] == -23.0 and
B[1,0] == 65.0 and
B[1,1] == 43.2
)
class testMerge(unittest.TestCase):
A = BBox( ((-23.5, 456), (56, 532.0)) )
B = BBox( ((-20.3, 460), (54, 465 )) )# B should be completely inside A
C = BBox( ((-23.5, 456), (58, 540.0)) )# up and to the right or A
D = BBox( ((-26.5, 12), (56, 532.0)) )
def testInside(self):
C = self.A.copy()
C.Merge(self.B)
self.failUnless(C == self.A)
def testFullOutside(self):
C = self.B.copy()
C.Merge(self.A)
self.failUnless(C == self.A)
def testUpRight(self):
A = self.A.copy()
A.Merge(self.C)
self.failUnless(A[0] == self.A[0] and A[1] == self.C[1])
def testDownLeft(self):
A = self.A.copy()
A.Merge(self.D)
self.failUnless(A[0] == self.D[0] and A[1] == self.A[1])
class testBBarray(unittest.TestCase):
BBarray = N.array( ( ((-23.5, 456), (56, 532.0)),
((-20.3, 460), (54, 465 )),
((-23.5, 456), (58, 540.0)),
((-26.5, 12), (56, 532.0)),
),
dtype=N.float)
print BBarray
BB = asBBox( ((-26.5, 12.), ( 58. , 540.)) )
def testJoin(self):
BB = fromBBArray(self.BBarray)
self.failUnless(BB == self.BB, "Wrong BB was created. It was:\n%s \nit should have been:\n%s"%(BB, self.BB))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,115 @@
"""
Part of the floatcanvas.Utilities package.
This module contains assorted GUI-related utilities that can be used
with FloatCanvas
So far, they are:
RubberBandBox: used to draw a RubberBand Box on the screen
"""
import wx
from floatcanvas import FloatCanvas
class RubberBandBox:
"""
Class to provide a rubber band box that can be drawn on a Window
"""
def __init__(self, Canvas, CallBack, Tol=5):
"""
To initialize:
RubberBandBox(Canvas, CallBack)
Canvas: the FloatCanvas you want the Rubber band box to be used on
CallBack: is the method you want called when the mouse is
released. That method will be called, passing in a rect
parameter, where rect is: (Point, WH) of the rect in
world coords.
Tol: The tolerance for the smallest rectangle allowed. defaults
to 5. In pixels
Methods:
Enable() : Enables the Rubber Band Box (Binds the events)
Disable() : Enables the Rubber Band Box (Unbinds the events)
Attributes:
CallBack: The callback function, if it's replaced you need to
call Enable() again.
"""
self.Canvas = Canvas
self.CallBack = CallBack
self.Tol = Tol
self.Drawing = False
self.RBRect = None
self.StartPointWorld = None
return None
def Enable(self):
"""
Called when you want the rubber band box to be enabled
"""
# bind events:
self.Canvas.Bind(FloatCanvas.EVT_MOTION, self.OnMove )
self.Canvas.Bind(FloatCanvas.EVT_LEFT_DOWN, self.OnLeftDown)
self.Canvas.Bind(FloatCanvas.EVT_LEFT_UP, self.OnLeftUp )
def Disable(self):
"""
Called when you don't want the rubber band box to be enabled
"""
# unbind events:
self.Canvas.Unbind(FloatCanvas.EVT_MOTION)
self.Canvas.Unbind(FloatCanvas.EVT_LEFT_DOWN)
self.Canvas.Unbind(FloatCanvas.EVT_LEFT_UP)
def OnMove(self, event):
if self.Drawing:
x, y = self.StartPoint
Cornerx, Cornery = event.GetPosition()
w, h = ( Cornerx - x, Cornery - y)
if abs(w) > self.Tol and abs(h) > self.Tol:
# draw the RB box
dc = wx.ClientDC(self.Canvas)
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.RBRect:
dc.DrawRectangle(*self.RBRect)
self.RBRect = (x, y, w, h )
dc.DrawRectangle(*self.RBRect)
event.Skip() # skip so that other events can catch these
def OnLeftDown(self, event):
# Start drawing
self.Drawing = True
self.StartPoint = event.GetPosition()
self.StartPointWorld = event.Coords
def OnLeftUp(self, event):
# Stop Drawing
if self.Drawing:
self.Drawing = False
if self.RBRect:
WH = event.Coords - self.StartPointWorld
wx.CallAfter(self.CallBack, (self.StartPointWorld, WH))
self.RBRect = None
self.StartPointWorld = None

View File

@@ -0,0 +1,7 @@
"""
__init__ for the floatcanvas Utilities package
"""
pass