How to add a mouse event in a child class

edited February 2018 in Python Mode

Following this thread I'm trying to add a mouse event to a sketch using the toxiclibs library.

I'd like to be able to move a specific particle with the mouse and see the distortion on the springs grid.

Here's what the sketch looks like

https://imgur.com/a/vUSd1

Main .Pyde file

add_library('verletphysics')
add_library('toxiclibscore')
from toxi.physics2d import VerletPhysics2D
from toxi.physics2d.behaviors import GravityBehavior 
from Particle import particle
from Locker import locker

def setup():
    size(600, 600, OPENGL)
    smooth(10)
    global physics, collection, lockers, n_rowcol

    physics = VerletPhysics2D()
    physics.addBehavior(GravityBehavior(Vec2D(0, 1)))

    collection, springs, lockers  = [], [], []
    n_rowcol, lngth, strength = 40, 10, .6

    #Display particles and add to Physics
    [[collection.append(particle(100 + (e * 10), 10 + (f * 10))) for f in range(n_rowcol)] for e in range(n_rowcol)]
    [physics.addParticle(e) for e in collection]

    #Lock specific particles
    [collection[e].lock() for e in (0, n_rowcol - 1, n_rowcol ** 2 - n_rowcol, n_rowcol ** 2 - 1)]

    #Display springs
    for e in range(0, n_rowcol ** 2 , n_rowcol):
        for f in range(n_rowcol - 1):
            a = collection[e + f]
            b = collection[e + f + 1]
            s = VerletSpring2D(a, b, lngth, strength)
            physics.addSpring(s)

    for e in range(n_rowcol):
        for f in range(0, n_rowcol ** 2 - n_rowcol, n_rowcol):
            a = collection[e + f]
            b = collection[e + f + n_rowcol]
            s = VerletSpring2D(a, b, lngth, strength)
            physics.addSpring(s)

    #Display lockers (red circles to move with mouse) 
    for e in (0, n_rowcol - 1, n_rowcol ** 2 - n_rowcol, n_rowcol**2 - 1):
        lockers.append(locker(collection[e].x(), collection[e].y()))


def draw():
    background(0)

    #Framerate
    [fill(255), text(int(frameRate), 10, 20)]

    #Run physics
    physics.update()

    #Draw lockers (red circles, not lockers really)
    for e in lockers:
        e.display()
        e.mousePressed()
        e.mouseDragged()

    #Draw particles
    [e.display() for e in collection]

    #Draw springlines
    springlines()


def springlines():
    for e in range(0, n_rowcol ** 2, n_rowcol):
        for f in range(n_rowcol - 1):
            a = collection[e + f]
            b = collection[e + f + 1]
            stroke(255)
            line(a.x(), a.y(), b.x(), b.y())

    for e in range(n_rowcol):
        for f in range(0, n_rowcol ** 2 - n_rowcol, n_rowcol):
            a = collection[e + f]
            b = collection[e + f + n_rowcol]
            stroke(255)
            line(a.x(), a.y(), b.x(), b.y())

Locker.py file

    class locker(object):

        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.over = False
            self.locked = False

        def display(self):

            if dist(self.x, self.y, mouseX, mouseY) < 20:
                self.over = True
                fill(0, 0, 255)
            else:
                self.over = False
                fill(255, 20, 20)
            noStroke()
            ellipse(self.x, self.y, 20, 20)

        def mousePressed(self):
            if mousePressed and self.over:
                self.locked = True
            else:
                self.locked = False

        def mouseDragged(self):
            if self.locked:
                self.x = mouseX
                self.y = mouseY

Particle.py file

    from toxi.physics2d import VerletParticle2D

    class particle(VerletParticle2D):

       over = False

        def display(pos):
            if dist(pos.x(), pos.y(), mouseX, mouseY) < 4:
                pos.over = True
                fill(255, 20, 20)
            else:
                pos.over = False
                fill(255)

            ellipse(pos.x(), pos.y(), 4, 4)

The problem lies in the last snippet above (Particle.py):

