Skip to content

Commit

Permalink
Merge pull request #72 from joewashear007/pbody
Browse files Browse the repository at this point in the history
Pbody into master
  • Loading branch information
joewashear007 committed Apr 25, 2014
2 parents 4fc342e + b63c51a commit b3e1ea6
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 75 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ This add-on is developed at the University of Toledo as a research project. The
Main Feature List:
------------------------------------------

+ Control the BGE using your smartphone!
+ Everything included in the add-on, use only the standard python modules (website does use CDN so internet connection is required)
+ Create new objects in blender, so it won't modify anything
+ Control the BGE using your smartphone or any web browser!
+ Everything included in the add-on, uses only the standard python modules (website does use CDN so internet connection is required)
+ Create new objects in blender, so it won't modify anything you have created
+ Uses python to create a web host to launch the controlling website
+ The website features button, keyboard, and touch controls
+ The website has a QR code for the connection information to easily share with others
+ Master/Everyone control modes; Switch on will only allow that user to control the model, Off will allow anyone to control it.

+ Custom Buttons can be created to fire your own Actuators
>
How to use
Expand Down Expand Up @@ -54,6 +54,8 @@ These strings are debug properties. They show the current ip:port addresses that

The machine that Blender is running on and the device you are trying to connect with have to be on the same network. Otherwise you would have to set some kind of 3rd party server.

There is a current bug that we are working fixing that the webpage won't load after launching the game engine multiple times. It can be fixed if you simple close and restart blender.

**The website looks like crap**

In order minimize the file size, improve make this script easy to edit, he website uses CDN's to get some JavaScript files. Make sure you have external internet connection so you device can fetch those files.
Expand All @@ -64,9 +66,19 @@ If the website is working fine, but youstill think it look bad from a design poi

This could be possible from a temp not being deleted OR a background process of blender not being closed properly.
For the first one, clean out your temporary directory (on windows: C:\Users\username\Appdata\local\temp, linux: /tmp/)
For the second one, look the the runnig processes to see of blender is running. Close it and try again.
For the second one, look the the running processes to see of blender is running. Close it and try again.

**Can I pick the ports this will use?**

Sure! After running the setup, select the Controller object (It is an empty in blender). There are game properites that deterime the port numbers. Changes these, 0 will auto selct a port.

**Can I make custom buttons/ have it control something else?**

YES! After you run the add-on, there is a script created called "customButtons.py". Copy the fucntion foreach butotn you want, first argument is the button text and the second is the name of the Actuator. You can also create a second row of buttons by calling the function with both arguments as blank strings.
_Make sure to wire the actuator up to the the Server Controller_

> WebSocketHandler.AddCustomButton("ButtonText", "ActuatorName")
**Why are there funny play buttons on the bottom of the website?**

They are for later use. I hope enable animation play back one day and this buttons are a place holder for that. They do nothing.

Expand All @@ -77,7 +89,7 @@ Please submit a GitHub issues!
**License**
This project is licensed under the the MIT license. See included file for more details

Copyright (c) <2014, Joseph Livecchi>
Copyright (c) 2014, Joseph Livecchi



4 changes: 2 additions & 2 deletions WebControllerAddon.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
css_files = ["style.css", "BlenderController.min.css"]
js_files = ["controller.js"]
html_files = ["index.html"]
py_files = ["handler.py", "server_test.py", "startServers.py", "endServers.py", "server.py", "WebControllerAddon.py"]
py_files = ["customButtons.py", "handler.py", "server_test.py", "startServers.py", "endServers.py", "server.py", "WebControllerAddon.py"]

