Forum Archive

For the fun, a Photos cube

cvp

For the fun, based on topic
You can pinch for zoom, or move the camera by a finger or close the view with a 2-finger swipe-down gesture
Import 6 Photos, square if possible, in the same directory as your script.

from objc_util import *
import ui
import math

load_framework('SceneKit')

SCNView, SCNScene, SCNBox ,SCNNode, SCNMaterial, SCNCamera, SCNLookAtConstraint = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox' ,'SCNNode', 'SCNMaterial', 'SCNCamera', 'SCNLookAtConstraint' ])

@on_main_thread
def demo():
    main_view = ui.View()
    main_view_objc = ObjCInstance(main_view)
    scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(100, 50)), )None).autorelease()
    scene_view.setAutoresizingMask_(18)
    scene_view.setAllowsCameraControl_(True)
    main_view_objc.addSubview_(scene_view)
    main_view.name = 'SceneKit Demo'

    scene = SCNScene.scene()
    scene_view.setScene_(scene)

    root_node = scene.rootNode()

    camera = SCNCamera.camera()
    camera_node = SCNNode.node()
    camera_node.setCamera(camera)
    camera_node.setPosition((-3.0,3.0, 3))

    cube_geometry = SCNBox.boxWithWidth_height_length_chamferRadius_(1, 1, 1, 0)

    Material_img1 = SCNMaterial.material()
    Material_img1.contents = UIImage.imageWithContentsOfFile_('Photo1.JPG')
    Material_img2 = SCNMaterial.material()
    Material_img2.contents = UIImage.imageWithContentsOfFile_('Photo2.JPG')
    Material_img3 = SCNMaterial.material()
    Material_img3.contents = UIImage.imageWithContentsOfFile_('Photo3.JPG')
    Material_img4 = SCNMaterial.material()
    Material_img4.contents = UIImage.imageWithContentsOfFile_('Photo4.JPG')
    Material_img5 = SCNMaterial.material()
    Material_img5.contents = UIImage.imageWithContentsOfFile_('Photo5.JPG')
    Material_img6 = SCNMaterial.material()
    Material_img6.contents = UIImage.imageWithContentsOfFile_('Photo6.JPG')
    cube_geometry.setMaterials_([Material_img1,Material_img2,Material_img3,Material_img4,Material_img5,Material_img6])

    cube_node = SCNNode.nodeWithGeometry_(cube_geometry)

    # Add a constraint to the camera to keep it pointing to the target cube
    constraint = SCNLookAtConstraint.lookAtConstraintWithTarget_(cube_node)
    constraint.gimbalLockEnabled = True
    camera_node.constraints = [constraint]

    root_node.addChildNode_(camera_node)    
    root_node.addChildNode_(cube_node)

    main_view.present(hide_title_bar=True)

demo()
abcabc

Nice. The following two lines could be added after cubenode definition if someone wants to animate the cube. (Also need to include SCNAction in the list defined after load_framework)

rotate_action = SCNAction.repeatActionForever_(SCNAction.rotateByX_y_z_duration_(0, math.pi*2, math.pi*2, 10))
cube_node.runAction_(rotate_action)
cvp

Thanks

I still try to define a shape like a tetrahedron (4 triangular faces) using SCNGeometrySource but I've problems with array of vertices.
I try to convert this code in Pythonista, I'm sure @JonB could do it in two minutes and I'm fighting against errors for hours.
My dream is to be able, one day in a very far future, to help JonB in something 🙄

SCNVector3 verts[] = { SCNVector3Make(0, 0, 0), SCNVector3Make(1, 0, 0), SCNVector3Make(0, 1, 0) };
SCNGeometrySource *src = [SCNGeometrySource geometrySourceWithVertices:verts count:3];
int indexes[] = { 0, 1, 2 };
NSData *datIndexes = [NSData dataWithBytes:indexes length:sizeof(indexes)];
SCNGeometryElement *ele = [SCNGeometryElement geometryElementWithData:datIndexes primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:1 bytesPerIndex:sizeof(int)];
SCNGeometry *geo = [SCNGeometry geometryWithSources:@[src] elements:@[ele]];
scj643

I actually need people to help make an API for scenekit so we can do this type of stuff with a python AI

cvp

I would like to help but I think I know not enough objective c for that...
When I program in objc, I spend hours before it works.

scj643

https://github.com/scj643/objc_tools is my repo

cvp

I know your repo, often checked...

momorprods

hey, digging out this old topic - did you have chance into the triangle mesh generation?

I have started converting the Obj-c snippet above, but it crashes Pythonista at the geometrySourceWithVertices call. Probably my bad, but I’m clueless about how to fiw this:

