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()
Related
I have a QTimeEdit in Python with a predefined range less than one hour, let's say from 08:45:00 to 09:15:00. I read about the problematic of entering a new value which gets out these limits when keying (https://doc.qt.io/qt-6/qdatetimeedit.html#keyboard-tracking) and set the keyboardTracking to False. I set the default value to minimum (so 08:45:00), then I can't change it to values above 08:59:59 because the spin arrows are deactivated for hour field, and I can't change 08 to 09 in hour field with the numpad neither.
Do you experience the same limitations for QTimeEdit especially ?
Btw, the wrapping function isn't adapted to times as it loops on the same field without incrementing the next one...
tl;dr
Some solutions already exist for this issue only related to the wheel and arrow buttons, but they don't consider keyboard editing.
In order to achieve that, it's necessary to override the validate() function (inherited from QAbstractSpinBox) and eventually try to fix up its contents:
class FlexibleTimeEdit(QTimeEdit):
def validate(self, input, pos):
valid, newInput, newPos = super().validate(input, pos)
if valid == QValidator.Invalid:
possible = QTime.fromString(newInput)
if possible.isValid():
fixed = max(self.minimumTime(), min(possible, self.maximumTime()))
newInput = fixed.toString(self.displayFormat())
valid = QValidator.Acceptable
return valid, newInput, newPos
A more complete solution
Since these aspects are actually common within the other related classes (QDateTimeEdit and QDateEdit), I propose a more comprehensive fix that could be used as a mixin with all three types, providing keyboard input and arrow/wheel fixes for these aspects.
The fix works by using an "abstract" class that has to be used with multiple inheritance (with it taking precedence over the Qt class), and provides the following:
optionally override the wheel behavior by setting the cursor position based on the mouse position, allowing to update a specific section without using the keyboard or clicking to change it (i.e.: if the current section is the hour one, and the mouse is on the minutes, then the wheel will update the minutes);
updates the arrow buttons (and related stepBy() calls) depending on the available range, without limiting the range to the section: if the current hour is 23 and the current range allows past the midnight, stepping up will update the value accordingly;
the validation allows values within the full current range, without limiting it to the section range;
Note that this is a bit advanced, so I strongly advise to carefully study the following code in order to understand how it works.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class _DateTimeEditFix(object):
_fullRangeStepEnabled = False
_wheelFollowsMouse = True
_deltaFuncs = {
QDateTimeEdit.YearSection: lambda obj, delta: obj.__class__.addYears(obj, delta),
QDateTimeEdit.MonthSection: lambda obj, delta: obj.__class__.addMonths(obj, delta),
QDateTimeEdit.DaySection: lambda obj, delta: obj.__class__.addDays(obj, delta),
QDateTimeEdit.HourSection: lambda obj, delta: obj.__class__.addSecs(obj, delta * 3600),
QDateTimeEdit.MinuteSection: lambda obj, delta: obj.__class__.addSecs(obj, delta * 60),
QDateTimeEdit.SecondSection: lambda obj, delta: obj.__class__.addSecs(obj, delta),
QDateTimeEdit.MSecSection: lambda obj, delta: obj.__class__.addMSecs(obj, delta),
}
_typeRefs = {
QTimeEdit: ('Time', QTime),
QDateEdit: ('Date', QDate),
QDateTimeEdit: ('DateTime', QDateTime)
}
_sectionTypes = {
QDateTimeEdit.YearSection: 'date',
QDateTimeEdit.MonthSection: 'date',
QDateTimeEdit.DaySection: 'date',
QDateTimeEdit.HourSection: 'time',
QDateTimeEdit.MinuteSection: 'time',
QDateTimeEdit.MSecSection: 'time'
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for cls in QTimeEdit, QDateEdit, QDateTimeEdit:
if isinstance(self, cls):
ref, self._baseType = self._typeRefs[cls]
break
else:
raise TypeError('Only QDateTimeEdit subclasses can be used')
self._getter = getattr(self, ref[0].lower() + ref[1:])
self._setter = getattr(self, 'set' + ref)
self._minGetter = getattr(self, 'minimum' + ref)
self._maxGetter = getattr(self, 'maximum' + ref)
#pyqtProperty(bool)
def fullRangeStepEnabled(self):
'''
Enable the arrows if the current value is still within the *full*
range of the widget, even if the current section is at the minimum
or maximum of its value.
If the value is False (the default), using a maximum time of 20:30,
having the current time at 20:29 and the current section at
HourSection, the up arrow will be disabled. If the value is set to
True, the arrow is enabled, and going up (using arrow keys or mouse
wheel) will set the new time to 20:30.
'''
return self._fullRangeStepEnabled
#fullRangeStepEnabled.setter
def fullRangeStepEnabled(self, enabled):
if self._fullRangeStepEnabled != enabled:
self._fullRangeStepEnabled = enabled
self.update()
def setFullRangeStepEnabled(self, enabled):
self.fullRangeStepEnabled = enabled
#pyqtProperty(bool)
def wheelFollowsMouse(self):
'''
By default, QDateTimeEdit "scrolls" with the mouse wheel updating
the section in which the cursor currently is, even if the mouse
pointer hovers another section.
Setting this property to True always tries to update the section
that is *closer* to the mouse cursor.
'''
return self._wheelFollowsMouse
#wheelFollowsMouse.setter
def wheelFollowsMouse(self, follow):
self._wheelFollowsMouse = follow
def wheelEvent(self, event):
if self._wheelFollowsMouse:
edit = self.lineEdit()
edit.setCursorPosition(edit.cursorPositionAt(event.pos() - edit.pos()))
super().wheelEvent(event)
def stepBy(self, steps):
section = self.currentSection()
if section in self._deltaFuncs:
new = self._deltaFuncs[section](self._getter(), steps)
self._setter(
max(self._minGetter(), min(new, self._maxGetter()))
)
self.setSelectedSection(section)
else:
super().stepBy(steps)
def _stepPossible(self, value, target, section):
if self._fullRangeStepEnabled:
return value < target
if value > target:
return False
if section in self._deltaFuncs:
return self._deltaFuncs[section](value, 1) < target
return False
def stepEnabled(self):
enabled = super().stepEnabled()
current = self._getter()
section = self.currentSection()
if (
not enabled & self.StepUpEnabled
and self._stepPossible(current, self._maxGetter(), section)
):
enabled |= self.StepUpEnabled
if (
not enabled & self.StepDownEnabled
and self._stepPossible(self._minGetter(), current, section)
):
enabled |= self.StepDownEnabled
return enabled
def validate(self, input, pos):
valid, newInput, newPos = super().validate(input, pos)
if valid == QValidator.Invalid:
# note: Qt6 deprecated some fromString() forms and QLocale functions
# should be preferred instead; see the documentation
possible = self._baseType.fromString(newInput, self.displayFormat())
if possible.isValid():
m = self._minGetter()
M = self._maxGetter()
fixedUp = max(m, min(possible, M))
if (
self._fullRangeStepEnabled
or m <= fixedUp <= M
):
newInput = fixedUp.toString(self.displayFormat())
valid = QValidator.Acceptable
return valid, newInput, newPos
class BetterDateTimeSpin(_DateTimeEditFix, QDateTimeEdit): pass
class BetterTimeSpin(_DateTimeEditFix, QTimeEdit): pass
class BetterDateSpin(_DateTimeEditFix, QDateEdit): pass
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
test = QWidget()
layout = QVBoxLayout(test)
fullRangeCheck = QCheckBox('Allow full range')
layout.addWidget(fullRangeCheck)
timeSpin = BetterTimeSpin(
displayFormat='hh:mm:ss',
minimumTime=QTime(8, 45, 0),
maximumTime=QTime(9, 15, 50),
)
layout.addWidget(timeSpin)
dateSpin = BetterDateTimeSpin(
displayFormat='dd/MM/yy hh:mm',
minimumDateTime=QDateTime(2022, 9, 15, 19, 25),
maximumDateTime=QDateTime(2023, 2, 12, 4, 58),
)
layout.addWidget(dateSpin)
fullRangeCheck.toggled.connect(lambda full: [
timeSpin.setFullRangeStepEnabled(full),
dateSpin.setFullRangeStepEnabled(full),
])
test.show()
sys.exit(app.exec())
Note: as with the standard QTimeEdit control, it's still not possible to use the time edit with a range having a minimum time greater than the maximum (ie: from 20:00 to 08:00).
I have been given a class which implements a Priority Queue , using a function to evaluate the priority.
class PriorityQueueWithFunction(PriorityQueue):
"""
Implements a priority queue with the same push/pop signature of the
Queue and the Stack classes. This is designed for drop-in replacement for
those two classes. The caller has to provide a priority function, which
extracts each item's priority.
"""
def __init__(self, priorityFunction):
# type: (object) -> object
"priorityFunction (item) -> priority"
self.priorityFunction = priorityFunction # store the priority function
PriorityQueue.__init__(self) # super-class initializer
def push(self, item):
"Adds an item to the queue with priority from the priority function"
PriorityQueue.push(self, item, self.priorityFunction(item))
I have been also , given , the priority function that I am going to initialize the class above with.
def manhattanHeuristic(position, problem, info={}):
"The Manhattan distance heuristic for a PositionSearchProblem"
xy1 = position
xy2 = problem.goal
return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1])
The above code is given to us and we cannot change it. I must create that PriorityQueueWithFunction Class and push an element to it. The push function of my class takes on argument , the item. But my PriorityFunction takes 2.
What kind of arguments should i use to push the right elemnt into my class and also make my priorityfunction work properly ?
That's what i tried and i am getting compiling errors , manhattanHeuristic...takes 2 arguments , 1 given
#Creating a queingFn
queuingFn = PriorityQueueWithFunction(heuristic)
Frontier = queuingFn
#Creating the item that needs to be pushed
StartState = problem.getStartState()
StartNode = (StartState,'',0,(-1,-1))
#Here is my problem
item = StartState , problem
Frontier.push(item)
Should I change my item's form ? Any ideas ?
You should make a new method that wraps call to manhattanHeuristic:
# for item as dict: item = {'position': POS, 'problem': PROBLEM}
def oneArgHeuristic(item):
position = item.position
problem = item.problem
return manhattanHeuristic(position, problem)
# for item as tuple: item = (POS, PROBLEM)
def oneArgHeuristic(item):
position, problem = item
return manhattanHeuristic(position, problem)
and pass it to PriorityQueueWithFunction instead of the original one
Hello I need some help I'm a novice when it comes to advanced python coding, I've been trying to solve this problem but I'm unable to find an answer. I'm trying to find a way so that when someone clicks on an object inside Maya for example a basic sphere it will print the object class and parent class into each of the textFields; then should a user select something else like a cube it will instead show that object class&parent class. I know I need a function which will activate when an object is selected then place the value into the textFields, but I can't figure out how to do it. If anyone has a solution it would be greatly appreciated :)
import maya.cmds as cmds
from functools import partial
class drawUI(): #Function that will draw the entire window
#check to see if window exists
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
ObjParClass = cmds.textField(editable = False)
cmds.showWindow("UI_MainWindow") #shows window
The main stumbling block is that you're using the class setup in a very unusual way. The usual pattern is to have an __init__ method that creates a new copy ("instance") of the class; in you case you're executing the code when the class is defined rather than when you invoke it. The usual class shell looks like:
class Something (object) # derive from object in python 2.7, ie, in maya
def __init__(self):
self.variable = 1 # set persistent variables for the object
do_something() # other setup runs now, when a new Something is created
def method(self):
print self.variable # you can use any variables defined in 'self'
The usual way to create a reactive UI in maya is to use a scriptJob. These will fire a script callback when certain events happen inside of Maya. The easiest event for your purpose is SelectionChanged which fires when the selection changes.
The other thing you want to do is figure out how to pack this into a class. Sometimes it's a good idea to create your class object and then give it a show() or layout() method that creates the actual UI -- a lot depends on how much you need to set the object up before showing the window.
In this case I put the behavior into the __init__ so it will create the UI when you make the object. The __init__ also creates the scriptJob and parents it to the window (so it will disappear when the window does).
class drawUI(object):
def __init__(self):
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
self.window = cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
self.ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
self.ObjParClass = cmds.textField(editable = False)
cmds.showWindow(self.window)
cmds.scriptJob (e = ("SelectionChanged", self.update_UI), p= self.window)
def update_UI(self, *_, **__):
sel = cmds.ls(selection=True) or []
c = ["-none-", "-none-"]
if sel:
c = cmds.nodeType(sel[0], i = True)
cmds.textField(self.ObjClass, e=True, text = c[-1])
cmds.textField(self.ObjParClass, e=True, text = ", ".join(c[:-1]))
test = drawUI()
By using self.update_UI without arguments in the scriptJob, we make sure that the function that fires knows which object it's updating. Using the self variables for the textfields lets us update the UI that goes with the window without worrying about remembering names in some other scope.
More details here
I've realized that there were similar questions located
here:
textfield query and prefix replacing
and
here:
Python - Change the textField after browsing - MAYA
However, these do not address the issue if you have two definitions and need the text in the textField to be queried (actually CHANGE the text in the textField).
I know from experience that doing what I have below in MelScript actually works, but for the sake of Python, and learning how to do it in Python, it seems to not work. Am I missing something? Do I need a lambda to get the name of the object selected and query the textField?
I have an example (a snip-bit of what needs to be fixed):
from pymel.core import *
def mainWindow():
window('myWin')
columnLayout(adj=1)
button('retopoplz', ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],
l='START RETOPOLOGY', c='Retopo(TextToMakeLive)')
TextToMakeLive = textField(ann='Mesh Selected', bgc=[.2,0,0],
edit=0, tx='NONE')
setParent('..')
showWindow('myWin')
def Retopo(TextToMakeLive):
#This tool selects the object to retopologize
MakeLiveField = textField(TextToMakeLive, q=1, tx=1)
MakeSelectionLive = (ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if MakeSelectionLive == 1:
TextToMakeLive = textField(TextToMakeLive, ed=1,
tx=MakeSelectionLive,
bgc=[0,.2,0])
shape = ls(s=MakeSelectionLive[0])
setAttr((shape + '.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer', num=1, nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
mainWindow()
GUI objects can always be edited -- including changing their commands -- as long as you store their names. So your mainWindow() could return the name(s) of gui controls you wanted to edit again and a second function could use those names to change the looks or behaviors of the created objects.
However, this is all much easier if you use a python class to 'remember' the names of the objects and any other state information: it's easy for the class to 'see' all the relevant info and state. Here's your original converted to classes:
from pymel.core import *
class RetopoWindow(object):
def __init__(self):
self.window = window('myWin')
columnLayout(adj=1)
button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY', c = self.do_retopo)
self.TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
def show(self):
showWindow(self.window)
def do_retopo(self, *_):
#This tool selects the object to retopologize
MakeLiveField= textField(self.TextToMakeLive,q=1,tx=1)
MakeSelectionLive=(ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if len( MakeSelectionLive) == 1:
TextToMakeLive=textField(self.TextToMakeLive,ed=1,tx=MakeSelectionLive,bgc=[0,.2,0])
shape=ls(s=MakeSelectionLive[0])
setAttr((shape+'.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer',num=1,nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
RetopoWindow().show()
As for the callbacks: useful reference here
You need to assign the command flag AFTER you have created your textField to be queried.
So you would do:
my_button = button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY')
TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
button(my_button, e=True, c=windows.Callback(Retopo, TextToMakeLive))
You were along the right thought chain when you suggested lambda. Pymel's Callback can be more advantageous over lambda here. Check out the docs: http://download.autodesk.com/global/docs/maya2014/zh_cn/PyMel/generated/classes/pymel.core.windows/pymel.core.windows.Callback.html
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).