python change child object and propagate to parent - python

I have an object "surface" which is stored in a "cell" as a list. You can think of a cube which is made of surfaces. Despite I can change surface properties using the "cell" object, there are times when is more convenient for me to change it directly on the "surface". Is it possible to change a property in "surface" object and then propagate it automatically to the same surface within "cell"? An example:
class Surface(object):
def __init__ (self, color):
self.color = color
class Cell(object):
def __init__(self, surfaces)
self.surface = surfaces
surfaces = []
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
cell = Cell(surfaces)
surface[0].color = 'red'
print(cell.surface[0].color)
'red'
When changing the surface color I would like it to be propagated to the same child within the "cell" object. Is this possible?
Appreciate the help!

Related

VTK+PyQt5: Connecting slider values to translation variables

I used the code below and tried to change slider value with first variable of transform.trasnlate(x,y,z)
but I faced the error below:
TypeError: Translate argument %Id: %V
details: I want to change the position of STL file as the the slider moves up or down.
I think my problem is that I'm using a method instead of an integer number, but I don't know how to fix. it
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.widget = QtWidgets.QWidget()
self.gridLayout = QtWidgets.QGridLayout()
#First STL file
reader = vtk.vtkSTLReader()
reader.SetFileName(filename)
transform = vtk.vtkTransform()
transform.Translate(self.size, 0, 0)
transformFilter = vtk.vtkTransformPolyDataFilter()
transformFilter.SetTransform(transform)
transformFilter.SetInputConnection(reader.GetOutputPort())
transformFilter.Update()
mapper = vtk.vtkPolyDataMapper()
if vtk.VTK_MAJOR_VERSION <= 5:
mapper.SetInput(transformFilter.GetOutput())
else:
mapper.SetInputConnection(transformFilter.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
self.vtkwidget = QVTKRenderWindowInteractor(self.widget)
self.gridLayout.addWidget(self.vtkwidget, 1, 0, 1, 1)
#Slider
self.title = "Rotaion"
self.top = 200
self.left = 500
self.width = 400
self.height = 300
self.setWindowIcon(QtGui.QIcon("icon.png"))
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.slider = QSlider()
self.slider.setOrientation(Qt.Vertical)
self.slider.setTickPosition(QSlider.TicksBelow)
self.slider.setTickInterval(10)
self.slider.setMinimum(-100)
self.slider.setMaximum(100)
self.slider.valueChanged.connect(self.handler)
self.gridLayout.addWidget(self.slider, 1, 1, 1, 1)
# Create a rendering window and renderer
self.ren = vtk.vtkRenderer()
self.vtkwidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkwidget.GetRenderWindow().GetInteractor()
self.ren.AddActor(actor)
self.iren.Initialize()
# Enable user interface interactor
self.widget.setLayout(self.gridLayout)
self.setCentralWidget(self.widget)
self.ren.ResetCamera()
def handler(self):
global size
self.size = self.slider.value()
print(self.size)
#self.label.setText(str(self.size))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
size() is one of the basic default properties of all QWidget classes, and since all Qt properties are accessible as callables what you're getting with self.size (without parentheses) is a reference to a method, and that's clearly not an acceptable type for the transform. In any case, the self.size() will return a QSize value, which is also not an acceptable type.
So, two considerations must be done:
No existing attribute of the inherited class(es) should ever be overwritten for uses different from their scope; since you're using a QMainWindow, you'll need to ensure that you're not overwriting any of the properties or functions of QMainWindow, QWidget and QObject; I strongly recommend you to always check for them (you can see the full member list by clicking the "List of all members, including inherited members" link at the beginning of the documentation of each class), but the rule of thumb is: if it's a very generic attribute name (like "size", "width", "font") it probably exists already and should not be overwritten.
If size wasn't an already existing attribute, you'd have faced an AttributeError, because you never declared it before that point.
Finally, if you want to change the transform, you certainly cannot just do that by overwriting the self.size value; Python doesn't work like that, at least with simple instance attributes: you'll have only changed the value of self.size, but the transform object can know anything about that.
You need to connect the valueChanged signal to a slot that changes the transform (or sets a new one). I don't use Vtk, so the following is just some pseudo code:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# ...
self.transformSize = 0
self.transform = vtk.vtkTransform()
self.transform.Translate(self.transformSize, 0, 0)
self.transformFilter = vtk.vtkTransformPolyDataFilter()
# ...
def handler(self, value):
self.transformSize = value
self.transform.Translate(value, 0, 0)
self.transformFilter.Update()
Some considerations:
As with size(), you should also not overwrite other existing attributes like width() or height().
Almost every signal that is emitted when some property has changed, provides the new value as argument, and that's also true for QAbstractSlider's valueChanged, so you don't need to use self.slider.value() inside handler() since that value is already the argument.
Grid layouts (as any other index-based object) are 0-index based, if you don't add anything to the first row or column of the grid (or you don't set a minimum height/width for them) there's no use adding widgets to the second row/column; also if the added widget is only using one "grid cell", the rowSpan and columnSpan arguments (the last 1, 1 in addWidget) are superfluous since they are the default values.
I don't know what that global in the handler() is doing there, since it's not used, but keep in mind that you should always avoid globals: the general rule is that "if you're using them, you're doing something wrong", or, better, "you should not use globals unless you know when you need them, and if you really know when you need them, you also know that you should not use them".

