Author Topic: [python api] get current mouse position  (Read 23747 times)

Is there any way to get current mouse position via python api ?

My purpose is to create New Node at mouse position,so I need to get the relative position of the mouse to the current node graph.

Could I get the information via pyside ?

I tried to find out the widget class of graph.But I can't figure out which is which.


[MSG][2079]<PySide2.QtWidgets.QLabel object at 0x000002A25D6DE548>
[MSG][2080]child
[MSG][2081]graphatomic://item_comment
[MSG][2082]objectName
[MSG][2083]_
[MSG][2084]<PySide2.QtWidgets.QLabel object at 0x000002A25D6DE588>
[MSG][2085]child
[MSG][2086]graphatomic://item_frame
[MSG][2087]objectName
[MSG][2088]_
[MSG][2089]<PySide2.QtWidgets.QLabel object at 0x000002A25D6DE5C8>
[MSG][2090]child
[MSG][2091]graphatomic://item_pin
[MSG][2092]objectName
[MSG][2093]_
[MSG][2094]<PySide2.QtWidgets.QToolBar object at 0x000002A25D6DE608>
[MSG][2095]child
[MSG][2096]enginetoolbar
[MSG][2097]objectName
[MSG][2098]_
Last Edit: August 01, 2019, 11:38:35 am

There's probably a better way to do whatever it is that you need with the cursor position and widgets.
What exactly is your objective? Can you provide more details?

To answer your question, you can get the cursor pos using Qt, rather than get external Python libraries for cross-platform support:
Code: [Select]
from PySide2 import QtGui

print QtGui.QCursor().pos()

thank you NevTD.

I know how to get mouse position in screen coordinate.But it's not my purpose.

Acturally I want to know the relative position of mouse to the CURRENT GRAPH.

It’s relative, even though I pan my view ,my mouse comes from right to left on the screen, the relative position is still not change.


I don't know exactly how the Node Graph window was build. I tried to get every child of the mainWindow ,but still can't find what controls the Node Graph window.

There's probably a better way to do whatever it is that you need with the cursor position and widgets.
What exactly is your objective? Can you provide more details?

To answer your question, you can get the cursor pos using Qt, rather than get external Python libraries for cross-platform support:
Code: [Select]
from PySide2 import QtGui

print QtGui.QCursor().pos()

forgot  to say , my purpose is to create a new node at the mouse position .

There's probably a better way to do whatever it is that you need with the cursor position and widgets.
What exactly is your objective? Can you provide more details?

To answer your question, you can get the cursor pos using Qt, rather than get external Python libraries for cross-platform support:
Code: [Select]
from PySide2 import QtGui

print QtGui.QCursor().pos()

thank you NevTD.

I know how to get mouse position in screen coordinate.But it's not my purpose.

Acturally I want to know the relative position of mouse to the CURRENT GRAPH.

It’s relative, even though I pan my view ,my mouse comes from right to left on the screen, the relative position is still not change.


I don't know exactly how the Node Graph window was build. I tried to get every child of the mainWindow ,but still can't find what controls the Node Graph window.

