# interactive animation based on orientation data of IMU browsing

• Hi,

I wanted to ask if they are any examples of how to make up an animation moving based on incoming orientation data that is sent via Bluetooth from a wearable sensor?

I tried to use pygame but it did not work? I checked scene but it looks like it is not the right one.

• posted
0

@ProgrammingGo sorry, not a lot free time now, please, try this

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

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

def demo():
global bt,i_bt
main_view = ui.View()
main_view.name = 'SceneKit IMU'
main_view.background_color = 'white'

b = ui.ButtonItem()
b.title = 'simul bt'

i_bt = 0
bt = [
(pi/2,pi/3,pi/4),
(pi/3,pi/3,pi/4),
(pi/2,pi/5,pi/4),
(pi/2,pi/3,pi/8)
]

def b_action(sender):
global i_bt,bt
# EulerAngles is a SCNVector3
# The order of components in this vector matches the axes of rotation:
# Pitch (the x component) is the rotation about the node’s x-axis.
# Yaw (the y component) is the rotation about the node’s y-axis.
# Roll (the z component) is the rotation about the node’s z-axis.
x = bt[i_bt]
geometry_node.setEulerAngles(x)
i_bt += 1
if i_bt == len(bt):
i_bt = 0

b.action = b_action
main_view.right_button_items = (b,)

main_view_objc = ObjCInstance(main_view)
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(main_view.width,main_view.height)), None).autorelease()
scene_view.setAllowsCameraControl_(True)

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((-30,30,30))

geometry = SCNBox.boxWithWidth_height_length_chamferRadius_(10, 10, 10, 0)

geometry_node = SCNNode.nodeWithGeometry_(geometry)

Materials = []
colors = ['red','blue','green','yellow','orange','pink']
for i in range(0,6):
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)
geometry.setMaterials_(Materials)

# 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_((30, 0, -30))
light = SCNLight.light()
#light.setType_('spot')
light.setType_('probe')
#light.setType_('directional')
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)

main_view.present('fullscreen')

demo()
``````

• posted
0

@cvp this is what you meant? 😉

``````from objc_util import *
import ui
from math import pi

import sceneKit as scn

def demo():
global bt,i_bt
main_view = ui.View()
w, h = ui.get_screen_size()
main_view.frame = (0,0,w,h)
main_view.name = 'SceneKit IMU'
main_view.background_color = 'white'

b = ui.ButtonItem()
b.title = 'simul bt'

i_bt = 0
bt = [
(pi/2,pi/3,pi/4),
(pi/3,pi/3,pi/4),
(pi/2,pi/5,pi/4),
(pi/2,pi/3,pi/8)
]

def b_action(sender):
global i_bt,bt
# EulerAngles is a SCNVector3
# The order of components in this vector matches the axes of rotation:
# Pitch (the x component) is the rotation about the node’s x-axis.
# Yaw (the y component) is the rotation about the node’s y-axis.
# Roll (the z component) is the rotation about the node’s z-axis.
x = bt[i_bt]
geometry_node.setEulerAngles(x)
i_bt += 1
if i_bt == len(bt):
i_bt = 0

b.action = b_action
main_view.right_button_items = (b,)

scene_view = scn.View(main_view.frame, superView=main_view)
scene_view.autoresizingMask = (scn.ViewAutoresizing.FlexibleHeight, scn.ViewAutoresizing.FlexibleRightMargin)

scene_view.allowsCameraControl = True

scene_view.backgroundColor = 'white'

scene = scn.Scene()
scene_view.scene = scene

root_node = scene.rootNode

camera = scn.Camera()
camera_node = scn.Node()
camera_node.camera = camera
camera_node.position = (-30,30,30)

geometry = scn.Box(10, 10, 10, 0)

geometry_node = scn.Node.nodeWithGeometry(geometry)

Materials = []
colors = ['red','blue','green','yellow','orange','pink']
for i in range(0,6):
Material = scn.Material()
Material.contents = colors[i]
Materials.append(Material)
geometry.materials = Materials

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

light_node = scn.Node()
light_node.position = (30, 0, -30)
light = scn.Light()
#light.setType_('spot')
light.type = scn.LightTypeProbe
#light.setType_('directional')
light.color = 'white'
light_node.light = light

main_view.present('fullscreen')

demo()

``````

• posted
0

@pulbrich it seems, yes 😀

• Hi guys, thank you soo much for your support. I need some time to go through the code, because I want to understand it. Thank you soo much and don't worry @cvp it is perfectly okay.

• posted
0

@ProgrammingGo please try this one, automatic (instead of manual) simulation of Bluetooth values and using continuous animation

``````from objc_util import *
import ctypes
import ui
from math import pi
from ImageColor import getrgb
from random import random

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

def __init__(self, geometry_node):
self.name = 'bt'
self.stop = False
self.geometry_node = geometry_node
def run(self):
pitch = 0
yaw   = 0
roll  = 0
delta_ang = pi/10
SCNTransaction = ObjCClass('SCNTransaction').alloc()
while True:
# EulerAngles is a SCNVector3
# The order of components in this vector matches the axes of rotation:
# Pitch (the x component) is the rotation about the node’s x-axis.
# Yaw (the y component) is the rotation about the node’s y-axis.
# Roll (the z component) is the rotation about the node’s z-axis.
pitch += (random() - 0.5) * delta_ang
yaw   += (random() - 0.5) * delta_ang
roll  += (random() - 0.5) * delta_ang
# change euler angles but by an animation
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.3)
self.geometry_node.setEulerAngles((pitch, yaw, roll))
SCNTransaction.commit()
if self.stop:
break

class MyView(ui.View):
def __init__(self,w,h):
self.width = w
self.height = h
self.name = 'SceneKit IMU'
self.background_color = 'white'

main_view_objc = ObjCInstance(self)
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(self.width,self.height)), None).autorelease()
scene_view.setAllowsCameraControl_(True)

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((-30,30,30))

geometry = SCNBox.boxWithWidth_height_length_chamferRadius_(10, 10, 10, 0)

geometry_node = SCNNode.nodeWithGeometry_(geometry)

Materials = []
colors = ['red','blue','green','yellow','orange','pink']
for i in range(0,6):
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)
geometry.setMaterials_(Materials)

# 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_((30, 0, -30))
light = SCNLight.light()
#light.setType_('spot')
light.setType_('probe')
#light.setType_('directional')
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)

def will_close(self):
for t in threading.enumerate():
if t.name == 'bt':
t.stop = True
return

def main():
w, h = ui.get_screen_size()
MainView = MyView(w, h)
MainView.present('fullscreen', hide_title_bar=False)

# Protect against import
if __name__ == '__main__':
main()
``````

• @cvp Hi cvp thank you soo much. I will give a look on that. Thank you for your time. If I will have questions I will write here. I am thinking that it would be better to use quaternions instead of euler angler to avoid gimbal lock. I think there should be a lib for that, right?

• posted
0

@ProgrammingGo said:

I think there should be a lib for that

I suppose but I really don't know anything about that.

• okay, I will give a trial. What for me is unclear is how I can map the sensor values to the graphic. I mean I know that I have a referential coordinate system and based on that my sensor is moving, but it is not clear how to do it.

• posted
0

@ProgrammingGo Euler angles are orientation data of your object (here a cube) versus the 3 axes, thus SceneKit geometry node is easy to orientate.

• posted
0

@cvp you don’t need a separate thread with all the overhead to update the scene objects. There is the scene renderer delegate (can be the same class instance as your main), which has an 'update' method to take care of exactly these type of tasks.

• posted
1

@pulbrich Thanks, the thread was for the Bluetooth listening, here simulated

• posted
0

@ProgrammingGo if you ever need to penetrate the domain of various coordinate system representations (angles included) I recommend you look at Pyrr

• posted
0

What would be cool would be to have two Pythonista scripts.

1. The iPhone script would get the pitch, yaw, and roll of the iPhone and transmit that over Bluetooth.
2. The iPad script could use those Bluetooth signals to change the angel of the airplane on the iPad screen.

• posted
0

@ccc getting the data is easy.
But I don't know anything about the Bluetooth part transmit/receive

• posted
0

• posted
0

@ccc yes, I know and understand but I don't have any experience with it

• posted
2

@ccc, would suggest multipeer for that. Uses bluetooth without the need to think about it.

• posted
0

@ccc said:

What would be cool would be to have two Pythonista scripts.

The iPhone script would get the pitch, yaw, and roll of the iPhone and transmit that over Bluetooth.
The iPad script could use those Bluetooth signals to change the angel of the airplane on the iPad screen.

Pitch, yaw and roll of iDevice + AirPlane, but not Bluetooth, nor WiFi
You can turn the device and pinch/rotate the image

Dirty (more than usual, believe me) and (not so) quick, but for the fun

``````from objc_util import *
import ctypes
import ui
from math import pi
from ImageColor import getrgb
from random import random