#List of files to move once built
output_files = ["WebControllerAddon.py"]
Expand Down Expand Up @@ -77,7 +77,7 @@ def minify(self):
min_content = self.func_minify(content)
output = open(self.out_dir + file, "w").write(min_content)
# all cap filename and replace dot wioth underscorse to get the replace string name, add double slashes
FileBuilder.replacements["_"+file.replace(".", "_").upper()] = '"""' + min_content.replace("\\n", "\\\\n").replace("\\d", "\\\\d").replace("\\'", "\\\\'") + '"""'
FileBuilder.replacements["_"+file.replace(".", "_").upper()] = '"""' + min_content.replace("\\n", "\\\\n").replace("\\r", "\\\\r").replace("\\d", "\\\\d").replace("\\'", "\\\\'") + '"""'
print("Done!")

def miniy_js(content):
Expand Down
Binary file added examples/Coord_systems.blend
Binary file not shown.
Binary file added examples/Water-Salt.blend
Binary file not shown.
45 changes: 45 additions & 0 deletions misc/copy_data_to_IPO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# This script is used to map the IPO curve of an object as it travels
# through frames of an animation. The algorithm follows:
#
# 1. Calculate number of steps needed to animate at given framerate.
# 2. Get the selected object.
# 3. Duplicate the selected object (which will hold the generated IPO).
# 4. Reset the duplicate object.
# 5. Loop over the number of frames at the calculated framerate.
# 6. Set the active frame.
# 7. Position the duplicate object to the same place as the selected one.
# 8. Copy the selected object's location and rotation into the duplicate's IPO.
#
# Usage:
# (1) Select the object that follows a curve.
# (2) Load this script in Blender's Text window.
# (3) Move the mouse to the Text window.
# (4) Press Alt-p.
#

import bpy


#framesPerSecond = Scene.GetCurrent().getRenderingContext().fps
framesPerSecond = bpy.context.scene.render.fps
firstFrame = bpy.data.scenes['Scene'].frame_start
lastFrame = bpy.data.scenes['Scene'].frame_end
stepsPerFrame = (lastFrame - firstFrame) // framesPerSecond
#stepsPerFrame = D.scenes['Scene'].frame_step

selected = bpy.context.scene.objects.active
bpy.ops.object.duplicate()
duplicate = bpy.context.scene.objects.active
duplicate.parent = None
bpy.ops.object.constraints_clear()
bpy.ops.anim.keyframe_clear_v3d()

for frame in range( firstFrame, lastFrame, stepsPerFrame ):
bpy.data.scenes['Scene'].frame_set(frame)
duplicate.matrix_world = selected.matrix_world
print(duplicate.matrix_world)
bpy.ops.anim.keyframe_insert(type="Location")
bpy.ops.anim.keyframe_insert(type="Rotation")

bpy.data.scenes['Scene'].frame_set(0)
10 changes: 8 additions & 2 deletions src/WebControllerAddon.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def draw(self, context):


class BlenderWebController_op(bpy.types.Operator):
"""Launches a website to contoller a new BGE camera""" # blender will use this as a tooltip for menu items and buttons.
"""Launches a website to control a new BGE camera""" # blender will use this as a tooltip for menu items and buttons.
bl_idname = "game.webcontroller" # unique identifier for buttons and menu items to reference.
bl_label = "Setup Web Controller" # display name in the interface.
bl_options = {'REGISTER', 'UNDO' }
Expand All @@ -46,6 +46,7 @@ def poll(self, context):
def execute(self, context):
bpy.context.scene.render.engine = 'BLENDER_GAME'
bpy.context.scene.game_settings.show_debug_properties = True

#-------------- Create Text Files --------------------------
bpy.ops.text.new()
bpy.data.texts[-1].name = 'StartServer'
Expand All @@ -61,6 +62,10 @@ def execute(self, context):
bpy.data.texts[-1].name = "server.py"
bpy.data.texts["server.py"].use_module = True
bpy.data.texts["server.py"].from_string($_SERVER_PY)
bpy.ops.text.new()
bpy.data.texts[-1].name = "customButtons.py"
bpy.data.texts["customButtons.py"].use_module = True
bpy.data.texts["customButtons.py"].from_string($_CUSTOMBUTTONS_PY)