Ah sorry, somehow I skipped over that part from your original post.
To get the relative position would be something like:
Code: [Select]
QWidget.mapFromGlobal(QtGui.QCursor().pos())
or
QGraphicsView.mapFromGlobal(QtGui.QCursor().pos())
However, in your case both QWidget and QGraphicsView (likely what they're using for the graph) are undefined, which seems to be the other issue you're facing.

You could capture it by re-implementing the QMouseEvents as well, but you're still facing the same issue of not knowing which widget the graph belongs to.

I don't have access to the latest Designer version/API docs at work, unfortunately, and the older versions don't support Qt. I'll look into how easy it is to get the graph once I'm home and have a chance.
Last Edit: August 01, 2019, 03:34:49 pm

So it appears neither the SD API or the PyQt wrapper allows you to do this.
You were on the right track...

Here's a slightly hackey but effective solution to create nodes at the cursor position relative to the graph:
Code: [Select]
from PySide2 import QtGui
from sd.api.sduimgr import SDUIMgr
import sd

class CompNodeCreator(object):
    def __init__(self):
        # Get app and UI manager.
        ctx = sd.getContext()
        app = ctx.getSDApplication()
        self.ui_manager = app.getQtForPythonUIMgr()
       
        # Get Qt window.
        self.qt_app_window = self.ui_manager.getMainWindow()
       
        # Qt graph widgets.
        self.qt_graph_widget = None
        self.qt_graph_viewer = None
   
    def add_node(self, name):
        """ Add node to the graph.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
        """
        valid = self._check_qt_graph_validity()
   
        if valid:
            return self._create_comp_node_at_cursor(name)
        else:
            print('Focus is invalid. Cursor must be over graph')

               
    def _check_qt_graph_validity(self):
        """ Checks if the widget under cursor is actually the graph.
       
        Returns:
            (bool): True if cursor is above graph. False if cursor is anywhere else.
        """
        # Get related Qt graph widgets.
        self.qt_graph_widget = self.qt_app_window.childAt(QtGui.QCursor().pos())  # Returns QWidget.
       
        # The base class name of this graph QtGraphicsView object is 'Pfx::Editor::Components::Graph::GraphView'.
        # Continue operation only if the QGrapgicsView object class matches the class name.
        # This is to prevent other QGraphicsView objects (2D View) from accidentally being considered.
        if self.qt_graph_widget:
            self.qt_graph_viewer = self.qt_graph_widget.parent()  # Should return QGraphicsView.
           
            if self.qt_graph_viewer.metaObject().className() == 'Pfx::Editor::Components::Graph::GraphView':
                return True
            else:
                return False
        else:
            return False
   
    def _create_comp_node_at_cursor(self, name):
        """ Create comp node object at cursor position relative to QGraphicsScene.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
       
        Returns:
            sd_node(sd.api.sdnode): Node object
        """
        scene = self.qt_graph_viewer.scene()  # Returns QGraphicsScene.
       
        # Map global cursor position to viewer and then translate to scene position.
        # This will give us the cursor position relative to the QGraphicsScene object.
        view_pos = self.qt_graph_viewer.mapFromGlobal(QtGui.QCursor().pos())
        scene_pos = self.qt_graph_viewer.mapToScene(view_pos)
       
        # Get the comp graph.
        sd_graph = self.ui_manager.getCurrentGraph()
       
        # Create a node.
        node_def_id = 'sbs::compositing::{definition}'.format(definition=name)
        sd_node = sd_graph.newNode(node_def_id)
        sd_node.setPosition(sd.api.sdbasetypes.float2(scene_pos.x(), scene_pos.y()))
       
        return sd_node

# Run.
if __name__ == '__main__':
    node_creator = CompNodeCreator()
    node = node_creator.add_node(name='hsl')
    print(node)

####################
# Testing...
"""
# Get app and UI manager.
ctx = sd.getContext()
app = ctx.getSDApplication()
ui_manager = app.getQtForPythonUIMgr()

# Get QT window.
qt_app_window = ui_manager.getMainWindow()

# Used to recursively track down all widgets of window and get their class names.
# Can be used to find 'Pfx::Editor::Components::Graph::GraphView'.
def recursive_search(parent, lvl):
    for child in parent.children():
        print('{0} - {1}'.format(lvl, child.metaObject().className()))
       
        if child.children():
            lvl += 1
            recursive_search(child, lvl)
recursive_search(qt_app_window, 0)
"""
Last Edit: August 02, 2019, 02:55:28 am

Wow, it works out very well. Thank you so much NevTD, I learned so much from you .

Are you the allegorithmic staff ? How do you know the class name is  Pfx::Editor::Components::Graph::GraphView

perfect solute ;)


So it appears neither the SD API or the PyQt wrapper allows you to do this.
You were on the right track...

Here's a slightly hackey but effective solution to create nodes at the cursor position relative to the graph:
Code: [Select]
from PySide2 import QtGui
from sd.api.sduimgr import SDUIMgr
import sd