SCNView, SCNScene, SCNBox, SCNPyramid, SCNCone, SCNCylinder, SCNSphere, SCNPlane, SCNNode, SCNMaterial, SCNCamera, SCNLight, SCNAction, SCNLookAtConstraint = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNPyramid', 'SCNCone', 'SCNCylinder', 'SCNSphere', 'SCNPlane', 'SCNNode', 'SCNMaterial', 'SCNCamera', 'SCNLight', 'SCNAction',  'SCNLookAtConstraint' ])

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

def __init__(self, geometry_node):
self.name = 'bt'
self.stop = False
self.geometry_node = geometry_node
def run(self):
pitch = 0
yaw   = 0
roll  = 0
delta_ang = pi/10
SCNTransaction = ObjCClass('SCNTransaction').alloc()
# https://forum.omz-software.com/topic/3030
CMMotionManager = ObjCClass('CMMotionManager').alloc().init()
#print(CMMotionManager.isDeviceMotionAvailable())
while True:
# EulerAngles is a SCNVector3
# The order of components in this vector matches the axes of rotation:
# Pitch (the x component) is the rotation about the node’s x-axis.
# Yaw (the y component) is the rotation about the node’s y-axis.
# Roll (the z component) is the rotation about the node’s z-axis.
#pitch += (random() - 0.5) * delta_ang
#yaw   += (random() - 0.5) * delta_ang
#roll  += (random() - 0.5) * delta_ang
gyro_data = CMMotionManager.gyroData()
if not gyro_data:
#print('data not available (yet?)')
continue
# Using the custom struct here:
rate = gyro_data.rotationRate(argtypes=[], restype=CMRotationRate)
# You can now access the struct's fields as x, y, z:
roll  = rate.z
pitch = rate.x
yaw   = rate.y
#print(rate.x, rate.y, rate.z)

# change euler angles but by using animation
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.3)
self.geometry_node.setEulerAngles((pitch, yaw, roll))
SCNTransaction.commit()
if self.stop:
break
CMMotionManager.release()

class MyView(ui.View):
def __init__(self,w,h):
self.width = w
self.height = h
self.name = 'SceneKit IMU'
self.background_color = 'white'

main_view_objc = ObjCInstance(self)
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(self.width,self.height)), None).autorelease()
scene_view.setAllowsCameraControl_(True)
#scene_view.setDebugOptions_(0xFFFF)

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((-35,35,35))

# build an image with text to use as material on wing: begin
l = 40
r = 2
h = 3*15  # 3 x height of text
sc = h / (2*pi*r)
w = l*sc
h = 2*pi*r*sc
with ui.ImageContext(w,h) as ctx:
path = ui.Path.rect(0,0,w,h)
ui.set_color('blue')
path.fill()
x = w/10
y = h/3
t = 'Pytho'
ui.draw_string(t, rect=(x, y, 8*w/10, 2+h/3), font=('<System-Bold>',15), color='white', alignment=ui.ALIGN_LEFT)
x = 6*w/10
t = 'nista'
ui.draw_string(t, rect=(x, y, 8*w/10, 2+h/3), font=('<System-Bold>',15), color='white', alignment=ui.ALIGN_LEFT)
ui_image = ctx.get_image()
#ui_image.show()
Material_pyth = SCNMaterial.material()
Material_pyth.contents = ObjCInstance(ui_image)
# build an image with text to use as material on wing: end

# build an image with text to use as material on cockpit: begin
l = 40
r = 2
h = 3*15  # 3 x height of text
sc = h / (2*pi*r)
w = l*sc
h = 2*pi*r*sc
with ui.ImageContext(w,h) as ctx:
path = ui.Path.rect(0,0,w,h)
ui.set_color('gray')
path.fill()
x = w/2 - 15/2 - 2
y = h/3
path1 = ui.Path.rect(x, y, 20, 2+h/3)
ui.set_color('lightgray')
path1.fill()
t = '👨‍✈️'
ui.draw_string(t, rect=(x, y, 20, 2+h/3), font=('<System-Bold>',15), color='white', alignment=ui.ALIGN_LEFT)
ui_image = ctx.get_image()
#ui_image.show()
Material_nose = SCNMaterial.material()
Material_nose.contents = ObjCInstance(ui_image)
# build an image with text to use as material on cockpit: end

# build an image with text to use as material on vertical stabilizer: begin
w = 22
h = 17
with ui.ImageContext(w,h) as ctx:
path = ui.Path.rect(0,0,w,h)
ui.set_color((0.17, 0.6, 0.0))
path.fill()
x = 8
y = 0
t = 'V'
ui.draw_string(t, rect=(x, y, w, h), font=('<System-Bold>',15), color='white', alignment=ui.ALIGN_LEFT)
ui_image = ctx.get_image()
#ui_image.show()
Material_icon1 = SCNMaterial.material()
Material_icon1.contents = ObjCInstance(ui_image)
# build an image with text to use as material on vertical stabilizer: end

