catching mouse click events under specific pixels

Hello..

Am having a hard time trying to figure out this problem. I want to catch double left click events created under specific lines that where created by DrawLine().

The idea I have is to create a list with the endpoints of the lines, something like this:

lines = [((Px1, Py1), (Px2, Py2)), ((Px3, Py3), (Px4, Py4)), ((Px5, Py5), (Px6, Py6))....

then iterate over each set of two points, and see if the position of the mouse (x,y) when the double left click event is raised, is in the line created by those 2 points using the slope-point equation.

The thing is that if I have lets say more than 20 lines, I think that the code could be too slow, its there a better way to implement this?

heres the code::.

import wx
from math import sqrt, radians, tan

class MainFrame(wx.Frame):
     def __init__(self, parent, id, title):
         wx.Frame.__init__(self, parent, id, title, size=(800, 600))
         self.Centre()
         wx.EVT_PAINT(self, self.OnPaint)
         self.SetBackgroundColour("black")
         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
         self.Bind(wx.EVT_MOTION, self.OnMotion)
         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
# self.Bind(wx.EVT_ERASE_BACKGROUND,self.OnEraseBackground)
         self.Initialize()

     def OnEraseBackground(self, evt):
         pass

     def OnLeftUp(self, evt):
         if self.x2 and self.y2:
             self.segments.append(((self.x1, self.y1), (self.x2, self.y2)))
             self.x1, self.y1 = self.x2, self.y2
         else:
             self.x1 = False
             self.y1 = False
             self.x2 = False
             self.y2 = False

     def OnLeftDown(self, evt):
         if evt.LeftDown():
             if not self.x1:
                 self.x1, self.y1 = evt.GetPosition()

     def OnMotion(self, evt):
         if evt.LeftIsDown():
             self.xv, self.yv = evt.GetPosition()
             self.x2, self.y2 = self.CalculateSecondPoint(self.xv, self.yv)
         self.Refresh(False)

     def CalculateSlope(self, x1, y1, x2, y2):
         """Function to calculate the slope of a line given two points
         """
         if x1 == x2:
             return False
         else:
             x1 = float(x1)
             x2 = float(x2)
             y1 = float(y1)
             y2 = float(y2)
             m = (y2 - y1)/(x2 - x1)
             return m

     def CalculateSecondPoint(self, xv, yv):
         mv = self.CalculateSlope(self.x1, self.y1, xv, yv)
         m = False
         if self.x1 < xv:
             #if the line grows to the right
             if tan(radians(300)) < mv <= 0:
                 #if the virtual line have a slope with an angle between 0 and 60 degrees
                 m = tan(radians(330))

             elif 0 < mv < tan(radians(60)):
                 #if the virtual line have a slope with an angle between 300 and 0 degrees
                 m = tan(radians(30))

         elif self.x1 > xv:
             #if the line grows to the left
             if tan(radians(179.9)) < mv < tan(radians(240)):
                 #if the virtual line have a slope with an angle between 110 and 180 degrees
                 m = tan(radians(30))

             elif tan(radians(120)) < mv < tan(radians(179.9)):
                 #if the virtual line have a slope with an angle between 180 and 240 degrees
                 m = tan(radians(150))

         if m:
             x2, y2 = self.Intersection(self.x1, self.y1, m, xv, yv)
         else:
             x2 = self.x1
             y2 = yv

         return x2, y2

     def Initialize(self):
         self.segments = []
         self.x1 = False
         self.y1 = False
         self.x2 = False
         self.y2 = False

     def Intersection(self, x1, y1, m, xc, yc):
         """Function to calculate the intersection point of two orthogonal lines
         given the slope of one of the line and one point of each line
         """

         y2 = (yc + (((y1/m) - x1 + xc)/m))/(1 + (1/(m**2)))
         x2 = ((y2 - y1)/m) + x1

         return x2, y2

     def OnPaint(self, dc):
         dc = wx.BufferedPaintDC(self)
         self.Draw(dc)

     def Draw(self, dc):
         dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
         dc.Clear()
         dc.SetPen(wx.Pen(wx.CYAN, 1))
# dc.DrawSpline(((240, 170), (240, 170), (285, 110)))
         if self.x2 and self.y2:
# dc.SetPen(wx.Pen('black', 1))
             dc.DrawLine(self.x1, self.y1, self.x2, self.y2)
         if len(self.segments) > 0:
             for segment in self.segments:
                 dc.DrawLine(segment[0][0], segment[0][1], segment[1][0], segment[1][1])

class App(wx.App):
     def OnInit(self):
         frame = MainFrame(None, -1, "test")
         frame.Show(True)
         return True

if __name__ == "__main__":
     app = App()
     app.MainLoop()

Am having a hard time trying to figure out this problem. I want to catch
double left click events created under specific lines that where created
by DrawLine().

The idea I have is to create a list with the endpoints of the lines,
something like this:

lines = [((Px1, Py1), (Px2, Py2)), ((Px3, Py3), (Px4, Py4)), ((Px5,
Py5), (Px6, Py6))....

then iterate over each set of two points, and see if the position of the
mouse (x,y) when the double left click event is raised, is in the line
created by those 2 points using the slope-point equation.

The thing is that if I have lets say more than 20 lines, I think that
the code could be too slow,

yes, it could be pretty pokey -- plus you have the issue of how far from the line you want to accept a click -- which requires the "distance from a line" equation, which you may be doing -- sorry, I didn't look closely at your code. Also, if you want to do more complex objects, splines, etc, then this gets far harder.

its there a better way to implement this?

yes. for one, there are more numerically simple ways to do the computation -- poke around on the net, in particular 'graphics gems" as a lot of good stuff.

But the way most folks do it not to to do the computational geometry, but rather, simply draw the line and see if the pixel in question was hit. There are two ways to do that:

1) first do a bouding box check or something to see if you are close. If so, draw the line again to an off screen bitmap, and see if the pixel of interest changes color. If you compute the shift, you can draw to a very small bitmap, so it can be quite fast.

2) everytime you draw theobject to the screen, also draw each object ina unique color to an offscrenn bitmap -- then all you need to do is check the color of the pixel in question, and you'll know what object (if any) was hit.

