Animating a custom QGraphicsItem with QPropretyAnimation - python

I want to make a car follow a path, so I tried animating a custom QGraphicsItem (describing a 'car') using QPropreties, starting by an example given on PySide documentation :
self.anim = QtCore.QPropertyAnimation(self.car, "geometry")
self.anim.setDuration(4000);
self.anim.setStartValue(QtCore.QRect(0, 0, 100, 30));
self.anim.setEndValue(QtCore.QRect(250, 250, 100, 30));
self.anim.start();
self.car here is an instance of Car, a class that inherits from QGraphicsItem and QObject ( EDIT Changed this, see EDIT 2):
class Car(QtGui.QGraphicsItem, QtCore.QObject):
def __init__(self, map = None, ....):
super(Car, self).__init__()
self.map = map
....
I always get the same error code when executing this :
QPropertyAnimation::updateState (geometry): Changing state of an animation without target
This is weird as the target is really set ! I tried putting setTargetObject ( see the documentation on PySide ) but it didn't change anything.
Any idea about where that could come from ?
EDIT : Something that could help -> I tried putting those three lines and the result shows that the object is not taken into account :
print self.car
self.anim.setTargetObject(self.car)
print self.anim.targetObject()
Result
<engine.Car(this = 0x43f9200 , parent = 0x0 , pos = QPointF(0, 0) , z = 0 , flags = ( ItemIsMovable | ItemIsSelectable ) ) at 0x1de0368>
None
EDIT 2 : Changing the class inheritance to QGraphicsObject seems to have solved the 'no target' problem ! But I have a different error now (using a custom defined property 'pos'):
QPropertyAnimation::updateState (pos, Car, ): starting an animation without end value
I defined my property that way :
def setPos(self, pos):
self.x = pos[0]
self.y = pos[1]
self.update()
def readPos(self):
return (self.x, self.y)
pp = QtCore.Property('tuple', readPos, setPos)