```
verts=[SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]

src = SCNGeometrySource.geometrySourceWithVertices_count_(POINTER(verts),3)

cvp

@momorprods No, sorry, I didn't success...

JonB

It wouldn't be POINTER here. Try byref.

JonB

Actually, verts must be a ctypes array, not a python list.

momorprods

oh ok thanks, going to try that and keep you posted!

momorprods

Made this, but still crashing. Am I making another noob mistake?
```
verts=[0.0,0.0,0.0 , 1.0,0.0,0.0 , 0.0,1.0,0.0]
arr = (ctypes.c_float * len(verts))(*verts)
src = SCNGeometrySource.geometrySourceWithVertices_count_(byref(arr),3)

cvp

@momorprods Perhaps this (at least, no crash 😀)

class SCNVector3(Structure):
    _fields_ = [('x', c_double), ('y', c_double), ('z', c_double)]

And

    verts=[SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]
    verts_array = (SCNVector3 * len(verts))(*verts)
    SCNGeometrySource = ObjCClass('SCNGeometrySource').geometrySourceWithVertices_count_(
    verts_array,len(verts),
    restype=c_void_p,
    argtypes=[POINTER(SCNVector3), c_ulong],)
momorprods

wooohoooo thanks!!

momorprods

Making some good progress thanks to your help. But I’m getting a last crash on the very last instruction - any idea?

```

triangle generation

verts=[SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]
verts_array = (SCNVector3 * len(verts))(*verts)

src = ObjCClass('SCNGeometrySource').geometrySourceWithVertices_count_(
verts_array,len(verts),
restype=c_void_p,
argtypes=[POINTER(SCNVector3), c_ulong],)

indexes=[0,1,2]
indexes_array = (c_ulonglen(indexes))(indexes)
datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,len(indexes_array))

ele=ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,SCNGeometryPrimitiveTypeTriangles,1,4)

CRASH Here:

geo = ObjCClass('SCNGeometry').geometryWithSources_elements_(src,ele)

cvp

@momorprods I've read that indices must contain a first element with the number of points but I don't know if it is true.
sources and elements must be arrays
But I have also a crash with my code
See faultlog below but I can't solve the problem, sorry

    verts = [SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]
    verts_array = (SCNVector3 * len(verts))(*verts)
    SCNGeometrySource = ObjCClass('SCNGeometrySource').geometrySourceWithVertices_count_(
    verts_array,len(verts),
    restype=c_void_p,
    argtypes=[POINTER(SCNVector3), c_ulong],)
    sources = NSArray([SCNGeometrySource])

    indices = [3,0,1,2] # number points and their indices
    indices_data = ns(indices)
    # primitiveType = 0 for triangle
    # primitiveCount = 1
    SCNGeometryElement = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(indices_data,0,1,4)
    elements = NSArray([SCNGeometryElement])

    geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_(sources, elements)        

Fatal Python error: Aborted

Current thread 0x0000000100d62b80 (most recent call first):
File "/var/containers/Bundle/Application/E4751F4F-64A4-4BE6-AB9D-9C9564715002/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 773 in call
File "/private/var/mobile/Containers/Shared/AppGroup/668A7D98-7216-47ED-917D-AA0B6173167E/Pythonista3/Documents/test6.py", line 51 in demo
File "/var/containers/Bundle/Application/E4751F4F-64A4-4BE6-AB9D-9C9564715002/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1066 in OMMainThreadDispatcher_invoke_imp

Thread 0x000000016fa5f000 (most recent call first):
File "/var/containers/Bundle/Application/E4751F4F-64A4-4BE6-AB9D-9C9564715002/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 898 in call
File "/var/containers/Bundle/Application/E4751F4F-64A4-4BE6-AB9D-9C9564715002/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1095 in new_func
File "/private/var/mobile/Containers/Shared/AppGroup/668A7D98-7216-47ED-917D-AA0B6173167E/Pythonista3/Documents/test6.py", line 76 in


Objective-C exception details:

NSInvalidArgumentException: -[SCNGeometrySource count]: unrecognized selector sent to instance 0x283276100
```

JonB

this works

from ctypes import *
from objc_util import *

class SCNVector3(Structure):
    _fields_ = [('x', c_double), ('y', c_double), ('z', c_double)]

verts=[SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]

SCNVector3Array3=SCNVector3 * len(verts)
verts_array=SCNVector3Array3(*verts)
SCNGeometrySource = ObjCClass('SCNGeometrySource')

s=SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array),len(verts),restype=c_void_p,argtypes=[POINTER(SCNVector3Array3), c_ulong],)
cvp

@JonB Yes, but in my code,,the crash was in

    geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_(sources, elements)        
cvp

@momorprods @JonB No crash with