3) Forget writing all this code, and use wx.lib.floatcanvas ( it uses method 2 )

-Chris

heres the code::.

PS: add code as an enclosure, when you put it inline, email clients tend to screw up the indentation.

···

On 3/30/11 1:27 PM, PythonJourney wrote:

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

PythonJourney wrote:

     def CalculateSecondPoint(self, xv, yv):
         mv = self.CalculateSlope(self.x1, self.y1, xv, yv)
         m = False
         if self.x1 < xv:
             #if the line grows to the right
             if tan(radians(300)) < mv <= 0:
                 #if the virtual line have a slope with an angle between
0 and 60 degrees
                 m = tan(radians(330))

             elif 0 < mv < tan(radians(60)):
                 #if the virtual line have a slope with an angle between
300 and 0 degrees
                 m = tan(radians(30))

         elif self.x1 > xv:
             #if the line grows to the left
             if tan(radians(179.9)) < mv < tan(radians(240)):
                 #if the virtual line have a slope with an angle between
110 and 180 degrees
                 m = tan(radians(30))

             elif tan(radians(120)) < mv < tan(radians(179.9)):
                 #if the virtual line have a slope with an angle between
180 and 240 degrees
                 m = tan(radians(150))

One thing caught my eye -- all of those "tan(radians(x))" calls are
constants, but Python doesn't know that. You should precompute them,
and store them in global constants somewhere handy.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

yup -- but as a rule,you can usually do this kind of thing without trig at all. Here's the first hit on google for "distance between point and line segment"

http://www.codeguru.com/forum/printthread.php?t=194400

It shouldn't be hard to translate the C to python. And if you want it really fast, use Cython.

-Chris

···

On 3/30/11 2:17 PM, Tim Roberts wrote:

One thing caught my eye -- all of those "tan(radians(x))" calls are
constants, but Python doesn't know that. You should precompute them,
and store them in global constants somewhere handy.

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

El 30/03/2011 04:14 p.m., Christopher Barker escribi�:

yes. for one, there are more numerically simple ways to do the computation -- poke around on the net, in particular 'graphics gems" as a lot of good stuff.

But the way most folks do it not to to do the computational geometry, but rather, simply draw the line and see if the pixel in question was hit. There are two ways to do that:

1) first do a bouding box check or something to see if you are close. If so, draw the line again to an off screen bitmap, and see if the pixel of interest changes color. If you compute the shift, you can draw to a very small bitmap, so it can be quite fast.

2) everytime you draw theobject to the screen, also draw each object ina unique color to an offscrenn bitmap -- then all you need to do is check the color of the pixel in question, and you'll know what object (if any) was hit.

3) Forget writing all this code, and use wx.lib.floatcanvas ( it uses method 2 )

-Chris

I feel bad for not using it before since this is the third time you recommend me floatcanvas... the thing is that I still found myself having a bad time reading and understanding other ppl code (I guess am still new to all this)

I will now study the floatcanvas sample in the wx demos to see if I can implement it..

El 30/03/2011 04:47 p.m., Tim Roberts escribi�:

