cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Add convenience methods to Python API

Add convenience methods to Python API

As a developer, it's very often inconvenient that objects are returned as instances of either APIv1 or APIv2 classes.

Both are often, but not always compatible to each other.

I've created several convenience extension methods for these that I have to inject (e.g. through a VRED Script Plugin) before I can use them in several scripts.

 

Wishlist:

  • easier conversion between APIv1 and APIv2 nodes until APIv1 is deprecated
  • provide iterators to vrNodePtr, vrdNode and more (non-Qt) classes
  • make vrdNode.getChild() capable to handle both int and str input

 

 

Let me know what you think about this idea 🙂

 

Following code has been tested with VRED 2023.2, I don't have access to newer versions of VRED yet.

 

 

 

"""
Provides additional functions for the following classes:
    vrKernelServices.vrKernelServices.vrdNode
     - asVrNodePtr (also available as property vrNodePtr)
     - getChild
     - addChild
    vrNodePtr.vrNodePtr
     - asVrdNode (also available as property vrdNode)
    vrOSGTypes.Pnt3f
     - getitem
    vrOSGTypes.Vec3f
     - getitem
    PySide2.QtGui.QVector3D
     - getitem
"""

import vrNodePtr
import vrKernelServices
from PySide2.QtGui import QVector3D
import vrOSGTypes

def getitem(self, key:int) -> float:
    """
    Extension method to support unpacking of QVector3D objects
    returns:
        float value of the x, y or z coordinate depending on the key
    """
    if key == 0:
        return self.x()
    elif key == 1:
        return self.y()
    elif key == 2:
        return self.z()
    else:
        # IndexError marks the end of an iteration
        raise IndexError(f"__getitem__ of {type(self).__name__} only supports keys in [0,1,2]")
# same method for QVector3D, Pnt3f and Vec3f, because their API is similar
QVector3D.__getitem__ = getitem
vrOSGTypes.Pnt3f.__getitem__ = getitem
vrOSGTypes.Vec3f.__getitem__ = getitem

def asVrdNode(self:vrNodePtr.vrNodePtr) -> vrKernelServices.vrdNode:
    """
    Extension method for vrNodePtr.vrNodePtr that provides access to corresponding VRED API v2 vrKernelServices.vrdNode
    """
    return vrNodeService.getNodeFromId(self.getID())
vrNodePtr.vrNodePtr.asVrdNode = asVrdNode

@property
def vrdNodeProperty(self:vrNodePtr.vrNodePtr) -> vrKernelServices.vrdNode:
    """
    property for vrNodePtr.vrNodePtr that provides access to corresponding VRED API v2 vrKernelServices.vrdNode
    """
    if isinstance(self, vrKernelServices.vrdNode):
        return self
    elif isinstance(self, vrNodePtr.vrNodePtr):
        return self.asVrdNode()
    else:
        raise TypeError(f"Property vrdNode not applicable to objects of type {type(self)}")
vrNodePtr.vrNodePtr.vrdNode = vrdNodeProperty
# it might not seem to be neccessary to provide a 'vrdNode' property to a 'vrKernelServices.vrdNode',
# but it's convenient to have code that's compatible to both types at the same time
vrKernelServices.vrdNode.vrdNode = vrdNodeProperty

def asVrNodePtr(self:vrKernelServices.vrdNode) -> vrNodePtr.vrNodePtr:
    """
    Extension method for vrKernelServices.vrdNode that provides access to corresponding VRED API v1 vrNodePtr.vrNodePtr
    """
    return vrNodePtr.toNode(self.getObjectId())
vrKernelServices.vrdNode.asVrNodePtr = asVrNodePtr

@property
def vrNodePtrProperty(self:vrKernelServices.vrdNode) -> vrNodePtr.vrNodePtr:
    """
    Extension method for vrKernelServices.vrdNode that provides access to corresponding VRED API v1 vrNodePtr.vrNodePtr
    """
    if isinstance(self, vrKernelServices.vrdNode):
        return self.asVrNodePtr()
    elif isinstance(self, vrNodePtr.vrNodePtr):
        return self
    else:
        raise TypeError(f"Property vrNodePtr not applicable to objects of type {type(self)}")
vrKernelServices.vrdNode.vrNodePtr = vrNodePtrProperty
# it might not seem to be neccessary to provide a 'vrNodePtr' property to a 'vrNodePtr.vrNodePtr',
# but it's convenient to have code that's compatible to both types at the same time
vrNodePtr.vrNodePtr.vrNodePtr = vrNodePtrProperty

def getChild(self:vrKernelServices.vrdNode, nameOrIndex) -> vrKernelServices.vrdNode:
    """
    Extension method for vrKernelServices.vrdNode that enables getChild search by name VRED API v1 vrNodePtr.vrNodePtr
    """
    # getChild of API v1 can distinguish between int or str input and can do the job
    return self.asVrNodePtr().getChild(nameOrIndex).asVrdNode()
vrKernelServices.vrdNode.getChild = getChild

def addChild(self:vrKernelServices.vrdNode, node) -> None:
    """
    Extension method for vrKernelServices.vrdNode as wrapper of vrNodePtr.vrNodePtr.addChild from VRED API v1
    """
    if isinstance(node, vrKernelServices.vrdNode):
        self.asVrNodePtr().addChild(node.asVrNodePtr())
    elif isinstance(node, vrNodePtr.vrNodePtr):
        self.asVrNodePtr().addChild(node)
    else:
        raise TypeError(f"Extension method 'addChild' doesn't support type '{type(node).__name__}' of node '{node}'")