# build an image with text to use as material on vertical stabilizer: begin
w = 22
h = 17
with ui.ImageContext(w,h) as ctx:
path = ui.Path.rect(0,0,w,h)
ui.set_color((0.17, 0.6, 0.0))
path.fill()
x = 3
y = 0
t = 'Λ'
ui.draw_string(t, rect=(x, y, w, h), font=('<System-Bold>',15), color='white', alignment=ui.ALIGN_LEFT)
ui_image = ctx.get_image()
#ui_image.show()
Material_icon2 = SCNMaterial.material()
Material_icon2.contents = ObjCInstance(ui_image)
# build an image with text to use as material on vertical stabilizer: end

lc = l/10
wing = SCNBox.boxWithWidth_height_length_chamferRadius_(20*r, 2*r, 0.3, 0)
vert = SCNBox.boxWithWidth_height_length_chamferRadius_(2*r, r, 0.3, 0)
hori = SCNBox.boxWithWidth_height_length_chamferRadius_(3*r, r*0.6, 0.3, 0)

cone_node = SCNNode.nodeWithGeometry_(cone)
sphe_node = SCNNode.nodeWithGeometry_(sphe)
wing_node = SCNNode.nodeWithGeometry_(wing)
vert_node = SCNNode.nodeWithGeometry_(vert)
hori_node = SCNNode.nodeWithGeometry_(hori)
mot1_node = SCNNode.nodeWithGeometry_(mot1)
mot2_node = SCNNode.nodeWithGeometry_(mot2)

tx,ty,tz = (0,l/2+lc/2,0)
x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1)
cone_node.setPivot_(x)
tx,ty,tz = (0,l/2+lc,0)
x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1)
sphe_node.setPivot_(x)
geometry_node = SCNNode.nodeWithGeometry_(geometry)

# vertical stabilizer: rotation 90° around axe y
tx,ty,tz = (2*r,-l/2+r/2,0)
x = (0,0,1,0, 0,1,0,0, -1,0,0,0, tx,ty,tz,1)
vert_node.setPivot_(x)

tx,ty,tz = (0,-l/2+r*0.4,-r*1.5)
x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1)
hori_node.setPivot_(x)

tx,ty,tz = (5*r,-0.2,0.5)
x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1)
mot1_node.setPivot_(x)

tx,ty,tz = (-5*r,-0.2,0.5)
x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1)
mot2_node.setPivot_(x)

# cylinder horizontal: rotation 90° around axe x
x = (1,0,0,0, 0,0,1,0, 0,-1,0,0, 0,0,0,1)
geometry_node.setPivot_(x)

Materials = []
colors = ['red','gray','gray','yellow','orange','gray','lightgray']
for i in range(0,6):
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)
geometry.setMaterials_(Materials)
cone.setMaterial_(Material_nose)
#cone.setMaterials_(Materials[3:5])
sphe.setMaterials_(Materials[4:5])
hori.setMaterial_(Materials[1])
mot1.setMaterials_(Materials)
mot2.setMaterials_(Materials)
wing_Materials = [Material_pyth,Materials[1],Material_pyth]+[Materials[1]]*3
wing.setMaterials_(wing_Materials)
vert_Materials = [Material_icon2,Materials[1],Material_icon1]+[Materials[1]]*3
vert.setMaterials_(vert_Materials)

# 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_((30, 0, -30))
light = SCNLight.light()
#light.setType_('spot')
light.setType_('probe')
#light.setType_('directional')
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)

def will_close(self):
for t in threading.enumerate():
if t.name == 'bt':
t.stop = True
return

def main():
w, h = ui.get_screen_size()
MainView = MyView(w, h)
MainView.present('fullscreen', hide_title_bar=False)

# Protect against import
if __name__ == '__main__':
main()
``````

• posted
1

@ccc said:

the angel of the airplane

I hope it's its guardian angel 😀 (humor, not mockery)

• posted
0

LOL... I read @mikael ‘s impressive multipeer this morning and then wrote the iPhone piece of the two iOS device idea that I mentioned above. If you add multipeer to your code above, the angle (but perhaps not the angel) of the plane ✈️ on the iPad will react to iPhone movements.
https://github.com/cclauss/Ten-lines-or-less/blob/master/attitude_to_multipeer.py

Internal error.

Oops! Looks like something went wrong!