欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

程序员文章站 2022-07-06 12:34:08
关于 iOS 的技术解读有很多,但是却鲜有设备可视化同步的介绍文章。本文一起了解下这个酷炫的 iOS 黑科技。 我们的任务很简单——如上图所示,实时获取设备的当前方向。 UIDevice.current.orientation 首先,需要调用 beginGeneratingDeviceOrienta ......

关于 iOS 的技术解读有很多,但是却鲜有设备可视化同步的介绍文章。本文一起了解下这个酷炫的 iOS 黑科技。

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

我们的任务很简单——如上图所示,实时获取设备的当前方向。

UIDevice.current.orientation

首先,需要调用

beginGeneratingDeviceOrientationNotifications() 

但仅仅这样还不行。因为如果设备上的旋转被锁定了,那么就不会产生以上通知。我的相机应用程序从头到尾都需要知道方向——所以我意识到我需要直接根据设备的加速度计算方向。

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

好了,现在有了这些值,我们该做些什么呢?这是一个较难的部分。如果将所有内容都输出到控制台,那么我们很快就会被大量数据淹没。我认为还是在屏幕上显示这些值比较好。

但是,等等,如果将数值显示在图表上,会怎么样?别想图表了,我们可以来用开源的 Blender 试试,它可以实现这些值的可视化,并且很容易扩展。

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

然而 Blender 并不是很好的代码编辑器,所以我们还是使用钟爱的外部编辑器吧。为了调用外部文件,我们可以将 print("hi") 替换成以下代码:

import bpy
import os
filename = os.path.join(os.path.dirname(bpy.data.filepath), "server.py")
exec(compile(open(filename).read(), filename, 'exec'))

下一步,我们需要在与 .blend 文件相同的文件夹中创建新的 server.py 文件,我们真正的代码就要保存在这里。现在我们可以用任何编辑器打开它,你可以选择 Atom、Sublime,甚至 Word 2007 都行。

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

找到该 Cube 对象,点击右键并选择重命名,重命名为 iPhone。现在让我们再来看一看 server.py。

import socket
import select
import json
import threading
import traceback
class ServerThread(threading.Thread):
 def __init__(self):
 threading.Thread.__init__(self)
 self.running = True
 def stopServer(self):
 self.running = False
 self.server.running = False
 def run(self):
 try:
 self.server = Server()
 while self.running:
 self.server.receive()
 except:
 pass
class Server:
 def __init__(self):
 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 self.socket.setblocking(False)
 self.socket.bind((str(socket.INADDR_ANY), 9845))
 self.socket.listen(2)
 self.running = True
 def __exit__(self, exc_type, exc_value, traceback):
 self.socket.close()
 def receive(self):
 pairs = []
 timeout = 1
 while self.running:
 sockets = list(map(lambda x: x[0], pairs))
 if len(pairs) > 0:
 read_sockets, write_sockets, error_sockets = select.select(sockets, [], [], timeout)
 for sock in read_sockets:
 data = sock.recv(4096)
 if not data :
 print('Client disconnected')
 pairs = []
 else :
 self.connectionReceivedData(connection, data.decode())
 try:
 try:
 connection,address = self.socket.accept()
 print("new connection: ", connection)
 pairs.append((connection, address))
 except:
 pass
 except:
 pass
 for pair in pairs:
 (connection, address) = pair
 connection.close()
 def connectionReceivedData(self, connection, data):
 try:
 motionData = json.loads(data)
 except json.decoder.JSONDecodeError:
 print("Invalid JSON: ", data)
 return None
 receivedMotionData(motionData)
# This is a global so when we run the script again, we can keep the server alive
# but change how it works
import bpy
def receivedMotionData(motionData):
 phone = bpy.context.scene.objects["iPhone"]
 phone.rotation_quaternion.x = float(motionData['x'])
 phone.rotation_quaternion.y = 0 - float(motionData['z'])
 phone.rotation_quaternion.z = float(motionData['y'])
 phone.rotation_quaternion.w = float(motionData['w'])
 pass
try:
 if serverThread.running == False:
 serverThread = ServerThread()
 serverThread.start()
 print("Starting server")
 else:
 print("Server already running, using new motion handler.")