One thing caught my eye -- all of those "tan(radians(x))" calls are
constants, but Python doesn't know that. You should precompute them,
and store them in global constants somewhere handy.

yes, thats a newbie mistake, thanks for pointing it!..

El 30/03/2011 05:24 p.m., Christopher Barker escribi�:

One thing caught my eye -- all of those "tan(radians(x))" calls are
constants, but Python doesn't know that. You should precompute them,
and store them in global constants somewhere handy.

yup -- but as a rule,you can usually do this kind of thing without trig at all. Here's the first hit on google for "distance between point and line segment"

http://www.codeguru.com/forum/printthread.php?t=194400

It shouldn't be hard to translate the C to python. And if you want it really fast, use Cython.

-Chris

actually am not trying to found the distance between a line a a point.. what am doing is finding the intersection between two orthogonal lines, and because am working in a isometric plane (the lines can only be perpendicular to the isometric axes) am calculating the slopes I need to use depending on the position of the mouse..

To give a little more background if anyone is interested, what am developing (or trying to) is a software to design and simulate piping systems.. I already have the code for the calculations (minimum diameter, pipe losses, pressure losses, flow, etc etc) but I want to create a nice GUI for the software..

What am thinking of doing, is to create a CAD like canvas, where the user can create the piping drawing (isometric view and top view of the layout), and I need the lines to be "click-able" (sorry if this is not the right expression, English is not my native language) so that the user can resize them, move them, etc..

···

On 3/30/11 2:17 PM, Tim Roberts wrote:

I feel bad for not using it before since this is the third time you
recommend me floatcanvas... the thing is that I still found myself
having a bad time reading and understanding other ppl code (I guess am
still new to all this)

well, it's up to you, it just seems that you are re-inventing a lot of wheels that are in floatcanvas already.

Learning to read others' code is a very valuable skill -- it's the primary way I learn anything!

However, I do understand that it's often easier to write your own -- that way it does exactly what you need, and you fully understand it.

actually am not trying to found the distance between a line and a point..

I thought you were trying to determined whether the user had click on a line. I you do an exactly "point on line" calculation, it will only return true if the user clicked exactly on an infinitely thin line. Rather, you really want to know if the user clicked _near_ the line -- which can be determined by how close the point is to the infinitely thin line.

Drawing code is doing all this already, to determine what color to make each pixel, so it really makes more sense to draw it and see if the pixel color changes, to leverage that existing, well optimized, code.

If you only need to know the visible object clicked, I'd go with the floatcanvas approach -- draw each object you want "clickable" on an off screen bitmap in a unique color -- it's fast and robust.

what am doing is finding the intersection between two orthogonal lines,
and because am working in a isometric plane (the lines can only be
perpendicular to the isometric axes) am calculating the slopes I need to
use depending on the position of the mouse..

That seems a slightly different process than "was a given line clicked on?"

-Chris

···

On 3/30/11 7:43 PM, PythonJourney wrote:

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

El 31/03/2011 11:46 a.m., Christopher Barker escribi�:

I feel bad for not using it before since this is the third time you
recommend me floatcanvas... the thing is that I still found myself
having a bad time reading and understanding other ppl code (I guess am
still new to all this)

well, it's up to you, it just seems that you are re-inventing a lot of wheels that are in floatcanvas already.

Learning to read others' code is a very valuable skill -- it's the primary way I learn anything!

However, I do understand that it's often easier to write your own -- that way it does exactly what you need, and you fully understand it.

actually am not trying to found the distance between a line and a point..

I thought you were trying to determined whether the user had click on a line. I you do an exactly "point on line" calculation, it will only return true if the user clicked exactly on an infinitely thin line. Rather, you really want to know if the user clicked _near_ the line -- which can be determined by how close the point is to the infinitely thin line.

Drawing code is doing all this already, to determine what color to make each pixel, so it really makes more sense to draw it and see if the pixel color changes, to leverage that existing, well optimized, code.

If you only need to know the visible object clicked, I'd go with the floatcanvas approach -- draw each object you want "clickable" on an off screen bitmap in a unique color -- it's fast and robust.

what am doing is finding the intersection between two orthogonal lines,
and because am working in a isometric plane (the lines can only be
perpendicular to the isometric axes) am calculating the slopes I need to
use depending on the position of the mouse..

That seems a slightly different process than "was a given line clicked on?"

-Chris

yes, sorry for the confusion.. in that particular comment of mine I was talking about the sample code I posted... In the sample I havent done anything to accomplish the "catching mouse event" thing..

I was just explaining what the code does...

Am going to try floatcanvas, If I have any question I'll be mailing the list!....

thank you!...

···

On 3/30/11 7:43 PM, PythonJourney wrote: