Forum Archive

[SOLVED] First attempt to integrate ARkit and first questions...

Brun0oO

Edit: I found a solution, see here https://forum.omz-software.com/topic/4362/first-attempt-to-integrate-arkit-and-first-questions/29

Hi,

Following some objective-c/swift ARkit examples, I made some python tests using the objc_util module on my iPhone 6S+ with iOS 11 GM.
I move forward step by step... as I'm a complete beginner in "calling objective-c from python"...
My results:
I can open the camera through ARkit but I'm thinking my arscnview is not correctly initialized and my 'delegate' is not correctly set. For example, despite my call to setDebugOptions_, I can't display the feature points for example, or if I insert a beep in the renderer_didAdd_for_ method, I can't ear any sound even if a plane is supposed to be detected by my camera.
So if you have any advice, I'm more than interrested !o)

Here my code:

# coding: utf-8


from objc_util import *
import ui
import os
import sys
#from myDebugToolKit import *

load_framework('SceneKit')
load_framework('ARKit')

# Some 'constants' used by ARkit
# But i can't transfer them to the ARKit framework, why ?????
ARWorldAlignmentGravity = 0
ARWorldAlignmentGravityAndHeading = 1
ARWorldAlignmentCamera = 2

ARPlaneDetectionNone = 0
ARPlaneDetectionHorizontal = 1 << 0
ARPlaneDetectionVertical = 1 << 1

ARSCNDebugOptionNone = 0 
ARSCNDebugOptionShowWorldOrigin = 1 << 0
ARSCNDebugOptionShowFeaturePoints = 1 << 1




SCNScene = ObjCClass('SCNScene')
ARSCNView = ObjCClass('ARSCNView')
ARWorldTrackingConfiguration = ObjCClass('ARWorldTrackingConfiguration')
ARSession = ObjCClass('ARSession')
UIViewController = ObjCClass('UIViewController')
ARPlaneAnchor = ObjCClass('ARPlaneAnchor')



# I should refactor te following line in a class but I need to learn more the create_objcc_class function 
sceneview = None   

# Here two little set up functions used by the main class
def createSampleScene():
    # an empty scene
    scene = SCNScene.scene()
    return scene


def createARSceneView(x, y, w, h, debug=True):
    v = ARSCNView.alloc().initWithFrame_((CGRect(CGPoint(x, y), CGSize(w, h))))
    v.setShowsStatistics_(debug)
    # Problem here... feature points are not shown.... despite the method call
    v.setDebugOptions_(ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOptionShowFeaturePoints)  
    return v

# Some callback definitions used by create_objc_class
def CustomViewController_touchesBegan_withEvent_(_self, _cmd, _touches, event):
    touches = ObjCInstance(_touches)
    for t in touches:
        loc = t.locationInView_(sceneview)
        sz = ui.get_screen_size()
        print(loc)

def CustomViewController_viewWillAppear_(_self, _cmd, animated):    
    configuration = ARWorldTrackingConfiguration.alloc().init()
    # Another problem here...constants aren't well communicated... (my assumption...)  
    configuration.setPlaneDetection_ (ARPlaneDetectionHorizontal)
    configuration.setWorldAlignment_(ARWorldAlignmentGravityAndHeading)

    session = sceneview.session()
    session.runWithConfiguration_(configuration)


def CustomViewController_viewWillDisappear_(_self, _cmd, animated):
    session = sceneview.session()   
    session.pause()


def MyARSCNViewDelegate_renderer_didAdd_for_(_self, _cmd, scenerenderer, node, anchor):
    if not isinstance(anchor, (ARPlaneAnchor)):
        return
    # to be implemented...