If you just need to animate position, you can animate GraphicsItem.pos rather than defining your own position functions (unless you want the car to have a position within the QGraphicsItem itself.
As for 'starting an animation without end value', make sure you:
Call setEndValue on the animation.
Pass in the correct type to setEndValue (e.g. your current implementation would require a tuple, using GraphicsItem.pos would require a QPoint).

The solution was to first change the inheritance from QGraphicsItem and QObject to QGraphicsObject. Then, the second error I did is I didn't define my property (x and y) correctly.
I ended up re-implementing my class to use QGraphicsObject's "pos" property and give it the correct start and end value (tuples).

Related

is it possible to call a method from another subclass in Python?

I'm trying to build a simulation for robotic behavior. I have a Robot parent abstract class, and a functioning NormalRobot subclass. I want to build a FaultyRobot subclass, possibly with something like partial "parallel" inheritance?
The parent class Robot has an abstact move() method, since I want all my Robot subclasses to be able to be called on with the same name move()
NormalRobot has a functioning method move() that simulates one "turn", of moving the specified distance in the specified direction. (multiple "turns" are simulated by calling move() multiple times)
FaultyRobot should also have a move() method, which is almost identical to the NormalRobot's move() except that at the start of the "turn" it randomly has a possibility of not moving and changing direction. Obviously I can do this by writing the randomness code (which i have in a helper method turn_is_faulty()) and copy-pasting the content of NormalRobot's move(). But obviously this is bad etiquete to have repeating code. Python inheritance makes it very easy to inhereit the move() method from the parent class Robot, but I need the move() from the parallel subclass NormalRobot.
I'm imagining that the code would look something like
def Class FaultyRobot:
def move(self):
if self.turn_is_faulty():
self.set_robot_direction(360 * random.random())
else:
#call the NormalRobot move()
Does Python allow some sort of partial "parallel" inheritance?
I would say "Faulty Robot" is just a "Normal Robot" with few faults.
import numpy as np
class NormalRobot():
def __init__(self, x):
self.x = x
def move(self, d):
self.x += d
def __repr__(self):
return f"Position: {self.x}"
class FaultyRobot(NormalRobot):
def __init__(self, x, is_faulty=True):
self.x = x
self.is_faulty = is_faulty
def turn_is_faulty(self):
return self.is_faulty
def move(self, d):
if self.turn_is_faulty():
self.x += np.random.random()
else:
super().move(d)
test:
n = NormalRobot(0)
f = FaultyRobot(0)
nf = FaultyRobot(0, False)
n.move(10)
nf.move(10)
f.move(10)
print (n, nf, f)
Output:
Position: 10 Position: 10 Position: 0.07103605819788694
You can put shared functionality into a mixin class.
import random
class AbstractRobot:
"""My 1 dimensional robot"""
def __init__(self, start_at):
self.pos = start_at
class RobotMoveMixin:
def move(self, delta):
self.pos += delta
print('moved to', self.pos)
class GoodRobot(RobotMoveMixin, AbstractRobot):
pass
class BadRobot(RobotMoveMixin, AbstractRobot):
def move(self, delta):
print("bad robot")
if random.choice((True, False)):
super().move(delta)
robot = BadRobot(0)
for _ in range(10):
robot.move(3)
Now GoodRobot and its inheritors get the good movement while BadRobot and its interitors get the messed up one.
thank you all for your help. I ended up finding another solution that I think may be interesting, so I am posting it. My solution doesn't use inheritance in any formal sense, so I suspect some of you may have though of something like it but not posted it, since it seems far off from the phrasing of my question. This is my own problem, since I saw this an a problem with inheritance, and so I assumed the solution would be found with better inheritance structure. Thankfully there is a simple workaround.
def Class FaultyRobot:
def move(self):
if self.turn_is_faulty():
self.set_robot_direction(360 * random.random())
else:
StandardRobot.move(self)
Python does indeed allow to call the move() of one subclass while defining the move() of a new subclass!

Python class parameters, where to define

class Camera(object):
def __init__(self, win, x=0.0, y=0.0, rot=0.0, zoom=1.0):
self.win = win
self.x = x
self.y = y
self.rot = rot
self.zoom = zoom
cam = Camera(Window,1,1,1,1)
vs
class Camera(object):
def __init__(self, win, x, y, rot, zoom):
self.win = win
self.x = x
self.y = y
self.rot = rot
self.zoom = zoom
cam = Camera(Window,1,1,1,1)
So does the first block of code just make the class static where it can only be made and not adjusted with parameters? If so, what's the usefulness of this?
The first block of code just sets default values for those variables. When the class is initialised if they are passed to the class, then they will be set. Otherwise the class variables will contain the default values.
In the second block of code, by not setting a default value you are making those parameters required for the class.
See this question for a better explanation: Why do we use __init__ in python classes?
The difference between the first one and second one is: the first one has default values while the other one dosen't. For example with the first one
cam = Camera(Window) wouldn't give you an error (x=0.0, y=0.0, rot=0.0 and zoom=1.0 have a default assigned value).
But the second code block would give you an error if you were to create new instance of the class like this cam = Camera(Windows). Wich is ultimately the difference between your first code block and your second one.
Cheers!

python class get member addressee?

everyone
I used c++ program for a long time.
This days, I make a GUI program using python.
But, I don't understand about class of python.
The problem is like this.
At this Gui program, user can add many view. then click the button to change the view.
In my code. I put the element to a class like this
class LoadingView():
def __init__(self):
self.idx=0
self.view = pg.GraphicsLayoutWidget()
self.w1 = self.view.addPlot()
self.view.nextRow()
self.w2 = self.view.addPlot()
self.view.nextRow()
self.w3 = self.view.addPlot()
self.view.nextRow()
self.w4 = self.view.addPlot()
and I creat class instant and put it to array
self.TreeIdx = self.TreeIdx + 1
self.AddNodeToTree(SetDlg.EditBoxName.text())
Loding_view = LoadingView()
self.LView.append(LoadingView)
self.gridLayout_2.addWidget(Loding_view.view, 0, 0, 1, 1)
finally, when I push button, I want to implement to change the view
self.gridLayout_2.addWidget(self.LView[int(Text.split('.')[0]) - 1].view, 0, 0, 1, 1)
but, it returned AttributeError: class LoadingView has no attribute 'view'
I don't know how to implement it . need you help...
thank you!
You need to include 'view' in your __ init __ function. Right now you only have self.
Try this:
class LoadingView:
def __init__(self,
idx=None,
view=None,
w1=None,
w2=None,
w3=None,
w4=None,
Nrows=None,
):
self.idx=idx if idx is not None else 0
self.view=view if view is not None else pg.GraphicsLayoutWidget()
self.w1=w1
self.w2=w2
self.w3=w3
self.w4=w4
self.Nrows=Nrows
def datafill(self):
for i in self.Nrows:
self.__dict__['w'+str(i+1)]=self.view.addPlot()
Loading_view=LoadingView()
Loading_view.datafill()
You can make w a list instead of explicitly defined terms but the way I've shown will loop over the explicitly defined terms using a dictionary. This method also sets the default value of view and idx should you not provide those when you define an instance of the class.

Kivy: drawing not updating when changing line points

I guess my question is pretty much summed up in the title.
I am using an update call (similar to the one in the Pong tutorial). Within this call I update the points of a line. Though I can check that the points are indeed being updated, the actual line drawing is not.
I'll put some of the code up here:
class GraphInterface(Widget):
node = ObjectProperty(None)
def update(self, dt):
for widget in self.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
widget.check_connection()
class GraphEdge(Widget):
r = NumericProperty(1.0)
#determines if edge has an attached node
connected_point_0 = Property(False)
connected_point_1 = Property(False)
#provides details of the attached node
connected_node_0 = Widget()
connected_node_1 = Widget()
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
if (self.connected_point_1 is False):
print "collision"
self.connected_point_1 = True
self.connected_node_1 = node
del self.line.points[-2:]
self.line.points[-2:]+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
The idea is to check for collisions initially, and once a collision has been made, I attach the line to this node widget. The points are then update as I move the node around. However right now although the points are updated, the drawing of the line is not.
If you need anymore code or information please ask.
del self.line.points[-2:]
self.line.points[-2:]+=node.center
These lines bypass operations that set the property, so the VertexInstruction doesn't know anything has changed and doesn't redraw itself.
They're a bit strange anyway, it would be simpler to just write:
self.line.points = self.line.points[:-2] + node.center
This would also update the instruction graphics, because you set the property directly rather than only modifying the existing list.

Change property parameter from within class constructor [Python / Traits]

I'm new to python - sorry if my terminology is wrong. I have a class which inherits the Enthought Traits attributes. Here is a simplified version:
from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item
class GUIThing(HasTraits):
my_slider = Range(0.0, 0.6, 0.1)
my_slider._high = 0.7 # works; not what I need 'coz not instance-dependent
view = View( Item('my_slider') )
def __init__(self, arg1):
# Call the parent's __init__
HasTraits.__init__(self)
self.my_slider._high = arg1 # what I need; doesn't work
# -- Main program -----
top_range = 0.9
my_gui = GUIThing(top_range)
my_gui.configure_traits()
This simply creates a window with a slider in it, originally going from 0.0 to 0.6 with initial value 0.1. When creating an instance of GUIThing I want to vary the maximum value for the slider depending on the current top_range value. However the line
self.my_slider._high = arg1
results in
AttributeError: 'float' object has no attribute '_high'
When within __init__(), self.my_slider returns not the slider object, but the current value of the slider.
What am I doing wrong? Thanks!
Edit:
The following also doesn't work:
class GUIThing(HasTraits):
def __init__(self, arg1):
# Call the parent's __init__
HasTraits.__init__(self)
self.my_slider = Range(0.0, arg1, 0.0)
view = View( Item('my_slider') )
That would be the direct way to do what I'm trying to do, but it results in a GUI where instead of a slider, there is a text box that reads "enthought.traits.trait_types.Range object at 0xa61946c". So the problem is that when my_slider is created within __init__() then "my_slider" comes to mean the object itself (which doesn't display properly via View); but if my_slider is created outside of __init__() then "my_slider" comes to mean the current value (a float; which prevents access to the object properties).
Not sure if this is peculiar to Traits or I just don't know how to initialise objects properly.
Finally found the answer in a recent mailing list message.
The code below works. It seems the devil is in the details of how Range() is called: Range(my_slider_low, my_slider_hi, 0.1) does not work.
from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item
class GUIThing(HasTraits):
my_slider_low = 0.0
my_slider_hi = 1.0
my_slider = Range(low='my_slider_low', high='my_slider_hi', value=0.1)
def __init__(self, arg1):
self.my_slider_hi = arg1
view = View( Item('my_slider') )
top_range = 0.2
my_gui = GUIThing(top_range)
my_gui.configure_traits()
My Gut Feeling is that you don't need to modify that class but rather extend the Range class and add the extra logic that you need to handle your specific case.
You need to use the add_trait method that will enable you to create dynamically new Range traits with the values you need.
This is taken from the Advanced page of the traits user manual
from traits.api import HasTraits, Range
class GUISlider (HasTraits):
def __init__(self, eval=None, label='Value',
trait=None, min=0.0, max=1.0,
initial=None, **traits):
HasTraits.__init__(self, **traits)
if trait is None:
if min > max:
min, max = max, min
if initial is None:
initial = min
elif not (min <= initial <= max):
initial = [min, max][
abs(initial - min) >
abs(initial - max)]
trait = Range(min, max, value = initial)
self.add_trait(label, trait)
There is actually a problem in Pteridium's answer. It does work but breaks two rules/recommendations.
Firstly, you have overwritten the constructor with your own init. If you do that (and if avoidable you shouldn't according to the coding recommendations for Traits), you should call the parent constructor with something like
super(GUISlider, self).init(self, **kwargs)
Secondly, the recommended initialization for HasTraits children is done during the instantiation in the constructor that takes keyword arguments. In your case the code could be like
from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item
class GUIThing(HasTraits):
my_slider_low = 0.0
my_slider_hi = 1.0
my_slider = Range(low='my_slider_low', high='my_slider_hi', value=0.1)
view = View( Item('my_slider') )
my_gui = GUIThing(my_slider_hi=0.4)
my_gui.configure_traits()
There you go, does what you want (I think), less code and follows the Traits. If someone can explain why we shouldn't use constructors I would like to know. Robert Kern can probably tell us.
I cropped down a nice example of a dynamic range from Jonathan March. This gives all the behavior that the OP wants AFAICT:
# Imports:
from traits.api import HasPrivateTraits, Float, Range, Int
from traitsui.api import View, Group, Item, Label, RangeEditor
class DynamicRangeEditor ( HasPrivateTraits ):
""" Defines an editor for dynamic ranges (i.e. ranges whose bounds can be
changed at run time).
"""
# The value with the dynamic range:
value = Float
# This determines the low end of the range:
low = Float(0.0)
# This determines the high end of the range:
high = Float(50)
# Traits view definitions:
traits_view = View(
# Dynamic simple slider demo:
Group(
Item( 'value',
editor = RangeEditor( low_name = 'low',
high_name = 'high',
format = '%.1f',
label_width = 28,
mode = 'auto' )
),
'_',
Item( 'low' ),
Item( 'high' ),
'_',
),
title = 'Dynamic Range Editor Demonstration',
buttons = [ 'OK' ],
resizable = True
)
# Create the demo:
demo = DynamicRangeEditor()
# Run the demo (if invoked from the command line):
if __name__ == '__main__':
demo.configure_traits()

Categories

Resources