```
verts = [SCNVector3(0, 0, 0), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]
SCNVector3Array3=SCNVector3 * len(verts)
verts_array=SCNVector3Array3(*verts)
SCNGeometrySource = ObjCClass('SCNGeometrySource')
s = SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array),len(verts),restype=c_void_p,argtypes=[POINTER(SCNVector3Array3), c_ulong],)

indexes=[0,1,2]
indexes_array = (c_ulong*len(indexes))(*indexes)
datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,len(indexes_array))
# primitiveType = 0 for triangle
# primitiveCount = 1  
e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,0,1,4)

geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],[e])
```

Edit: but nothing visible

cvp

@momorprods I'm not sure if you should take in account the length of c_ulong
Just an idea, rally not sure.
Anyway, Scene stays blank 😢

    #indexes=[0,1,2]
    indexes = [c_ulong(0),c_ulong(1),c_ulong(2)]
    indexes_array = (c_ulong*len(indexes))(*indexes)
    #datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array, len(indexes_array))
    datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array, len(indexes_array)*sizeof(c_ulong))
    # primitiveType = 0 for triangle
    # primitiveCount = 1  
    #e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,0,1,4)
    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,0,1,sizeof(c_ulong))
momorprods

@cvp @JonB thanks for your help, I am going to investigate that empty scene stuff.

JonB

dataWithBytes_length_ expects sizeof(verts_array)

datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,sizeof(indexes_array))

as a check:

geometry.geometryElements()[0].data()
cvp

@JonB Thanks for the correction but obviously not the only error because still blank Scene...

cvp

@momorprods Eureka, same code but with other vertices shows a triangle

One more time, thanks to @JonB without whom we would never solve these problems.

    verts = [SCNVector3(0, 0, 1), SCNVector3(1, 0, 0), SCNVector3(0, 1, 0)]
momorprods

guys you are genius. Not sure why the vertex order did change something, probably it needs to be defined clockwise or something like that.

Thanks for all of your help, you are awesome!

cvp

@momorprods Be careful, indexes need to be 32 bits long, thus use c_int
In a previous post, I said the first index must be the number of points, that's true but for polygons, not for triangles, sorry for that

    indexes = [3,0,1,2]
    indexes_array = (c_int*len(indexes))(*indexes)
    datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,sizeof(indexes_array))
    # primitiveType = 0 for SCNGeometryPrimitiveTypeTriangles
    #                                   4 for SCNGeometryPrimitiveTypePolygon
    # primitiveCount = 1
    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,1,sizeof(c_int))
cvp

@momorprods I don't think we need a particular order for vertices but if the plane of the triangle contains the camera, you don't see the element, try by turning the triangle with one finger

cvp

@momorprods Sorry, you were right about vertices sequence, see here

momorprods

@cvp I think there is still something weird, as the coordinates of the vertices really don’t match what’s expected. The triangle totally disappear if I just scale the Z value of the 1st vertice, which is not normal.

And overall the size of the triangle is not what it should be (confirmed this by overlapping a cube box and also by estimating through the ARKit rendering).

Since it shows the triangle, the indexing part of the code must be ok - it probably deals with the SCNVector3 array. Perhaps I’m wrong, but SCNVector3 looks like an ObjC class, not a C record?

momorprods

ah sorry my bad, In my tests I tweaked badly my SCNVector3 using c_double instead of c_float. Fixed and working now.

cvp

@momorprods This works also:
```
verts = [SCNVector3(0, 1, 0),
SCNVector3(-0.5, 0, 0.5),
SCNVector3(0.5, 0, 0.5),
SCNVector3(0.5, 0, -0.5),
SCNVector3(-0.5, 0, -0.5),
SCNVector3(0, -1, 0)]

SCNVector3Array3 = SCNVector3 * len(verts)
verts_array = SCNVector3Array3(*verts)
SCNGeometrySource = ObjCClass('SCNGeometrySource')
s = SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array), len(verts), restype=c_void_p, argtypes=[POINTER(SCNVector3Array3), c_ulong],)

indexes = [3,3,3,3,3,3,3,3,
0, 1, 2,
2, 3, 0,
3, 4, 0,
4, 1, 0,
1, 5, 2,
2, 5, 3,
3, 5, 4,
4, 5, 1]
indexes_array = (c_int*len(indexes))(*indexes)
datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,sizeof(indexes_array))
# primitiveType = 0 for SCNGeometryPrimitiveTypeTriangles
#                                   4 for SCNGeometryPrimitiveTypePolygon
# primitiveCount = 1
e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,8,sizeof(c_int))

geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],[e])  ```
cvp

For info SCNVector3 is 3 c_float not c_double
Try

from objc_util import *
import ctypes
import ui
import math
from ImageColor import getrgb

load_framework('SceneKit')

SCNView, SCNScene, SCNBox, SCNNode, SCNMaterial, SCNCamera, SCNLight, SCNAction, SCNLookAtConstraint = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNNode', 'SCNMaterial', 'SCNCamera', 'SCNLight', 'SCNAction',  'SCNLookAtConstraint' ])

