0

I am trying to paint pyqtgraph.opengl.GLLinePlotItem nested within other GLLinePlotItem with different parents, where the painted result is identical had you not nested them and instead just painted normally.

So if there is a GLViewWidget containing 2 child 'GLLinePlotItem' collections (l1, l2), which have 2 different transformation's to their respective GLLinePlotItem parent (self.nestPar1, self.nestPar2). Then each parent has their own parent (self.gP1, self.gP2) and have different transforms wrt to the GLViewWidet. The goal is to have the paint method from self.nestPar1 combined all the children pos arrays but still have the correct world as if it were never combined. The 22 lineage is just a copy of the 2 lineage, but the grandparent is added directly to the widget, therefore the intended output from the l2 lines contained in self.nestPar2 should match the l22 contained in self.nestPar22.

from PySide6 import QtWidgets, QtGui
import numpy as np, sys
from pyqtgraph.opengl import GLLinePlotItem, GLViewWidget, GLTextItem
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem

class BlockUpdateGLLinePlot(GLLinePlotItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)   

class NestedLineItem(GLLinePlotItem):
    def __init__(self, *args, **kwargs):
        self._otherNests=[]
        self._lineItems=[]
        self.blockUpdates=False
        self.blockPaint=False
        super().__init__(*args, **kwargs)   

    def addNest(self, nestedLineItem):
        self._otherNests.append(nestedLineItem)
    
    def addLineItem(self, lineItem):
        self._lineItems.append(lineItem)
        
    def paint(self):
        if not self.blockPaint:
            combined_pos = None
            for lineItem in self._lineItems:
                pos = self._mapLineItem(lineItem.pos.copy(), lineItem.transform())
                combined_pos = pos if combined_pos is None else np.vstack((combined_pos, pos))
            
            for nestedLineItem in self._otherNests:
                for lineItem in nestedLineItem._lineItems:
                    pos = self._mapLineItem(lineItem.pos.copy(), lineItem.transform())
                    combined_pos = pos if combined_pos is None else np.vstack((combined_pos, pos))
            
                self.last_child2pos = pos[-1] # last position of the child2 GLLinePlotItem's
            
            self.blockUpdates=True
            self.setData(pos=combined_pos)
            self.blockUpdates=True
            super().paint()

    def _mapLineItem(self, pos, tr):        
        if not tr.isIdentity():
            points_h = np.hstack([pos, np.ones((pos.shape[0], 1))])
            tr_matrix = np.array(tr.data()).reshape(4, 4).T
            transformed_h = points_h @ tr_matrix.T
            pos = transformed_h[:, :3]
        return pos
    
    def update(self):
        if not self.blockUpdates:
            super().update()    
    
class Window(QtWidgets.QMainWindow):
    def __init__(self, *args, app=None, **kwargs):
        super().__init__(*args, **kwargs)   
        self.gl_widget = GLViewWidget()
        self.setCentralWidget(self.gl_widget)
        
        self.gP1, self.gP2, self.gP22 = GLGraphicsItem(), GLGraphicsItem(), GLGraphicsItem()        
        self.gP1.translate(0, 1, 1), self.gP2.translate(1, 0, -1), self.gP22.translate(1, 0, -1)

        self.nestPar1, self.nestPar2, self.nestPar22 = NestedLineItem(color="red", mode="lines"), NestedLineItem(color="yellow", mode="lines"), NestedLineItem(color="orange", mode="lines")
        self.nestPar1.translate(0.2, 1, 1), self.nestPar2.translate(-0.2, 0, 0.2), self.nestPar22.translate(-0.2, 0, 0.2)
        
        self.nestPar1.setParentItem(self.gP1), self.nestPar2.setParentItem(self.gP2), self.nestPar22.setParentItem(self.gP22)
        
        lineLength = 1
        
        for z in np.linspace(0, 5, 6):
            pos1 = [[0, 0, z], [lineLength, 0, z]]
            pos2 = [[0, 1, z ], [lineLength, 1, z]]  
            pos22 = [[0, 1, z + 0.03], [lineLength, 1, z + 0.03]] # small offset
            l1, l2, l22 = GLLinePlotItem(pos=pos1, mode="lines"), GLLinePlotItem(pos=pos2, mode="lines"), GLLinePlotItem(pos=pos22, mode="lines")
            
            translation = -0.5, -1, 0
            
            l1.translate(*translation, True), l2.translate(*translation, True), l22.translate(*translation, True)
            self.nestPar1.addLineItem(l1), self.nestPar2.addLineItem(l2), self.nestPar22.addLineItem(l22)
        
        self.nestPar2.blockPaint=True
        self.nestPar1.addNest(self.nestPar2)
        
        self.gl_widget.addItem(self.gP1)
        self.gl_widget.addItem(self.gP22)
        
        self.showMaximized()
        self.nestPar1.paint()
        
        text2 = GLTextItem(pos=self.nestPar1.last_child2pos, text="mapping of child2 (incorrect)", font=QtGui.QFont("Arial", 6))
        text2.setTransform(self.nestPar1.transform())
        text2.setParentItem(self.gP1)
        
        text22 = GLTextItem(pos=pos22[-1], text="Child2 should be here", font=QtGui.QFont("Arial", 6))
        text22.translate(*translation, True)
        text22.setParentItem(self.gP22)
        
        
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    sys.exit(app.exec())