Python/Psychopy: Instantiating multiple circle objects from a class

I've made a class that contains a circle (a Psychopy Circle object). I want to know how I can instantiate 2 circle objects using this class, each for example, with a different fill color.
class Circle(object):
def __init__(self):
self.circle = visual.Circle(win, units = 'deg', pos=(1,1),
radius=1, lineColor="black", fillColor="red")
self.components = [self.circle]
def draw(self):
[component.draw() for component in self.components]
circle=Circle() #red colour
circle2=Circle() #blue colour if possible
Is there a way for me to instantiate circle2 whilst accessing some of the visual.circle parameters e.g. to change it's position or fill color? This is my first use of classes. Currently, If I draw 'circle' and 'cirle2' to the screen, one simply overlays the other, as one is merely a copy of the other.
Cheers,
Jon
Based on your clarification in the comment, I assume you want something like this:
class Circle(psychopy.visual.circle.Circle):
def __init__(self, win, lineColor='black', fillColor='red'):
super(Circle, self).__init__(
win=win, lineColor=lineColor, fillColor=fillColor, units='deg',
pos=(1,1), radius=1)
Circle would then default to units='deg', pos=(1,1), and radius=1. You could, however, specify different lineColors and fillColors for each instance. Since Circle inherits from the PsychoPy visual.Circle class, it has all its features. The call to super() actually initializes the parent class. See e.g. this post for more information on the super() function.
Let's put this to work.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from psychopy import core, visual, monitors
import psychopy.visual.circle
class Circle(psychopy.visual.circle.Circle):
def __init__(self, win, lineColor='black', fillColor='red'):
super(Circle, self).__init__(
win=win, lineColor=lineColor, fillColor=fillColor, units='deg',
pos=(1,1), radius=1)
def main():
# Create a temporary monitor configuration.
monitor = monitors.Monitor('test_display')
monitor.setWidth(60)
monitor.setDistance(80)
monitor.setSizePix((1440, 900))
win = visual.Window(monitor=monitor)
colors = ('red', 'green', 'blue')
circles = [Circle(win=win, fillColor=color) for color in colors]
for circle in circles:
circle.draw()
win.flip()
core.wait(1)
core.quit()
if __name__ == '__main__':
main()
This code will create three Circles with different colors, and display them one after the other. I had to create a temporary monitor configuration or else PsychoPy would refuse to open a Window on my current computer.
You can change the constructor of the class (the __init__ method) adding an atribute color and in the fillColor change the value to the variable color. With the position you can do the same.

Having issues with binding names to shapes