class CompNodeCreator(object):
    def __init__(self):
        # Get app and UI manager.
        ctx = sd.getContext()
        app = ctx.getSDApplication()
        self.ui_manager = app.getQtForPythonUIMgr()
       
        # Get Qt window.
        self.qt_app_window = self.ui_manager.getMainWindow()
       
        # Qt graph widgets.
        self.qt_graph_widget = None
        self.qt_graph_viewer = None
   
    def add_node(self, name):
        """ Add node to the graph.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
        """
        valid = self._check_qt_graph_validity()
   
        if valid:
            return self._create_comp_node_at_cursor(name)
        else:
            print('Focus is invalid. Cursor must be over graph')

               
    def _check_qt_graph_validity(self):
        """ Checks if the widget under cursor is actually the graph.
       
        Returns:
            (bool): True if cursor is above graph. False if cursor is anywhere else.
        """
        # Get related Qt graph widgets.
        self.qt_graph_widget = self.qt_app_window.childAt(QtGui.QCursor().pos())  # Returns QWidget.
       
        # The base class name of this graph QtGraphicsView object is 'Pfx::Editor::Components::Graph::GraphView'.
        # Continue operation only if the QGrapgicsView object class matches the class name.
        # This is to prevent other QGraphicsView objects (2D View) from accidentally being considered.
        if self.qt_graph_widget:
            self.qt_graph_viewer = self.qt_graph_widget.parent()  # Should return QGraphicsView.
           
            if self.qt_graph_viewer.metaObject().className() == 'Pfx::Editor::Components::Graph::GraphView':
                return True
            else:
                return False
        else:
            return False
   
    def _create_comp_node_at_cursor(self, name):
        """ Create comp node object at cursor position relative to QGraphicsScene.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
       
        Returns:
            sd_node(sd.api.sdnode): Node object
        """
        scene = self.qt_graph_viewer.scene()  # Returns QGraphicsScene.
       
        # Map global cursor position to viewer and then translate to scene position.
        # This will give us the cursor position relative to the QGraphicsScene object.
        view_pos = self.qt_graph_viewer.mapFromGlobal(QtGui.QCursor().pos())
        scene_pos = self.qt_graph_viewer.mapToScene(view_pos)
       
        # Get the comp graph.
        sd_graph = self.ui_manager.getCurrentGraph()
       
        # Create a node.
        node_def_id = 'sbs::compositing::{definition}'.format(definition=name)
        sd_node = sd_graph.newNode(node_def_id)
        sd_node.setPosition(sd.api.sdbasetypes.float2(scene_pos.x(), scene_pos.y()))
       
        return sd_node

# Run.
if __name__ == '__main__':
    node_creator = CompNodeCreator()
    node = node_creator.add_node(name='hsl')
    print(node)

####################
# Testing...
"""
# Get app and UI manager.
ctx = sd.getContext()
app = ctx.getSDApplication()
ui_manager = app.getQtForPythonUIMgr()

# Get QT window.
qt_app_window = ui_manager.getMainWindow()

# Used to recursively track down all widgets of window and get their class names.
# Can be used to find 'Pfx::Editor::Components::Graph::GraphView'.
def recursive_search(parent, lvl):
    for child in parent.children():
        print('{0} - {1}'.format(lvl, child.metaObject().className()))
       
        if child.children():
            lvl += 1
            recursive_search(child, lvl)
recursive_search(qt_app_window, 0)
"""

How do you know the class name is  Pfx::Editor::Components::Graph::GraphView

Glad it worked for you.  :)

There's a small commented out section I left in at the bottom so you could see my thought process.
Initially I was trying to find the graph by recursively finding all the widgets in the window and filtering by the objectName, but almost all of the object names were empty.
I decided to get the class name instead (see "recursive_search" function) and searched the results for everything with "graph".

Alternatively, you can use 'self.qt_graph_widget = self.qt_app_window.childAt(QtGui.QCursor().pos())' while the cursor is over the graph and then get the parent of that widget (QGraphicsView) and check its class name, which will return  'Pfx::Editor::Components::Graph::GraphView' as well.

I haven't tested it with other graph types 'mdl', 'fx' etc., but I assume it would still work since 'Pfx::Editor::Components::Graph::GraphView' is the base class.

clever idea!  appreciate that!

BTW, is there any way to get current selected connections(properties) ?

I found the getCurrentGraphSelection() returns only nodes.


How do you know the class name is  Pfx::Editor::Components::Graph::GraphView

Glad it worked for you.  :)

There's a small commented out section I left in at the bottom so you could see my thought process.
Initially I was trying to find the graph by recursively finding all the widgets in the window and filtering by the objectName, but almost all of the object names were empty.
I decided to get the class name instead (see "recursive_search" function) and searched the results for everything with "graph".

Alternatively, you can use 'self.qt_graph_widget = self.qt_app_window.childAt(QtGui.QCursor().pos())' while the cursor is over the graph and then get the parent of that widget (QGraphicsView) and check its class name, which will return  'Pfx::Editor::Components::Graph::GraphView' as well.

I haven't tested it with other graph types 'mdl', 'fx' etc., but I assume it would still work since 'Pfx::Editor::Components::Graph::GraphView' is the base class.

BTW, is there any way to get current selected connections(properties) ?

I found the getCurrentGraphSelection() returns only nodes.

There doesn't appear to be any way to do this via the API currently, the options for connections are very limited.

Even if you could find the QT object representing the connection pipe, there's not much you could do with it since there doesn't appear to be a way to point that to an SD API object (sdconnection). Forcing a connection change at the QT level, won't necessarily get recognized by Designer, if done improperly.

This is something they should expand in the API.
Last Edit: August 02, 2019, 06:26:14 pm