For example, a child1 coordinate of (1,0,2) with parent transformations, results in a (2,2,2) coordinate, which is what is painted on the view widget, and for a child2 coordinate of (3,2,5) with arbitrary transformations results in a (4,3,2). In other words, child1.setData((1,0,2) will give a painted dot on the widget at coordinate (2,2,2) and a child1.setData((3,2,5)) will give a painted dot at the (4,3,2) point.

enter image description here

I want to be able to use that same child2 local coordinate of (3,2,5) and have it stored with child1, with the parent1 using that (3,2,5) coordinate to still output a (4,3,2) point, which will require some type of transformation (which is the blue line). I am essentially trying to combine 2 GLLinePlotItem's with different parentItems and transformation matrices into the same numpy array during the paint method of the parent. But am not sure the required transformation is.

enter image description here

The right-most red lines should be where the orange lines are. The Orange lines is just plotting child2 with it's respective parent normally and the right red lines comes from concatenating the child2 positions with the position arrays in the parent1

enter image description here

It seems that with the paint() mapLineItem methods below works with the given transformations above. But if a scaling method is applied e.g. self.nestPar2.scale(-1, 1, -1), it fails.

    def paint(self):
        if not self.blockPaint:
            combined_pos = None
            for lineItem in self._lineItems:
                pos = self._mapLineItem(lineItem.pos.copy(), lineItem.transform())
                combined_pos = pos if combined_pos is None else np.vstack((combined_pos, pos))
                            
            T = (self.parentItem().transform() * self.transform()).inverted()[0]
            for nestedLineItem in self._otherNests:
                for lineItem in nestedLineItem._lineItems:
                    pos = [T.map(QtGui.QVector3D(p[0], p[1], p[2])) for p in lineItem.pos]
                    
                    pos = [nestedLineItem.parentItem().transform().map(QtGui.QVector3D(p.x(), p.y(), p.z())) for p in pos]
                    pos = [nestedLineItem.transform().map(QtGui.QVector3D(p.x(), p.y(), p.z())) for p in pos]
                    pos = np.array([[p.x(), p.y(), p.z()] for p in pos])
                    
                    pos = self._mapLineItem(pos, lineItem.transform())
                    combined_pos = pos if combined_pos is None else np.vstack((combined_pos, pos))
                    
                self.last_child2pos = pos[-1]
    
            self.blockUpdates=True
            self.setData(pos=combined_pos)
            self.blockUpdates=True
            super().paint()

    def _mapLineItem(self, pos, tr):        
        if not tr.isIdentity():
            points_h = np.hstack([pos, np.ones((pos.shape[0], 1))])
            tr_matrix = np.array(tr.data()).reshape(4, 4).T
            transformed_h = points_h @ tr_matrix.T
            pos = transformed_h[:, :3] / transformed_h[:, 3:4]  
        return pos
2
  • Sorry but your post seems to lack a proper description of the problem you're facing and an explanation about why the result you're getting is not acceptable. Remember that we know absolutely nothing about your problem, we can only make assumptions based on our interpretation of your description and the result of your code. Please take your time to revise your post by adding a simple and clear explanation of what you want (including screenshots of what you currently get and possibly mock-up images of the intended result) and how/why what you get is different from that. Commented Jul 30 at 1:28
  • I have added diagrams and further explanation. I am just trying to find a transformation, tr, such that tr * child2_localPos appended to the parent1 pos array will result in the same world coordinates output as child2 being painted from the parent2 Commented Jul 30 at 5:26

1 Answer 1

0

You need to accumulate world transforms by traversing each item's .parentItem() chain and multiplying their transforms to compute the final world-space position.

Sign up to request clarification or add additional context in comments.

2 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
World transforms are unique, based on context by definition; simply and arbitrarily accumulating them will probably get unexpected results. Can you please improve your answer by providing a more accurate explanation of what you mean, possibly including an appropriate minimal reproducible example, even if it's pseudo-code?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.