# The main class...
class MyARView(ui.View):
    def __init__(self):
        global sceneview
        self.flex = 'WH'

        screen = ui.get_screen_size()

        # set up the scene
        scene = createSampleScene()

        # set up the ar scene view delegate
        methods = [MyARSCNViewDelegate_renderer_didAdd_for_]
        protocols = ['ARSCNViewDelegate']
        MyARSCNViewDelegate = create_objc_class('MyARSCNViewDelegate', NSObject, methods=methods, protocols=protocols)
        delegate = MyARSCNViewDelegate.alloc().init()

        # set up the ar scene view
        sceneview = createARSceneView(0, 0, screen.width, screen.height)
        sceneview.scene = scene
        sceneview.delegate = delegate


        # set up the custom view controller
        methods = [CustomViewController_touchesBegan_withEvent_,            CustomViewController_viewWillAppear_, CustomViewController_viewWillDisappear_]
        protocols = []
        CustomViewController = create_objc_class('CustomViewController', UIViewController, methods=methods, protocols=protocols)
        cvc = CustomViewController.new().init().autorelease()
        cvc.view = sceneview


        # last set up
        self_objc = ObjCInstance(self)
        self_objc.addSubview_(sceneview)

        # workaround : I need to call manually viewWillAppear as otherwise my callback is not called...
        cvc.viewWillAppear_(False)


    def will_close(self):
        session = sceneview.session()
        session.pause()


@on_main_thread
def main():
    v = MyARView()
    v.present('full_screen', hide_title_bar=True)

if __name__ == '__main__':
    main()

Brun0oO

I'm trying to debug this problem using the xcode template in order to create an application from this script.
Then, I hope I can have some informations with the debug tools of xcode...

Brun0oO

@Brun0oO said:

ARSCNDebugOptionShowWorldOrigin = 1 << 0
ARSCNDebugOptionShowFeaturePoints = 1 << 1

I'm not sure about these values. I've reviewed the Apple documentation
https://developer.apple.com/documentation/arkit/arscndebugoptions?language=objc
https://developer.apple.com/documentation/scenekit/scndebugoptions?language=objc
and I decided to test these values :

ARSCNDebugOptionShowWorldOrigin = 1 << 10
ARSCNDebugOptionShowFeaturePoints = 1 << 11

but I got no result :o(
I inspected the debug console with xcode and I didn't find any valuable message.

I don't know :
* if my arkit classes are correctly configured,
* if I need to call a kind of super init (and how can I do this with create_objc_class),
* if my delegates are well configured...

I'm open to any suggestion :o)

mikael

@Brun0oO, I am probably not able to help you directly, but still super-interested in how far you manage to take this. Did you make any progress since your last post?

Printing the configuration in viewWillAppear looks like the options get applied allright:

<ARWorldTrackingConfiguration: 0x1c0499000 planeDetection=Horizontal worldAlignment=GravityAndHeading lightEstimation=Enabled>
mikael

Adding the scene below gives me a floating cube, which hoverever is not fully ”connected” to the surroundings but keeps slipping away as I try to walk around it.

def createSampleScene():
    # an empty scene
    scene = SCNScene.scene()
    root_node = scene.rootNode()

    cube_geometry = SCNBox.boxWithWidth_height_length_chamferRadius_(1, 1, 1, 0)
    cube_node = SCNNode.nodeWithGeometry_(cube_geometry)
    cube_node.setPosition((0, 0, -5))

    light = SCNLight.light()
    light.setType_('omni')
    light_node = SCNNode.node()
    light_node.setLight_(light)
    light_node.setPosition((1.5, 1.5, 1.5))

    root_node.addChildNode_(light_node)
    root_node.addChildNode_(cube_node)

    return scene
Brun0oO

@mikael : thanks for your support. I continue to seek a solution to this 'arkit' challenge ;o)