I have created an array with Map names - 4 names and i used these names as parameters for naming rectangle shapes on a canvas. Now i would like to map these array names with the rectangle names so that when a user clicks on the rectangle, it will produce the name i have assigned.
I am trying to bind the mapNames array names to the rectangles but having trouble doing it, having issues with StringVar() and changing the values to match the different rectangles, any help would be appreciated?
class Viewer(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
........lines of code
self.canvas.bind("<Button-1>", self.bind)
self.var = StringVar()
def bind(self, event):
self.x, self.y = event.x, event.y
for names in self.mapNames:
self.var = self.var.set(self.names)
return self.var
##Trying to set the StringVar as the names in array Mapnames so
that when user clicks, the Map names that is the same as the
names that were assigned to the rectangles will print.

Qgraphicsview items not being placed where they should be

I recently created a program that will create QgraphicsEllipseItems whenever the mouse is clicked. That part works! However, it's not in the exact place where my cursor is. It seems to be slightly higher than where my mouse cursor is. I do have a QGraphicsRectItem created so maybe the two items are clashing with each other and moving off of one another? How can I get these circles to be placed on top of the rectangle item? Here's the code
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self):
self.cursor = QtGui.QCursor()
self.x = self.cursor.pos().x()
self.y = self.cursor.pos().y()
self.circleItem = QtGui.QGraphicsEllipseItem(self.x,self.y,10,10)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
self.setScene(self.scene)
class Window(QtGui.QMainWindow):
def __init__(self):
#This initializes the main window or form
super(Window,self).__init__()
self.setGeometry(50,50,1000,1000)
self.setWindowTitle("Pre-Alignment system")
self.view = MyView()
self.setCentralWidget(self.view)
def mousePressEvent(self,QMouseEvent):
self.view.paintMarkers()
Much thanks!
There are two issues with the coordinates you are using to place the QGraphics...Items. The first is that the coordinates from QCursor are global screen coordinates, so you need to use self.mapFromGlobal() to convert them to coordinates relative to the QGraphicsView.
Secondly, you actually want the coordinates relative to the current QGraphicsScene, as this is where you are drawing the item. This is because the scene can be offset from the view (for example panning around a scene that is bigger than a view). To do this, you use self.mapToScene() on the coordinates relative to the QGraphicsView.
I would point out that typically you would draw something on the QGraphicsScene in response to some sort of mouse event in the QGraphicsView, which requires reimplementing things like QGraphicsView.mouseMoveEvent or QGraphicsView.mousePressEvent. These event handlers are passed a QEvent which contains the mouse coordinates relative to the view, and so you don't need to do the global coordinates transformation I mentioned in the first paragraph in these cases.
Update
I've just seen your other question, and now understand some of the issue a bit better. You shouldn't be overriding the mouse event in the main window. Instead override it in the view. For example:
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self, event):
# event position is in coordinates relative to the view
# so convert them to scene coordinates
p = self.mapToScene(event.x(), event.y())
self.circleItem = QtGui.QGraphicsEllipseItem(0,0,10,10)
self.circleItem.setPos(p.x()-self.circleItem.boundingRect().width()/2.0,
p.y()-self.circleItem.boundingRect().height()/2.0)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
# self.setScene(self.scene) # <-- this line should not be needed here
# Note, I've renamed the second argument `event`. Otherwise you locally override the QMouseEvent class
def mousePressEvent(self, event):
self.paintMarkers(event)
# you may want to preserve the default mouse press behaviour,
# in which case call the following
return QGraphicsView.mousePressEvent(self, event)
Here we have not needed to use QWidget.mapFromGlobal() (what I covered in the first paragraph) because we use a mouse event from the QGraphicsView which returns coordinates relative to that widget only.
Update 2
Note: I've updated how the item is created/placed in the above code based on this answer.

addText() change the text color inside a QGraphicsView

I have managed to make a vertical text label inside a QGraphicsView which is imported from showGui.UI. Everything is exactly how I want it except I can't figure out to change the text color. setStyleSheet doesn't work with QGraphicsViews. I made another attempt with QPainter but i couldn't get that to roate correctly or inside of my graphics view. All the documentation I have found is in C++ and that leaves me bewildered. I think adding:
self.trans_graphicsView_cat.drawForeground(QPainter(setPen(QtGui.QColor(168, 34, 3)))
into the createScene Function will do it but I haven't been able to crack it.
My Code:
class MainDialog(QtGui.QMainWindow, showGui.Ui_MainWindow):
dbPath = appDataPath + "barter.db"
dbConn = sqlite3.connect(dbPath)
dbConn.text_factory = str
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.text = self.tr("Categories")
self.createScene()
def createScene(self):
scene = QtGui.QGraphicsScene()
self.trans_graphicsView_cat.setScene(scene)
item = scene.addText(self.text, QtGui.QFont('Arial Black', 15, QtGui.QFont.Light))
item.rotate(270)
The addText method returns a QGraphicsTextItem. This class has a setDefaultTextColor method which should allow you to change the colour of the text.
Alternatively, you could create your own instance of QGraphicsTextItem and then use addItem to add it to the scene. Note that the QGraphicsTextItem class has a setHtml method which should give you even greater control over the formatting of the text.

Categories

Resources