class SCNVector3(Structure):
    _fields_ = [('x', c_float), ('y', c_float), ('z', c_float)]

@on_main_thread
def demo():
    main_view = ui.View()
    main_view_objc = ObjCInstance(main_view)
    scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(100, 50)), None).autorelease()
    scene_view.setAutoresizingMask_(18)
    scene_view.setAllowsCameraControl_(True)
    main_view_objc.addSubview_(scene_view)
    main_view.name = 'SceneKit Demo'

    scene = SCNScene.scene()
    scene_view.setScene_(scene)

    root_node = scene.rootNode()

    camera = SCNCamera.camera()
    camera_node = SCNNode.node()
    camera_node.setCamera(camera)
    camera_node.setPosition((0,0,5))
    root_node.addChildNode_(camera_node)    
    #https://medium.com/@zxlee618/custom-geometry-in-scenekit-f91464297fd1
    verts = [
    SCNVector3(0, 1, 0),
    SCNVector3(-0.5, 0, 0.5),
    SCNVector3(0.5, 0, 0.5),
    SCNVector3(0.5, 0, -0.5),
    SCNVector3(-0.5, 0, -0.5),
    SCNVector3(0, -1, 0)]

    SCNVector3Array3 = SCNVector3 * len(verts)
    verts_array = SCNVector3Array3(*verts)
    SCNGeometrySource = ObjCClass('SCNGeometrySource')
    s = SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array), len(verts), restype=c_void_p, argtypes=[POINTER(SCNVector3Array3), c_ulong],)

    indexes = [3,3,3,3,3,3,3,3,
    0, 1, 2,
    2, 3, 0,
    3, 4, 0,
    4, 1, 0,
    1, 5, 2,
    2, 5, 3,
    3, 5, 4,
    4, 5, 1]

    indexes_array = (c_int*len(indexes))(*indexes)
    datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,sizeof(indexes_array))
    # primitiveType = 0 for SCNGeometryPrimitiveTypeTriangles
    #                                   4 for SCNGeometryPrimitiveTypePolygon
    # primitiveCount = 1
    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,8,sizeof(c_int))

    geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],[e])  

    #print(geometry.geometryElements()[0].data())   # to check

    Material = SCNMaterial.material()
    Material.contents =             ObjCClass('UIColor').colorWithRed_green_blue_alpha_(1,0.9,0.9,1.0)
    geometry.setMaterials_([Material])

    geometry_node = SCNNode.nodeWithGeometry_(geometry)
    root_node.addChildNode_(geometry_node)

    e2 = []
    Materials = []
    colors = ['red','blue','green','yellow','orange','pink','cyan','orchid']
    for i in range(0,8):
      j = 8 + i*3
      indexes2 = [indexes[j],indexes[j+1],indexes[j+2]]
      indexes_array2 = (c_int*len(indexes2))(*indexes2)
      datIndexes2 = ObjCClass('NSData').dataWithBytes_length_(indexes_array2,sizeof(indexes_array2))
      e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes2,0,1,sizeof(c_int))
      e2.append(e)
      rgb = getrgb(colors[i])
      r,g,b = tuple(c/255.0 for c in rgb)
      Material = SCNMaterial.material()
      Material.contents =           ObjCClass('UIColor').colorWithRed_green_blue_alpha_(r,g,b,1.0)
      Materials.append(Material)
    geometry2 = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],e2)  
    geometry2.setMaterials_(Materials)
    geometry2_node = SCNNode.nodeWithGeometry_(geometry2)
    tx,ty,tz = (-1.5,0,0)
    x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1) # translation
    geometry2_node.setPivot_(x)
    root_node.addChildNode_(geometry2_node)

    # Add a constraint to the camera to keep it pointing to the target geometry
    constraint = SCNLookAtConstraint.lookAtConstraintWithTarget_(geometry_node)
    constraint.gimbalLockEnabled = True
    camera_node.constraints = [constraint]

    light_node = SCNNode.node()
    light_node.setPosition_((100, 0, -10))
    light = SCNLight.light()
    light.setType_('directional')
    light.setCastsShadow_(True)
    light.setColor_(UIColor.whiteColor().CGColor())
    light_node.setLight_(light)
    root_node.addChildNode_(light_node)

    rotate_action = SCNAction.repeatActionForever_(SCNAction.rotateByX_y_z_duration_(0, math.pi*2, 0, 10))
    geometry_node.runAction_(rotate_action)
    geometry2_node.runAction_(rotate_action)

    main_view.present(hide_title_bar=True)

demo()
cvp

momorprods

great job @cvp ! currently working onto loading collada files, might take most of my upcoming oversea flight! I’ll keep you posted.