As a roadmap, my first step is to be able to see the feature points (I need it because I don't know if the arkit framework is correctly set), then, I will try to use the plane detection and then, I will try to add a 3D object to this plane...my dream...

I'm still thinking the major problem lies in the fact that my custom view controller is badly added to the main hierarchy. It's not normal that I need to call manually 'cvc.viewWillAppear_(False)'. So I think some callbacks are not called because my hierarchy is badly "mounted". If someone can check this, I would be grateful...

Another part of the jigsaw : I've noticed if I use 'setDebugOptions_' with a value, this value is clamped to (1<<11)-1 (1<<10 is the last bit flag you can use without any ARkit stuff). It's like my ARWorldTrackingConfiguration was not taken into account...
Moreover, if I inspect the "session" variable in the "CustomViewController_viewWillAppear_" function, "configuration" is actually null :o(

Caution : the following sentence contains Beatles lyrics inside :
"help!, i need somebody... help..." ;o)

JonB

Have you tried on_main_thread in __init__? I think things like adding subviews are supposed to be on the main thread.

mikael

@JonB, is it not already on main thread, since the call to main is decorated?

JonB

good point, yes

Phuket2

Sorry, a side note guys. Has anyone written anything either in a gist or in the forum that really explains @on_main_thread as it applies specifically to Pythonista and some simple logic to know wether you need to be using it. Or is it safe to say if you are not using objC or your own threads that you can safley say you dont need to be concerned with it?

zrzka

According to Ole's reply in one issue. @on_main_thread is there for objc_util only. Every other module should handle this internally. If not and main thread is required, it's a bug.

See https://github.com/omz/Pythonista-Issues/issues/461 for more info.

mikael

@Brun0oO, both @JonB and @zrzka have replied on this thread, and neither pointed out anything obvious wrong with your code, so we are out of luck. :-) Unless @omz dips in, but I think he is too busy with the next release of Pythonista to spend time on the fun stuff.

Phuket2

@zrzka , ok thanks thats very clear! Just good to know

JonB

Back to the original problem:
per apple docs, you have to add the child view controller first:

[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];

The problem i think is that ui.Views don't have a viewcontroller until they are presented

mikael

@JonB, allright! So, by changing the MyARView __init__ to initialize and only calling it after present:

v = MyARView()
v.present('full_screen', hide_title_bar=True)
v.initialize()

... we can follow the right order, using the "forbidden" nextResponder trick:

self_objc.nextResponder().addChildViewController_(cvc)
self_objc.addSubview_(sceneview)
cvc.didMoveToParentViewController_(self_objc)

... after which CustomViewController_viewWillAppear_ no longer needs to be called manually, i.e. things work as they should.