Got it.Thank you for your patience for answering my questions.

Waiting for SD updating ;)

BTW, is there any way to get current selected connections(properties) ?

I found the getCurrentGraphSelection() returns only nodes.

There doesn't appear to be any way to do this via the API currently, the options for connections are very limited.

Even if you could find the QT object representing the connection pipe, there's not much you could do with it since there doesn't appear to be a way to point that to an SD API object (sdconnection). Forcing a connection change at the QT level, won't necessarily get recognized by Designer, if done improperly.

This is something they should expand in the API.

Hi ,NevTD Trank you again here. By your help, I made a new SD plugin to create new nodes by shortcuts.

It works really good!

https://www.artstation.com/artwork/3oP8Bg


So it appears neither the SD API or the PyQt wrapper allows you to do this.
You were on the right track...

Here's a slightly hackey but effective solution to create nodes at the cursor position relative to the graph:
Code: [Select]
from PySide2 import QtGui
from sd.api.sduimgr import SDUIMgr
import sd

class CompNodeCreator(object):
    def __init__(self):
        # Get app and UI manager.
        ctx = sd.getContext()
        app = ctx.getSDApplication()
        self.ui_manager = app.getQtForPythonUIMgr()
       
        # Get Qt window.
        self.qt_app_window = self.ui_manager.getMainWindow()
       
        # Qt graph widgets.
        self.qt_graph_widget = None
        self.qt_graph_viewer = None
   
    def add_node(self, name):
        """ Add node to the graph.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
        """
        valid = self._check_qt_graph_validity()
   
        if valid:
            return self._create_comp_node_at_cursor(name)
        else:
            print('Focus is invalid. Cursor must be over graph')

               
    def _check_qt_graph_validity(self):
        """ Checks if the widget under cursor is actually the graph.
       
        Returns:
            (bool): True if cursor is above graph. False if cursor is anywhere else.
        """
        # Get related Qt graph widgets.
        self.qt_graph_widget = self.qt_app_window.childAt(QtGui.QCursor().pos())  # Returns QWidget.
       
        # The base class name of this graph QtGraphicsView object is 'Pfx::Editor::Components::Graph::GraphView'.
        # Continue operation only if the QGrapgicsView object class matches the class name.
        # This is to prevent other QGraphicsView objects (2D View) from accidentally being considered.
        if self.qt_graph_widget:
            self.qt_graph_viewer = self.qt_graph_widget.parent()  # Should return QGraphicsView.
           
            if self.qt_graph_viewer.metaObject().className() == 'Pfx::Editor::Components::Graph::GraphView':
                return True
            else:
                return False
        else:
            return False
   
    def _create_comp_node_at_cursor(self, name):
        """ Create comp node object at cursor position relative to QGraphicsScene.

        Args:
            name(str): Node definiton name as defined in docs.
                ../resources/documentation/pythonapi/html/pythonapi/modules/sbs_compositing.html
       
        Returns:
            sd_node(sd.api.sdnode): Node object
        """
        scene = self.qt_graph_viewer.scene()  # Returns QGraphicsScene.
       
        # Map global cursor position to viewer and then translate to scene position.
        # This will give us the cursor position relative to the QGraphicsScene object.
        view_pos = self.qt_graph_viewer.mapFromGlobal(QtGui.QCursor().pos())
        scene_pos = self.qt_graph_viewer.mapToScene(view_pos)
       
        # Get the comp graph.
        sd_graph = self.ui_manager.getCurrentGraph()
       
        # Create a node.
        node_def_id = 'sbs::compositing::{definition}'.format(definition=name)
        sd_node = sd_graph.newNode(node_def_id)
        sd_node.setPosition(sd.api.sdbasetypes.float2(scene_pos.x(), scene_pos.y()))
       
        return sd_node

# Run.
if __name__ == '__main__':
    node_creator = CompNodeCreator()
    node = node_creator.add_node(name='hsl')
    print(node)

####################
# Testing...
"""
# Get app and UI manager.
ctx = sd.getContext()
app = ctx.getSDApplication()
ui_manager = app.getQtForPythonUIMgr()

# Get QT window.
qt_app_window = ui_manager.getMainWindow()

# Used to recursively track down all widgets of window and get their class names.
# Can be used to find 'Pfx::Editor::Components::Graph::GraphView'.
def recursive_search(parent, lvl):
    for child in parent.children():
        print('{0} - {1}'.format(lvl, child.metaObject().className()))
       
        if child.children():
            lvl += 1
            recursive_search(child, lvl)
recursive_search(qt_app_window, 0)
"""

Glad it all worked out.

The plugin looks really useful, well done!