Closed laurentott closed 2 years ago
Thank you @lott999
XrSessionActionSetsAttachInfo.actionSets
is a pointer to an XrActionSet
.
The type XrActionSet
is a handle type defined as a pointer to an opaque XrActionSet_T
structure.
So it's actually correct for action_sets
to be annotated as POINTER(POINTER(ActionSet_T))
.
But it might be clearer if it were written as POINTER(ActionSetHandle)
Thank you. But then how do I need to fill the XrSessionActionSetsAttachInfo.actionSets field to make xr.attach_session_action_sets( ... )
succeed ? Below I modified the track_hmd.py example to retrieve the controller poses following the openxr_make_actions() and openxr_poll_actions() functions from @maluoi https://github.com/maluoi/OpenXRSamples/blob/master/SingleFileExample/main.cpp
The code works if I change POINTER(POINTER(ActionSet_T)) to POINTER(ActionSet_T) in xr/typedefs.py and if I don't I get
TypeError: incompatible types, LP_ActionSet_T instance instead of LP_LP_ActionSet_T instance
import time
import xr
import ctypes
# Once XR_KHR_headless extension is ratified and adopted, we
# should be able to avoid the Window and frame stuff here.
with xr.Instance(application_name="track_hmd") as instance:
with xr.System(instance) as system:
with xr.GlfwWindow(system) as window:
with xr.Session(system) as session:
asci = xr.ActionSetCreateInfo()
asci.action_set_name = b'gameplay'
asci.localized_action_set_name = b'Gameplay'
action_set = xr.create_action_set(instance.handle, asci)
hand_subaction_path = (xr.Path * 2)(*([xr.Path()] * 2))
hand_subaction_path[0] = xr.string_to_path(instance.handle, "/user/hand/left")
hand_subaction_path[1] = xr.string_to_path(instance.handle, "/user/hand/right")
#Create an action to track the position and orientation of the hands! This is
#the controller location, or the center of the palms for actual hands.
aci = xr.ActionCreateInfo()
aci.action_name = b'hand_pose'
aci.localized_action_name = b'Hand_pose'
aci.action_type = xr.ActionType.POSE_INPUT.value
aci.count_subaction_paths = len(hand_subaction_path)
aci.subaction_paths = hand_subaction_path
pose_action = xr.create_action(action_set, aci)
#Create an action for listening to the select action! This is primary trigger
#on controllers, and an airtap on HoloLens
aci.action_name = b'select'
aci.localized_action_name = b'Select'
aci.action_type = xr.ActionType.BOOLEAN_INPUT.value
select_action = xr.create_action(action_set, aci)
#Bind the actions we just created to specific locations on the Khronos simple_controller
#definition! These are labeled as 'suggested' because they may be overridden by the runtime
#preferences. For example, if the runtime allows you to remap buttons, or provides input
#accessibility settings
pose_path = (xr.Path * 2)(*([xr.Path()] * 2))
pose_path[0] = xr.string_to_path(instance.handle, "/user/hand/left/input/grip/pose")
pose_path[1] = xr.string_to_path(instance.handle, "/user/hand/right/input/grip/pose")
profile_path = xr.string_to_path(instance.handle, "/interaction_profiles/khr/simple_controller")
bindings = (xr.ActionSuggestedBinding * 2)(*([xr.ActionSuggestedBinding()] * 2))
bindings[0].action = pose_action
bindings[0].binding = pose_path[0]
bindings[1].action = pose_action
bindings[1].binding = pose_path[1]
suggested_binds = xr.InteractionProfileSuggestedBinding()
suggested_binds.interaction_profile = profile_path
suggested_binds.count_suggested_bindings = len(bindings)
suggested_binds.suggested_bindings = bindings
xr.suggest_interaction_profile_bindings(instance.handle , suggested_binds)
# Create frames of reference for the pose actions
hand_space = [None] * 2
for hand in range(2):
asci = xr.ActionSpaceCreateInfo()
asci.action = pose_action
asci.pose_in_action_space = xr.Posef()
asci.subaction_path = hand_subaction_path[hand]
hand_space[hand] = xr.create_action_space(session.handle , asci)
#Attach the action set we just made to the session
ai = xr.SessionActionSetsAttachInfo()
ai.count_action_sets = 1
p_action_set = ctypes.cast(
ctypes.byref(action_set),
ctypes.POINTER(xr.ActionSet_T))
ai.action_sets = p_action_set
xr.attach_session_action_sets(session.handle, ai)
for _ in range(10):
session.poll_xr_events()
if session.state in (
xr.SessionState.READY,
xr.SessionState.SYNCHRONIZED,
xr.SessionState.VISIBLE,
xr.SessionState.FOCUSED,
):
session.wait_frame()
session.begin_frame()
view_state, views = session.locate_views()
print('LEFT eye pose' + str(views[xr.Eye.LEFT.value].pose), flush=True)
if session.state == xr.SessionState.FOCUSED:
# Update our action set with up-to-date input data!
active_action_set = xr.ActiveActionSet()
active_action_set.action_set = action_set
active_action_set.subaction_path = xr.NULL_PATH
sync_info = xr.ActionsSyncInfo()
sync_info.count_active_action_sets = 1
p_active_action_set = ctypes.cast(
ctypes.byref(active_action_set),
ctypes.POINTER(xr.ActiveActionSet))
sync_info.active_action_sets = p_active_action_set
xr.sync_actions(session.handle, sync_info)
render_hand = [None] * 2
hand_pose = [None] * 2
# Now we'll get the current states of our actions, and store them for later use
for hand in range(2):
get_info = xr.ActionStateGetInfo()
get_info.subaction_path = hand_subaction_path[hand]
get_info.action = pose_action
pose_state = xr.get_action_state_pose(session.handle, get_info)
render_hand[hand] = pose_state.is_active
space_location = xr.locate_space(hand_space[hand], session.space.handle, session.frame_state.predicted_display_time)
if space_location.location_flags & xr.SPACE_LOCATION_POSITION_VALID_BIT and space_location.location_flags & xr.SPACE_LOCATION_ORIENTATION_VALID_BIT:
hand_pose[hand] = space_location.pose
print('HAND ' + str(hand) + ' ' + str(hand_pose[hand]), flush=True )
time.sleep(0.5)
session.end_frame()
What happens if you change your explicit cast like so?
p_action_set = ctypes.cast(
ctypes.byref(action_set),
ctypes.POINTER(xr.ActionSetHandle))
OK that does the trick, thanks
@lott999 I'm glad you got it working, and I'm glad you are working on this.
Your work on this presents an opportunity to improve the pyopenxr bindings.
Anytime an explicit cast like this is required, it's worth asking if the python methods can be improved.
For example, xr.create_action_set()
should maybe return an object pre-cast to type ActionSetHandle
.
And SessionActionSetsAttachInfo
should accept some reasonable values of the action_set attribute without casting or errors.
When the C++ API has two parameters like this for the count and a pointer to an array, a more pythonic interface would be to accept just one parameter taking an object that knows its own length. At the very least a ctypes array constructed like (ActionSetHandle * 1)(*[action_set, ])
, but also maybe any valid python sequence of ActionSetHandle
s, like simply [action_set, ]
.
I can avoid the ctypes.{cast,byref,POINTER}
using your idea of stacking the action sets in a list, and using that list to get the count.
#Attach the action set we just made to the session
ai = xr.SessionActionSetsAttachInfo()
action_sets = [action_set, ]
ai.action_sets = (ActionSetHandle * len(action_sets))(*action_sets)
ai.count_action_sets = len(action_sets)
xr.attach_session_action_sets(session.handle, ai)
Thanks
That's great! That's more semantically reasonable code. I still think it would be useful to push some of that boilerplate down into SessionActionSetsAttachInfo
. But also wrap some of this into a higher level ActionSet
class, in the same way we are abstracting the other FooHandle
classes into higher level classes like Session
, Instance
, and System
.
@lott999 I've just finished converting the hello_xr example into python. My working action set code is at https://github.com/cmbruns/pyopenxr_examples/blob/8c7fb4795c49430e80180c2a9872bcb4dd8494b8/xr_examples/hello_xr/openxr_program.py#L335
I'd appreciate any comments or feedback you might have.
The OpenXR specs gives the following definition
but in xr/typedefs.py there is
I had to change both
POINTER(POINTER(ActionSet_T))
toPOINTER(ActionSet_T)
in order to make a successful call toxr.attach_session_action_sets( ... )