Pivy - get coordinates from context in event handler

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
tomkcook
Posts: 87
Joined: Wed Jul 20, 2016 3:39 pm

Pivy - get coordinates from context in event handler

Post by tomkcook »

Can someone point me to how I can get the `SoCoordinate3` data from the scenegraph context in a Pivy event handler?

A full example is below, but the important bit is the event handler:

Code: Select all

    def key_cb(self, attr, event_cb):
        state = event_cb.getAction().getState()
        co_el = coin.SoCoordinateElement.getInstance(state)
        points = co_el.getArrayPtr3()
Here `coin.SoCoordinateElement.getInstance(state)` is returning None. Is there another way of doing this?

Code: Select all

import FreeCAD, FreeCADGui
from pivy import coin
from pivy import graphics

class TestSep(coin.SoSeparator):
    def __init__(self):
        super(coin.SoSeparator, self).__init__()
        self.marker = coin.SoMarkerSet()
        self.events = coin.SoEventCallback()
        self += self.events, self.marker
        self.events.addEventCallback(
            coin.SoKeyboardEvent.getClassTypeId(), self.key_cb)

    def key_cb(self, attr, event_cb):
        state = event_cb.getAction().getState()
        co_el = coin.SoCoordinateElement.getInstance(state)
        points = co_el.getArrayPtr3()

class TestObj():
    def __init__(self, obj):
        # self.Type = 'hull'
        obj.Proxy = self
        TestObjViewProvider(obj.ViewObject)

    def onChanged(self, fp, prop):
        pass

    def execute(self, obj):
        pass

class TestObjViewProvider():
    def __init__(self, obj):
        self.ViewObject = obj
        obj.Proxy = self

    def attach(self, obj):
        parent = coin.SoSeparator()
        c = coin.SoCoordinate3()
        c.point.setValues(0, 1, [FreeCAD.Vector(0, 1, 0)])
        m = TestSep()
        parent += c, m
        for mode in self.getDisplayModes(obj):
            obj.addDisplayMode(parent, mode)

    def updateData(self, fp, prop):
        pass

    def getDisplayModes(self, obj):
        return ['Shaded']

    def getDefaultDisplayMode(self):
        return 'Shaded'

    def setDisplayMode(self, mode):
        return mode

    def onChanged(self, vp, prop):
        pass

    def getIcon(self):
        return """
/* XPM */
static const char * ViewProviderBox_xpm[] = {
"16 16 6 1",
"    c None",
".   c #141010",
"+   c #615BD2",
"@   c #C39D55",
"#   c #000000",
"$   c #57C355",
"        ........",
"   ......++..+..",
"   .@@@@.++..++.",
"   .@@@@.++..++.",
"   .@@  .++++++.",
"  ..@@  .++..++.",
"###@@@@ .++..++.",
"##$.@@$#.++++++.",
"#$#$.$$$........",
"#$$#######      ",
"#$$#$$$$$#      ",
"#$$#$$$$$#      ",
"#$$#$$$$$#      ",
" #$#$$$$$#      ",
"  ##$$$$$#      ",
"   #######      "};
"""

    def __getstate__(self):
        return None

    def __setState__(self, state):
        return None

def makeTest():
    a = FreeCAD.ActiveDocument.addObject('App::FeaturePython', 'Test')
    TestObj(a)
vocx
Veteran
Posts: 5197
Joined: Thu Oct 18, 2018 9:18 pm

Re: Pivy - get coordinates from context in event handler

Post by vocx »

tomkcook wrote: Mon Sep 14, 2020 4:37 pm Can someone point me to how I can get the `SoCoordinate3` data from the scenegraph context in a Pivy event handler?
I suggest inspecting how the Draft workbench does it. For example, the drafttools/gui_lines.py module shows how it uses the active view to pick points.

Code: Select all

self.view.addEventCallback("SoEvent", self.action)

def action(self, arg):
    if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
        self.finish()
    elif arg["Type"] == "SoLocation2Event":
        self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
        gui_tool_utils.redraw3DView()
    elif (arg["Type"] == "SoMouseButtonEvent"
          and arg["State"] == "DOWN"
          and arg["Button"] == "BUTTON1"):
The gui_tool_utils.getPoint function gets the point directly from the active view.

Code: Select all

p = Gui.ActiveDocument.ActiveView.getCursorPos()
point = Gui.ActiveDocument.ActiveView.getPoint(p)
info = Gui.ActiveDocument.ActiveView.getObjectInfo(p)
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
tomkcook
Posts: 87
Joined: Wed Jul 20, 2016 3:39 pm

Re: Pivy - get coordinates from context in event handler

Post by tomkcook »

Thanks for your response, but that's not quite what I'm trying to do. I'm not trying to get the point where the event happened (which I think is what your code does) but get a reference to the nearest SoCoordinate3 in the scene graph stack. This might of course contain more than one coordinate and would also let the event handler modify the scenegraph by changing the values in the SoCoordinate3.

The method I attempted is based on this function from coin3d's SoVertexShape.cpp:

Code: Select all

void
SoVertexShape::getVertexData(SoState * state,
                             const SoCoordinateElement *& coords,
                             const SbVec3f *& normals,
                             const SbBool neednormals)
{
  coords = SoCoordinateElement::getInstance(state);
  assert(coords);

  normals = NULL;
  if (neednormals) {
    normals = SoNormalElement::getInstance(state)->getArrayPtr();
  }
}
But this doesn't seem to work with the state object returned by the callback event.
tomkcook
Posts: 87
Joined: Wed Jul 20, 2016 3:39 pm

Re: Pivy - get coordinates from context in event handler

Post by tomkcook »

Thanks to some help over at github, we figured this out. To do what I was attempting here, it's necessary to put this in the TestSep constructor:

Code: Select all

    def __init__(self):
        super(coin.SoSeparator, self).__init__()
        # Add this line:
        coin.SoHandleEventAction.enableElement(coin.SoCoordinateElement.getClassTypeId(), coin.SoCoordinateElement.getClassStackIndex())
        self.marker = coin.SoMarkerSet()
        self.events = coin.SoEventCallback()
        self += self.events, self.marker
        self.events.addEventCallback(
        coin.SoKeyboardEvent.getClassTypeId(), self.key_cb)
Here SoHandleEventAction is the type of the action that is passed to the event handler and SoCoordinateElement is the element type that I want to get from the stack.
Post Reply