#-------------- Add empty Controller ------------------------
bpy.ops.object.add(type='EMPTY')
Expand All @@ -70,7 +75,7 @@ def execute(self, context):
bpy.ops.object.game_property_new(type="STRING", name="Website Address")
bpy.ops.object.game_property_new(type="STRING", name="Socket Address")
bpy.data.objects["Controller"].location = (0.0, 0.0, 0.0)
bpy.data.objects['Controller'].game.properties['Website Port'].value = 8000
bpy.data.objects['Controller'].game.properties['Website Port'].value = 0
bpy.data.objects['Controller'].game.properties['Socket Port'].value = 0
bpy.data.objects['Controller'].game.properties['Website Address'].show_debug = True
bpy.data.objects['Controller'].game.properties['Socket Address'].show_debug = True
Expand Down Expand Up @@ -120,6 +125,7 @@ def execute(self, context):
bpy.data.objects["ControllerView"].game.actuators["ZoomIn"].offset_location = (0.0, 0.0, 0.01)
bpy.data.objects["ControllerView"].game.actuators["ZoomOut"].offset_location = (0.0, 0.0, -0.01)
bpy.data.objects['Controller'].game.actuators['SendQuit'].to_property = bpy.data.objects['Controller'].name
bpy.data.objects['Controller'].game.actuators['SendQuit'].subject = "QUIT"

c = bpy.data.objects['Controller'].game.controllers['Sever']
d = bpy.data.objects['Controller'].game.controllers['QuitSever']
Expand Down
9 changes: 9 additions & 0 deletions src/customButtons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /usr/bin/env python3

# Copyright (C) <2014> <Joseph Liveccchi, joewashear007@gmail.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from server import WebSocketHandler
WebSocketHandler.AddCustomButton("Quit", "SendQuit")
2 changes: 2 additions & 0 deletions src/endServers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import bge
import time
if "Server" in bge.logic.globalDict:
bge.logic.globalDict["Server"].stop()
time.sleep(1)
cont = bge.logic.getCurrentController()
cont.activate(cont.actuators["QuitGame"])
10 changes: 3 additions & 7 deletions src/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
class BlenderHandler(server.WebSocketHandler):
def on_message(self, msg):
cont = bge.logic.getCurrentController()
#msg_info = json.loads(msg)
msg_info = msg
if "Actuator" in msg_info:
direction = msg_info["Actuator"]
Expand All @@ -27,10 +26,10 @@ def on_message(self, msg):
rot = cont.actuators[direction].dRot
loc = cont.actuators[direction].dLoc
#Normalize the speed back to 1
print(sum(rot))
#print(sum(rot))
if sum(rot) != 0:
rot = [x/abs(sum(rot)) for x in rot]
print(sum(loc))
#print(sum(loc))
if sum(loc) != 0:
loc = [x/abs(sum(loc)) for x in loc]
#set teh new speed
Expand All @@ -51,7 +50,4 @@ def on_message(self, msg):

def send_message(self, msg):
pass

def on_close(self):
cont = bge.logic.getCurrentController()
cont.activate(cont.actuators["SendQuit"])

49 changes: 32 additions & 17 deletions src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ def log_message(self, format, *args):
# Inherit this class to handle the websocket connection
class WebSocketHandler(socketserver.BaseRequestHandler):

# ---------------- Statis Functions --------------------------------
@classmethod
def AddCustomButton(cls, text, action):
cls._customButtons.append ( {'text':text, 'action':action} )

#-------------- Over ride these ----------------------------------------
def on_message(self, msg):
#msg is a array, decoded from JSON
Expand All @@ -58,10 +63,6 @@ def handle_message(self, msg):
WebSocketHandler.lock_id = None
self.send_json(dict(MASTER_STATUS=False))
self.broadcast_all(dict(SLAVE=False))
#elif "MESSAGE" in msg_data:
# self.on_message(msg["MESSAGE"])
#else:
# print("Unknown CMD, trashing: ", msg_data)
self.on_message(msg_data)
else:
self.send_json(dict(SLAVE=True));
Expand Down Expand Up @@ -89,15 +90,14 @@ def broadcast_all(self, data):
#-------------------------------------------------------------------

magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
_customButtons = []
lock_id = None
connections = []

def _hasLock(self):
#there is no lock or the current thread has it
return (not WebSocketHandler.lock_id) or (WebSocketHandler.lock_id == self.id)



def setup(self):
#Overwrtien function from socketserver
#Init some varibles
Expand All @@ -113,10 +113,10 @@ def handle(self):
#Overwrtien function from socketserver
try:
self.handshake()
except:
except e:
print("HANDSHAKE ERROR! - Try using FireFox")
#return

print(e)
def run(self):
#runs the handler in a thread
while self.alive.isSet():
Expand Down Expand Up @@ -159,15 +159,16 @@ def handshake(self):
raise Exception("Couldn't find the key?:", data)
print('Handshaking... ', end = '')
digest = self._websocketHash(key)
response = 'HTTP/1.1 101 Switching Protocols\n'
response += 'Upgrade: websocket\n'
response += 'Connection: Upgrade\n'
response += 'Sec-WebSocket-Accept: %s\n\n' % digest
response = 'HTTP/1.1 101 Switching Protocols\r\n'
response += 'Upgrade: websocket\r\n'
response += 'Connection: Upgrade\r\n'
response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest
self.handshake_done = self.request.send(response.encode())
print("Sending Connected Message... ", end = '')
if self.handshake_done:
self.send_message("Connected!")
print("Connected!\n")
self.send_json({"BUTTONS": WebSocketHandler._customButtons});
print("Connected!\n")

def _websocketHash(self, key):
result_string = key + self.magic
Expand All @@ -187,7 +188,21 @@ def _get_framehead(self, close=False):
#send the default text frame
frame_head[0] = frame_head[0] | (1 << 0)
return frame_head


def _int_to_bytes(self, number, bytesize):
try:
return bytearray(number.to_bytes(bytesize, byteorder='big'))
except:
# PYTHON2 HACK...
d = bytearray(bytesize)
# Use big endian byteorder
fmt = '!' + {1: 'b', 2: 'H', 4: 'L', 8: 'Q'}[bytesize]
try:
struct.pack_into(fmt, d, 0, number)
except:
raise OverflowError("Need more bytes to represent that number")
return d

def _pack(self, data ,close=False):
#pack bytes for sending to client
frame_head = self._get_framehead(close)
Expand All @@ -198,12 +213,12 @@ def _pack(self, data ,close=False):
# First byte must be set to 126 to indicate the following 2 bytes
# interpreted as a 16-bit unsigned integer are the payload length
frame_head[1] = 126
frame_head += int_to_bytes(len(data), 2)
frame_head += self._int_to_bytes(len(data), 2)
elif len(data) < (2**64) -1:
# Use 8 bytes to encode the data length
# First byte must be set to 127
frame_head[1] = 127
frame_head += int_to_bytes(len(data), 8)
frame_head += self._int_to_bytes(len(data), 8)
frame = frame_head + data.encode('utf-8')
return frame

Expand Down
5 changes: 5 additions & 0 deletions src/server_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@

from server import WebSocketHttpServer
from server import WebSocketHandler
import customButtons

def main():
WebSocketHandler.AddCustomButton("", "")
WebSocketHandler.AddCustomButton("Hello", "Wuit")
WebSocketHandler.AddCustomButton("World", "Huit")
run = True
print()
print()
Expand All @@ -28,6 +32,7 @@ def main():
i = input("Enter Command:")
if i == "q":
server.stop()
print("------------------------------------------")
run = False
else:
if i:
Expand Down
1 change: 1 addition & 0 deletions src/startServers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from server import WebSocketHttpServer
from handler import BlenderHandler
import customButtons
import threading

def main():
Expand Down
Loading

0 comments on commit b3e1ea6

Please sign in to comment.