To drag a specific particle when mousePressed, I need to access the x and y coordinates of the ellipse (the particle) and say something like:

    def mousePressed(): 
        if pos.over:
            pos.x() = mouseX
            pos.y() = mouseY

But doing so gives me an error: "can't assign to function call" because x() and y() appear as functions.

How can I access the x and y coordinates and make that mouse event work ?

Answers

  • edited February 2018

    ...because x() and y() appear as functions.

    B/c they're indeed getter methods. If you wanna set their values, use the set() method. *-:)

  • edited February 2018
    • There are some further issues in your class particle though. :-S
    • 1st 1 is you're using the lowerCamelCase naming convention particle instead of the UpperCamelCase 1 Particle. /:)
    • 2nd 1 is that you've created a static variable over instead of an instance variable! #-o
    • If you need to have extra instance variables for your subclasses, beyond those automatically inherited by their parent class, you're gonna need to implement __init__()! #:-S
    • Take a look at my own class Particle from your previous forum thread: O:-)
      https://Forum.Processing.org/two/discussion/26346/class-inheritance-with-toxiclibs-pyhton#Item_18
    • Specifically the commented out: # def __init__(p, *args): super(Particle, p).__init__(*args).
    • Here's how to include over as an instance variable member for class Particle: :ar!

    def __init__(p, *args, **kw):
        super(Particle, p).__init__(*args, **kw)
        p.over = False
    
  • I see... so many things I didn't know. :-B

    Following your instructions and this tutorial I came up with this:

    from toxi.physics2d import VerletParticle2D

    class Particle(VerletParticle2D):
    
        def __init__(p, over, target):
            super(Particle, p).__init__(over, target)
            p.over = False
            p.target = False
    
        def display(p):
            if dist(p.x(), p.y(), mouseX, mouseY) < 4:
                p.over = True
                fill(255, 20, 20)
            else:
                p.over = False
                fill(255)
    
            ellipse(p.x(), p.y(), 4, 4)
    
        def mousePressed(p):
            if p.over:
                p.target = True
            else:
                p.target = False
    
        def mouseDragged(p):
            if p.target:
                p.set(mouseX, mouseY)
    

    But unfortunately it's not working properly, the mouse barely drag anything + the mousePressed function doesn't work (the drag function "kind of" works although I didn't press anything). :-?

  • edited February 2018

    Also I have carefully looked at your code but please keep in mind that I don't have your level of coding + you integrated many functions that were not really necessary to understand the problem (confusing at first). It's sometime better to focus only on what really matters to convey your idea rather than implementing "subsidiary" functions even though I totally understand the satisfaction to write comprehensive and "pretty" code.

  • edited February 2018

    Callbacks like mousePressed(), mouseDragged(), etc., just like the main 1s setup() & draw(), all of them belong to class PApplet. :-B

    Normally, Processing can't find them when they're defined outside the sketch's level scope! :-SS

    Anyways, Processing provides the undocumented PApplet::registerMethod() so we can have callbacks defined in our own classes findable by Processing.

    However, I've got my doubts that would work under Python Mode. :-/

    Therefore, you're better off simply using the regular sketch-scoped callbacks rather than having them inside other classes. :(

  • edited February 2018

    ok, I feel like i'm struggling against some invisible force. But I'm not giving up yet.

    So, following your suggestion I wrote the mouse event within the main .pyde file but once again (please forgive the beginner that I am) it's messy AND it doesn't work: def draw()

       #Draw particles
        for e in collection:
            e.display()
    
            def mousePressed():
                if e.over:
                    e.target = True
                else:
                    e.target = False
    
            def mouseDragged():
                if e.target:
                    e.set(mouseX, mouseY)
    

    The particle.py file looks now like this:

    from toxi.physics2d import VerletParticle2D
    
    class Particle(VerletParticle2D):
    
        def __init__(p, x, y):
            super(Particle, p).__init__(x, y)
            p.over = False
            p.target = False
    
        def display(p):
            if dist(p.x(), p.y(), mouseX, mouseY) < 4:
                p.over = True
                fill(255, 20, 20)
            else:
                p.over = False
                fill(255)
    
            ellipse(p.x(), p.y(), 4, 4)
    
  • I tried many configurations (in the main sketch file, in a distinct function, in the draw(), in a child class...etc) but none worked. I have implemented mouse events before but it doesn't seem to work in this sketch specifically.

  • edited February 2018

    You're creating functions inside a loop there! :-&
    Callbacks need to go to the sketch-global-level scope. [-(

    http://py.Processing.org/reference/mouseDragged.html
    http://py.Processing.org/tutorials/interactivity/

  • I know, I tried but it didn't work. That's why I started "experimenting" weird configurations. lol ~X(

  • edited February 2018

    This won't work either so...

    def setup():
        global over, target
        over = False
        target = False
    
       ...
    
    def draw():
        global over, target
    
       ...
    
    for e in collection:
        e.display()
        if dist(e.x(), e.y(), mouseX, mouseY) < 4:
            over = True
        else:
            over = False
    
       mousePressed()
       mouseDragged()
       mouseReleased()
    
    
    
    def mousePressed():
        global over, target
        for e in collection:
            if over == True:
                target = True
            else:
                target = False
    
    def mouseDragged():
        global target
        for e in collection:
            if target:
                e.set(mouseX, mouseY)
    
    def mouseReleased():
        global target
        for e in collection:
            e.target = False
    
  • edited February 2018

    This is the best I could figure out and it's still not working:

    def setup():
        global over, target
        over = False
        target = False
    
       ...
    
    def draw():
        global over, target
    
       ...
    
        for e in collection:
            e.display()
            if dist(e.x(), e.y(), mouseX, mouseY) < 4:
                over = True
            else:
                over = False
    
            if mousePressed and over == True:
                target = True
            else:
                target = False
    
            if target == True:
                    mouseDragged()
    
    
        mouseReleased()
    
    
    def mouseDragged():
        for e in collection:
            if mousePressed and over == True:
                e.set(mouseX, mouseY)
    
    def mouseReleased():
        global target
        for e in collection:
            e.target = False
    

    To get the dragging work I have to call the the mouseDragged() function. But it's still not working. Do you or anyone have any idea what's wrong ?

  • edited February 2018

    I guess you should set apart your project here and learn how to drag objects 1st.
    Found this random online Java Mode sketch below: :bz

    https://OpenProcessing.org/sketch/472169

    BtW, you're mixing up global variables w/ class instance variables. :-\"

  • edited February 2018 Answer ✓

    Working code below ! :

    Particle class

    from toxi.physics2d import VerletParticle2D
    
    class particle(VerletParticle2D):
    
        def __init__(p, x, y):
            super(particle, p).__init__(x, y)
            p.over = False
            p.target = False
    
    
        def display(p):
            if dist(p.x(), p.y(), mouseX, mouseY) < 4:
                fill(255, 20, 20)
            else:
                fill(255)
    
            ellipse(p.x(), p.y(), 4, 4)
    
    
        def clicked(p, x, y):
            if dist(p.x(), p.y(), mouseX, mouseY) < 4:
                p.over = True
            else:
                p.over = False
    
    
        def stopDragging(p):
            p.over = False
    
    
        def drag(p, x, y):
            if p.over:
                p.set(x, y)
    

    What's inside draw()

       ...
    
       #Draw particles
        for e in collection:
            e.display()
            e.drag(mouseX, mouseY)
       ...
    

    Additional functions

    def mousePressed():
        for e in collection:
            e.clicked(mouseX, mouseY)
    
    
    def mouseReleased():
        for e in collection:
            e.stopDragging()
    

    (Note the springs can be dragged but not the particles)

  • @solub -- thank you for sharing your solution!

    Was draggable springs rather than particles what you wanted, in the end?

  • @jeremydouglass Thank YOU and GoToLoop for the amazing help you're providing here. I'm sure it's possible to drag particles as well but I ended up only displaying springs so I'm fine with the code as it is.

Sign In or Register to comment.