(Here's a gist with this version of the code.)

But still, debug info does not appear, nor does the cube stay anchored to the surroundings.

mikael

@omz, just checking: for ARKit to really work in Pythonista, would Pythonista need to include the arkit key in plist or somesuch?

zrzka

Nope, the key is not required. See https://developer.apple.com/documentation/arkit (Important section at the beginning). You only need device with >= A9 chip. Use isSupported to check if your device is supported.

mikael

@zrzka, thanks.

Again, just to be sure, I checked the property and got True on my 6S+.

Would be nice if someone could run the gist from the previous post, maybe on a newer device, just to check that the results are similar.

Brun0oO

@mikael and @JonB, many thanks. It's better, now, the custom view controller is correctly "mounted".

I'm investigating the ARSession problem and i'm trying to understand why its configuration stays null after a "runWithConfiguration_" call (I'm going to use some delegate methods in order to spy the initialization process...).
I will update my github ASAP.

Stay tuned...

zrzka

I'm rewriting the Apple demo (candles, ...) into Python(ista). Will share it when finished.

mikael

@zrzka, just curious, did you progress with this? And if not, was it due to some problems or just other more important things taking up your bandwidth?

zrzka

@mikael simple answer, pretty busy these days :/

scj643

If anyone needs a bridge for SceneKit I have https://github.com/scj643/objc_tools/blob/master/objc_tools/scenekit/sk_scene.py

mikael

@scj643, looks good, thanks. As soon as some real programmer gets ARKit working, I am sure to use your code.

momorprods

omg this works well. The cube scene generation example perfectly matches the local world - just set it up at (0,0,0).

mikael

@momorprods, thanks for the heads up. Looks like with the 3.3 beta ARKit now works on Pythonista!

momorprods

hehe yep made my day! Struggling now into loading SceneKit SCN file, did anyone had chance with this?

Brun0oO

Hi all, stay tuned for this topic...

I've made some progress since the 3.3 beta delivery and some new personal investigations watching ARKit headers.

... I'm refactoring my basic code sample...

Brun0oO

Hi,
Finally, I managed to initialize an ar session
but I remain open to any improvement (
especially the famous ARKit constants and the call to the sleep function ;o)

Here the code :

# coding: utf-8
import objc_util

from objc_util import *
import ui
import os
import sys

#from myDebugToolKit import *
import time
from enum import IntFlag


load_framework('SceneKit')
load_framework('ARKit')

# Some 'constants' used by ARkit
# But i can't transfer them to the ARKit framework, why ?????

class ARWorldAlignment(IntFlag):
    ARWorldAlignmentGravity = 0
    ARWorldAlignmentGravityAndHeading = 1
    ARWorldAlignmentCamera = 2

class ARPlaneDetection(IntFlag):
    ARPlaneDetectionNone = 0
    ARPlaneDetectionHorizontal = 1 << 0
    ARPlaneDetectionVertical = 1 << 1

# Work In Progress here, I'm deciphering the ARKit constants...
#class ARSCNDebugOption(IntFlag):
#    ARSCNDebugOptionNone = 0
#    ARSCNDebugOptionShowWorldOrigin = int("ffffffff80000000", 16)
#    ARSCNDebugOptionShowFeaturePoints = int("ffffffff40000000", 16)

class ARSessionRunOptions(IntFlag):
    ARSessionRunOptionsNone                     = 0
    ARSessionRunOptionResetTracking             = 1 << 0
    ARSessionRunOptionRemoveExistingAnchors     = 1 << 1


NSError = ObjCClass('NSError')
SCNScene = ObjCClass('SCNScene')
ARSCNView = ObjCClass('ARSCNView')
ARWorldTrackingConfiguration = ObjCClass('ARWorldTrackingConfiguration')
ARSession = ObjCClass('ARSession')
UIViewController = ObjCClass('UIViewController')
ARPlaneAnchor = ObjCClass('ARPlaneAnchor')




# I should refactor te following line in a class but I need to learn more the create_objcc_class function
sceneview = None

# Here some set up functions used by the main class
def createSampleScene():
    # an empty scene
    scene = SCNScene.scene()
    return scene

def setDebugOptions(arscn):
    # Work In Progress Here, I'm trying to decipher the arkit constants...
    #val = ARSCNDebugOption.ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOption.ARSCNDebugOptionShowFeaturePoints
    val = int("fffffffffc000000", 16) # this value is a combination of ShowWorldOrigin and ShowFeaturePoints flags, but I can't isolate each flags....
    print('Before calling setDebugOptions_(%s) : debugOptions=%s' %(hex(val), hex(arscn.debugOptions())))
    arscn.setDebugOptions_(val)
    print('After calling setDebugOptions_(%s) : debugOptions=%s' % (hex(val),hex(arscn.debugOptions())))


def createARSceneView(x, y, w, h, debug=True):
    v = ARSCNView.alloc().initWithFrame_((CGRect(CGPoint(x, y), CGSize(w, h))))
    v.setShowsStatistics_(debug) # I love statistics...
    return v

# Some callback definitions used by create_objc_class
def CustomViewController_touchesBegan_withEvent_(_self, _cmd, _touches, event):
    touches = ObjCInstance(_touches)
    for t in touches:
        loc = t.locationInView_(sceneview)
        sz = ui.get_screen_size()
        print(loc)

@on_main_thread
def runARSession(arsession):
    arconfiguration = ARWorldTrackingConfiguration.alloc().init()
    arconfiguration.setPlaneDetection_ (ARPlaneDetection.ARPlaneDetectionHorizontal)
    arconfiguration.setWorldAlignment_(ARWorldAlignment.ARWorldAlignmentGravity) # I do not use ARWorldAlignmentGravityAndHeading anymore because on my device, sometimes it fails to initialize the ar session because of an unitialized sensor (error 102). I think my magnetic phone casing plays tricks on me...

    arsession.runWithConfiguration_options_(arconfiguration, ARSessionRunOptions.ARSessionRunOptionResetTracking | ARSessionRunOptions.ARSessionRunOptionRemoveExistingAnchors )

    time.sleep(0.5) # Let the system breathe ;o) Ok, that's the workarround I found to retrieve the ar session configuration (otherwise I got None)....
    print('configuration',arsession.configuration()) # Very usefull for the debuging (at least for me !)


def CustomViewController_viewWillAppear_(_self, _cmd, animated):
    return

def CustomViewController_viewWillDisappear_(_self, _cmd, animated):
    session = sceneview.session()
    session.pause()

def MyARSCNViewDelegate_renderer_didAdd_for_(_self, _cmd, scenerenderer, node, anchor):
    if not isinstance(anchor, (ARPlaneAnchor)):
        return
    # to be implemented...


def MyARSCNViewDelegate_session_didFailWithError_(_self,_cmd,_session,_error):
    print('error',_error,_cmd,_session)
    err_obj=ObjCInstance(_error)
    print(err_obj) # Again, very usefull for the debuging...

# The main class...
class MyARView(ui.View):
    def __init__(self):
        super().__init__(self)


    @on_main_thread
    def initialize(self):
        global sceneview
        self.flex = 'WH'

        screen = ui.get_screen_size()

        # set up the scene
        scene = createSampleScene()

        # set up the ar scene view delegate
        methods = [MyARSCNViewDelegate_renderer_didAdd_for_,MyARSCNViewDelegate_session_didFailWithError_]
        protocols = ['ARSCNViewDelegate']
        MyARSCNViewDelegate = create_objc_class('MyARSCNViewDelegate', NSObject, methods=methods, protocols=protocols)
        delegate = MyARSCNViewDelegate.alloc().init()

        # set up the ar scene view
        sceneview = createARSceneView(0, 0, screen.width, screen.height)
        sceneview.scene = scene
        sceneview.setDelegate_(delegate)


        # set up the custom view controller
        methods = [CustomViewController_touchesBegan_withEvent_, CustomViewController_viewWillAppear_, CustomViewController_viewWillDisappear_]
        protocols = []
        CustomViewController = create_objc_class('CustomViewController', UIViewController, methods=methods, protocols=protocols)
        cvc = CustomViewController.alloc().init()
        cvc.view = sceneview


        # internal scheming...
        self_objc = ObjCInstance(self)
        self_objc.nextResponder().addChildViewController_(cvc)
        self_objc.addSubview_(sceneview)
        cvc.didMoveToParentViewController_(self_objc)

        # here, we try...
        runARSession(sceneview.session()) # I call here this function because I'm trying to find the best place to run the ar session...

        setDebugOptions(sceneview) # I call here this function because I'm trying to find the best place to set the debuging options....

    def will_close(self):
        session = sceneview.session()
        session.pause()



if __name__ == '__main__':
    v = MyARView()
    v.present('full_screen', hide_title_bar=True, orientations=['portrait'])
    v.initialize()

~~Note: if someone can correct the autorotate, I'm also interested !o)~~
Edit: I found a solution for my autorotate problem -> I turn it off ;o)

Live session

pome-ta

hi ! I tried 😊

ShowFeaturePoints
arscn.setDebugOptions_((1 << 30))

ShowWorldOrigin
arscn.setDebugOptions_((1 << 32))

↑ 31 or more?

I don't know why, but I feel like I did it for some reason

thank you!

Pinoy

did you progress with this. if you want to watch pinoy series click on https://lambinganhdreply.su/