vrKernelServices.vrdNode.addChild = addChild

def getVrNodePtrItem(self:vrNodePtr.vrNodePtr, childIndex:int) -> vrNodePtr.vrNodePtr:
    """
    Extension method to support unpacking of vrNodePtr objects
    returns:
        vrNodePtr
    """
    # prevent 'vrNodePtr::getChild : Index out of range!' message in vred terminal
    if not childIndex < self.getNChildren():
        raise IndexError(f"vrNodePtr::getChild : Index {childIndex} out of range [0, {self.getNChildren()}]")
    childVrNodePtr = self.getChild(childIndex)
    if not childVrNodePtr.isValid():
        raise IndexError(f"vrNodePtr::getChild : Child node with index {childIndex} doesn't exist")
    return childVrNodePtr
vrNodePtr.vrNodePtr.__getitem__ = getVrNodePtrItem

def getVrdNodeItem(self:vrKernelServices.vrdNode, childIndex:int) -> vrKernelServices.vrdNode:
    """
    Extension method to support unpacking of vrKernelServices.vrdNode objects
    returns:
        vrKernelServices.vrdNode
    """
    if not childIndex < self.getChildCount():
        raise IndexError(f"vrNodePtr::getChild : Index {childIndex} out of range [0, {self.getChildCount()}]")
    childVrdNode = self.getChild(childIndex)
    if not childVrdNode.isValid():
        raise IndexError(f"vrKernelServices.vrdNode::getChild : Child node with index {childIndex} doesn't exist")
    return childVrdNode
vrKernelServices.vrdNode.__getitem__ = getVrdNodeItem

if __name__ == '__main__':
    # API v1
    root_v1 = getRootNode()
    print(f"type(root_v1) = {type(root_v1)}")
    assert isinstance(root_v1, vrNodePtr.vrNodePtr)

    print(f"type(root_v1.vrdNode) = {type(root_v1.vrdNode)}")
    assert isinstance(root_v1.vrdNode, vrKernelServices.vrdNode)

    print(f"type(root_v1.asVrdNode()) = {type(root_v1.asVrdNode())}")
    assert isinstance(root_v1.asVrdNode(), vrKernelServices.vrdNode)

    print(f"[childNode.getName() for childNode in root_v1] = {[childNode.getName() for childNode in root_v1]}")
    assert "Perspective" in [childNode.getName() for childNode in root_v1]

    # API v2
    root_v2 = vrNodeService.getRootNode()

    print(f"type(root_v2) = {type(root_v2)}")
    assert isinstance(root_v2, vrKernelServices.vrdNode)

    print(f"type(root_v2.vrNodePtr) = {type(root_v2.vrNodePtr)}")
    assert isinstance(root_v2.vrNodePtr, vrNodePtr.vrNodePtr)

    print(f"type(root_v2.asVrNodePtr()) = {type(root_v2.asVrNodePtr())}")
    assert isinstance(root_v2.asVrNodePtr(), vrNodePtr.vrNodePtr)

    print(f"type(root_v2.getChild('Perspective')) = {type(root_v2.getChild('Perspective'))}")
    assert isinstance(root_v2.getChild('Perspective'), vrKernelServices.vrdNode)

    print(f"[childNode.getName() for childNode in root_v2] = {[childNode.getName() for childNode in root_v2]}")
    assert "Perspective" in [childNode.getName() for childNode in root_v2]

    # Qt / Pnt3f / Vec3f
    q = QVector3D(1, 2, 3)
    p = Pnt3f(1, 2, 3)
    v = Vec3f(1, 2, 3)
    for obj in [q,p,v]:
        print(f"{type(obj)} values: {[value for value in obj]}")

 

 

Usage is shown in the '__main__' section. Script can be imported to inject methods to existing classes.

 

Output in the VRED terminal:

 

type(root_v1) = <class 'vrNodePtr.vrNodePtr'>
type(root_v1.vrdNode) = <class 'vrKernelServices.vrdNode'>
type(root_v1.asVrdNode()) = <class 'vrKernelServices.vrdNode'>
[childNode.getName() for childNode in root_v1] = ['VREDMaterialPool', 'Perspective', 'Front', 'Side', 'Top', 'Environments']
type(root_v2) = <class 'vrKernelServices.vrdNode'>
type(root_v2.vrNodePtr) = <class 'vrNodePtr.vrNodePtr'>
type(root_v2.asVrNodePtr()) = <class 'vrNodePtr.vrNodePtr'>
type(root_v2.getChild('Perspective')) = <class 'vrKernelServices.vrdCameraNode'>
[childNode.getName() for childNode in root_v2] = ['VREDMaterialPool', 'Perspective', 'Front', 'Side', 'Top', 'Environments']
<class 'PySide2.QtGui.QVector3D'> values [1.0, 2.0, 3.0]
<class 'vrOSGTypes.Pnt3f'> values [1.0, 2.0, 3.0]
<class 'vrOSGTypes.Vec3f'> values [1.0, 2.0, 3.0]

Can't find what you're looking for? Ask the community or share your knowledge.

Submit Idea