except:
 serverThread = ServerThread()
 serverThread.start()
 print("Starting server")
iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

检查 Blender,你应该看到 iPhone 根本没有改变。这是因为上面的脚本使用四元组设置了 iPhone 的旋转角度,并且它使用了欧拉角进行旋转。需要做一些修改。将 Python 控制台面切换到 “Properties”,然后单击该面板顶部的橙色立方体图标。中部 Transform 的下面,点击 XYZ Euler 并选择 Quaternion。现在尝试再次运行 client.py。

你应该看到 iPhone 立即翻转过来了。不要惊慌,这就是我们想要的。现在,我们需要让这个模型跟着实际的 iPhone 旋转。

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

我们需要将运动数据从 iPhone 发送到运行 Blender 的计算机。感谢上苍我们不需要深入到 Swift 中的原始 C 套接字级别,因为 Foundation 具有抽象。

我们可以将以下代码放入新的 iOS 项目中,以替换默认的 ViewController。请确保使用计算机的本地 IP 地址替换 host 变量。

import UIKit
import CoreMotion
class CoreMotionViewController: UIViewController, StreamDelegate {
 let motionManager = CMMotionManager()
 let queue = OperationQueue()
 let host = "192.168.1.2"
 override func viewDidLoad() {
 super.viewDidLoad()
 setUpStreams(host: host)
 motionManager.startDeviceMotionUpdates(to: queue) { (data: CMDeviceMotion?, error: Error?) in
 guard let data = data else {
 print("Error: \(error!)")
 return
 }
 let attitude: CMAttitude = data.attitude
 let quaternion = attitude.quaternion
 var motionData = MotionData()
 motionData.x = quaternion.x
 motionData.y = quaternion.y
 motionData.z = quaternion.z
 motionData.w = quaternion.w
 let encoder = JSONEncoder()
 do {
 let json = try encoder.encode(motionData)
 self.send(data: json)
 } catch let error {
 print("Couldn't send data, error: \(error)")
 }
 }
 }
 // MARK: - Streams
 var inputStream: InputStream?
 var outputStream: OutputStream?
 func setUpStreams(host: String) {
 var readStream: Unmanaged<CFReadStream>?
 var writeStream: Unmanaged<CFWriteStream>?
 CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
 host as CFString, 9845,
 &readStream,
 &writeStream)
 inputStream = readStream!.takeRetainedValue()
 outputStream = writeStream!.takeRetainedValue()
 guard let inputStream = inputStream, let outputStream = outputStream else {
 print("Failed to create streams")
 return
 }
 inputStream.delegate = self
 outputStream.delegate = self
 inputStream.schedule(in: .current, forMode: .commonModes)
 outputStream.schedule(in: .current, forMode: .commonModes)
 inputStream.open()
 outputStream.open()
 }
 func send(data: Data) {
 guard let outputStream = outputStream else {
 return
 }
 _ = data.withUnsafeBytes {
 outputStream.write($0, maxLength: data.count)
 }
 }
 func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
 if eventCode == .errorOccurred {
 inputStream = nil
 outputStream = nil
 print("Error: Stream error")
 } else if eventCode == .endEncountered {
 inputStream = nil
 outputStream = nil
 print("Error: Encountered end of stream")
 }
 let maxReadLength = 4096
 if eventCode == .hasBytesAvailable {
 guard let inputStream = inputStream else {
 return
 }
 while inputStream.hasBytesAvailable {
 let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength)
 inputStream.read(buffer, maxLength: maxReadLength)
 buffer.deallocate()
 }
 }
 }
}
// MARK: - Data Model
private struct MotionData: Codable {
 var x: Double = 0
 var y: Double = 0
 var z: Double = 0
 var w: Double = 0
}
iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

iPhone是卖的最好的手机?用Python照样把他玩弄鼓掌之间!

 

那么最终我是如何从移动管理器获取方向信息的?

欢迎关注我的博客或者公众号:https://home.cnblogs.com/u/Python1234/ Python学习交流

 欢迎加入我的千人交流学习答疑群:125240963