diff --git a/.gitignore b/.gitignore index 1333ac7..94bc1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -215,6 +215,5 @@ pip-log.txt .mr.developer.cfg #The generated index file -index.html *.blend1 *.blend2 \ No newline at end of file diff --git a/Python/WebControllerAddon.py b/Python/WebControllerAddon.py new file mode 100644 index 0000000..058410d --- /dev/null +++ b/Python/WebControllerAddon.py @@ -0,0 +1,143 @@ +# Copyright (C) <2014> +# 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. + + + +bl_info = { + "name": "Blender Web Controller", + "category": "Game Engine", +} + +import bpy +import math + +class BlenderWebController_pl(bpy.types.Panel): + bl_idname = "game.panel.webcontroller" + bl_label = "Web Controller" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "render" + + def draw(self, context): + layout = self.layout + + obj = context.object + row = layout.row() + row.operator('game.webcontroller') + + + +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. + 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' } + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + + #when to show this add on + @classmethod + def poll(self, context): + return True + + + 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' + bpy.data.texts['StartServer'].from_string($_START_SERVER) + bpy.ops.text.new() + bpy.data.texts[-1].name = "EndServer" + bpy.data.texts["EndServer"].from_string($_END_SERVER) + bpy.ops.text.new() + bpy.data.texts[-1].name = "handler.py" + bpy.data.texts["handler.py"].use_module = True + bpy.data.texts["handler.py"].from_string($_HANDLER) + bpy.ops.text.new() + bpy.data.texts[-1].name = "server.py" + bpy.data.texts["server.py"].use_module = True + bpy.data.texts["server.py"].from_string($_SERVER) + + #-------------- Add empty Controller ------------------------ + bpy.ops.object.add(type='EMPTY') + bpy.context.active_object.name = "Controller" + bpy.ops.object.game_property_new(type="INT", name="Website Port") + bpy.ops.object.game_property_new(type="INT", name="Socket Port") + 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['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 + + bpy.ops.logic.sensor_add( type="DELAY", name="StartServer") + bpy.ops.logic.controller_add( type="PYTHON", name="Sever") + bpy.ops.logic.actuator_add( type="MOTION", name="RotateRight") + bpy.ops.logic.actuator_add( type="MOTION", name="RotateLeft") + bpy.ops.logic.actuator_add( type="MOTION", name="RotateUp") + bpy.ops.logic.actuator_add( type="MOTION", name="RotateDown") + bpy.ops.logic.actuator_add( type="MOTION", name="ZRotateLeft") + bpy.ops.logic.actuator_add( type="MOTION", name="ZRotateRight") + + #------------- Add Camera ---------------- + bpy.ops.object.add(type='CAMERA') + bpy.context.active_object.name = "ControllerView" + bpy.data.objects["ControllerView"].location = (0.0, -5.0, 0.0) + bpy.data.objects["ControllerView"].rotation_euler = (math.radians(90),0.0,0.0) + bpy.data.objects["ControllerView"].parent = bpy.data.objects["Controller"] + bpy.ops.logic.actuator_add(type="MOTION", name="ZoomOut") + bpy.ops.logic.actuator_add(type="MOTION", name="ZoomIn") + con = bpy.data.objects["ControllerView"].constraints.new('DAMPED_TRACK') + con.name = "ViewLock" + con.target = bpy.data.objects["Controller"] + con.track_axis = 'TRACK_NEGATIVE_Z' + bpy.context.scene.camera = bpy.data.objects["ControllerView"] + + #------------------ Add Logic Block Info ----------------------- + bpy.data.objects["Controller"].game.sensors["StartServer"].use_repeat = True + bpy.data.objects['Controller'].game.controllers['Sever'].use_priority = True + bpy.data.objects['Controller'].game.controllers['Sever'].text = bpy.data.texts['StartServer'] + bpy.data.objects["Controller"].game.actuators["RotateRight"].offset_rotation = (0.0, 0.0, math.radians(-1)) + bpy.data.objects["Controller"].game.actuators["RotateLeft"].offset_rotation = (0.0, 0.0, math.radians(1)) + bpy.data.objects["Controller"].game.actuators["RotateUp"].offset_rotation = (math.radians(1), 0.0, 0.0) + bpy.data.objects["Controller"].game.actuators["RotateDown"].offset_rotation = (math.radians(-1), 0.0, 0.0) + bpy.data.objects["Controller"].game.actuators["ZRotateLeft"].offset_rotation = (0.0, math.radians(1), 0.0) + bpy.data.objects["Controller"].game.actuators["ZRotateRight"].offset_rotation = (0.0, math.radians(-1), 0.0) + 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) + + c = bpy.data.objects['Controller'].game.controllers['Sever'] + bpy.data.objects['Controller'].game.sensors['StartServer'].link(c) + bpy.data.objects['Controller'].game.actuators['RotateRight'].link(c) + bpy.data.objects['Controller'].game.actuators['RotateLeft'].link(c) + bpy.data.objects['Controller'].game.actuators['RotateUp'].link(c) + bpy.data.objects['Controller'].game.actuators['RotateDown'].link(c) + bpy.data.objects['Controller'].game.actuators['ZRotateLeft'].link(c) + bpy.data.objects['Controller'].game.actuators['ZRotateRight'].link(c) + bpy.data.objects["ControllerView"].game.actuators["ZoomIn"].link(c) + bpy.data.objects["ControllerView"].game.actuators["ZoomOut"].link(c) + + + bpy.ops.object.select_all(action="DESELECT") + bpy.data.objects['Controller'].select = True + return {'FINISHED'} + +def register(): + bpy.utils.register_class(BlenderWebController_op) + bpy.utils.register_class(BlenderWebController_pl) + + +def unregister(): + bpy.utils.unregister_class(BlenderWebController_op) + bpy.utils.unregister_class(BlenderWebController_pl) + + +# This allows you to run the script directly from blenders text editor +# to test the addon without having to install it. +if __name__ == "__main__": + register() diff --git a/Python/endServers.py b/Python/endServers.py new file mode 100644 index 0000000..f981e80 --- /dev/null +++ b/Python/endServers.py @@ -0,0 +1,5 @@ +import bge +if "Server" in bge.logic.globalDict: + bge.logic.globalDict["Server"].stop() + cont = bge.logic.getCurrentController() + cont.activate(cont.actuators["Quit"]) \ No newline at end of file diff --git a/handler.py b/Python/handler.py similarity index 62% rename from handler.py rename to Python/handler.py index 59e640a..5a853e7 100644 --- a/handler.py +++ b/Python/handler.py @@ -1,13 +1,11 @@ #! /usr/bin/env python3 -# /* - # * ---------------------------------------------------------------------------- - # * "THE BEER-WARE LICENSE" (Revision 42): - # * wrote this file. As long as you retain this notice you - # * can do whatever you want with this stuff. If we meet some day, and you think - # * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - # * ---------------------------------------------------------------------------- - # */ +# Copyright (C) <2014> +# 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. + + import server import bge diff --git a/Python/server.py b/Python/server.py new file mode 100644 index 0000000..6ec7920 --- /dev/null +++ b/Python/server.py @@ -0,0 +1,384 @@ +#! /usr/bin/env python3 + +# Copyright (C) <2014> +# 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. + + + +import tempfile +import http.server +import threading +import os +import socket +import socketserver +import base64 +import hashlib +import struct +import shutil +import webbrowser +import json +from io import StringIO +from string import Template + + +def writeWebsite(file, new_address): + site = Template($_WEBSITE).safe_substitute(address=new_address) + html = open(file ,"w") + html.write(site) + html.close() + + +class QuiteCGIHandler(http.server.CGIHTTPRequestHandler): + def log_message(self, format, *args): + pass #Hides all messages for Request Handler + +# Inherit this class to handle the websocket connection +class WebSocketHandler(socketserver.BaseRequestHandler): + +#-------------- Over ride these ---------------------------------------- + def on_message(self, msg): + #msg is a array, decoded from JSON + #Override this function to handle the input from webcontroller + print(msg) + #self.send_message("Got :" + ast.literal_eval(msg)) + + def handle_message(self, msg): + #only the user with the lock can control + if self._hasLock(): + msg_data = json.loads(msg) + if "MASTER_REQUEST" in msg_data: + if msg_data["MASTER_REQUEST"]: + WebSocketHandler.lock_id = threading.current_thread().ident + self.send_json(dict(MASTER_STATUS=True)); + print("Locking to thread: " ,WebSocketHandler.lock_id, " : ", self.id) + self.broadcast_all(dict(SLAVE=True)) + else: + 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)); + print("Locked, trashing: ", msg) + + def on_close(self): + print("Server: Closing Connection for ", self.client_address) + self.send_message("Server: Closing Connection") + + def send_message(self, message): + print("Sending: ", message) + self.send_json(dict(MESSAGE=message)) + + def send_json(self, data): + #sends a python dict as a json object + self.request.sendall(self._pack(json.dumps(data))) + + def broadcast_all(self, data): + #send a araay converted into JSON to every thread + for t in WebSocketHandler.connections: + if t.id == self.id: + continue + t.send_json(data) + +#------------------------------------------------------------------- + + magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + 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 + print("\nConnection Established", self.client_address) + self.closeHandle = False + self.id = threading.current_thread().ident + self.alive = threading.Event() + self.alive.set() + WebSocketHandler.connections.append(self) + + def handle(self): + #handles the handshake with the server + #Overwrtien function from socketserver + try: + self.handshake() + except: + print("HANDSHAKE ERROR! - Try using FireFox") + #return + + def run(self): + #runs the handler in a thread + while self.alive.isSet(): + msg = self.request.recv(2) + if not msg or self.closeHandle or msg[0] == 136: + print("Received Closed") + break + length = msg[1] & 127 + if length == 126: + length = struct.unpack(">H", self.request.recv(2))[0] + elif length == 127: + length = struct.unpack(">Q", self.request.recv(8))[0] + masks = self.request.recv(4) + decoded = "" + for char in self.request.recv(length): + decoded += chr(char ^ masks[len(decoded) % 4]) + self.handle_message(decoded) + + #WebSocketHandler.broadcast_all.wait(0.01) + + self.close() + + def close(self, message="Cxn Closed"): + self.closeHandle = True + self.request.sendall(self._pack(message, True)) + self.on_close() + + def handshake(self): + key = None + data = self.request.recv(1024).strip() + for line in data.splitlines(): + if b'Upgrade:' in line: + upgrade = line.split(b': ')[1] + if not upgrade == b'websocket': + raise Exception("Upgrade is Not a websocket!", data) + if b'Sec-WebSocket-Key:' in line: + key = line.split(b': ')[1] + break + if key is None: + 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 + self.handshake_done = self.request.send(response.encode()) + print("Sending Connected Message... ", end = '') + if self.handshake_done: + self.send_message("Connected!") + print("Connected!\n") + + def _websocketHash(self, key): + result_string = key + self.magic + sha1_digest = hashlib.sha1(result_string).digest() + response_data = base64.encodestring(sha1_digest).strip() + response_string = response_data.decode('utf8') + return response_string + + def _get_framehead(self, close=False): + #Gets the frame header for sending data, set final fragment & opcode + frame_head = bytearray(2) + frame_head[0] = frame_head[0] | (1 << 7) + if close: + # send the close connection frame + frame_head[0] = frame_head[0] | (8 << 0) + else: + #send the default text frame + frame_head[0] = frame_head[0] | (1 << 0) + return frame_head + + def _pack(self, data ,close=False): + #pack bytes for sending to client + frame_head = self._get_framehead(close) + # payload length + if len(data) < 126: + frame_head[1] = len(data) + elif len(data) < ((2**16) - 1): + # 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) + 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 = frame_head + data.encode('utf-8') + return frame + +class HTTPServer(threading.Thread): + def __init__(self, address_info=('',0)): + threading.Thread.__init__(self) + self.httpd = None + self._start_server( address_info ) + + def _start_server(self, address_info): + #Starts the server at object init + try: + #Using std CGI Handler + self.httpd = http.server.HTTPServer(address_info, QuiteCGIHandler) + print("HTTP Server on : ", self.get_address() ) + except Exception as e: + print("The HTTP server could not be started") + + def get_address(self): + #returns string of the servers address or None + if self.httpd is not None: + return 'http://{host}:{port}/'.format(host=socket.gethostbyname(self.httpd.server_name), port=self.httpd.server_port) + else: + return None + + def run(self): + #Overwrtien from Threading.Thread + if self.httpd is not None : + self.httpd.serve_forever() + else: + print("Error! - HTTP Server is NULL") + + def stop(self): + #Overwrtien from Threading.Thread + print("Killing Http Server ...") + if self.httpd is not None: + self.httpd.shutdown() + print("Done") + +class WebSocketTCPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): + #Added a list of current handlers so they can be closed + def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): + socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate) + self.handlers = [] + self.daemon_threads = True + + def finish_request(self, request, client_address): + #Finish one request by instantiating RequestHandlerClass + print("launching a new request") + t = self.RequestHandlerClass(request, client_address, self) + print("Request:" , t) + self.handlers.append(t) + print("Num request:" ,len(self.handlers)) + t.run() + + # def process_request(self, request, client_address): + # #Start a new thread to process the request + # t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) + # t.daemon = True + # t.start() + + def get_handlers(self): + #returns the list of handlers + return self.handlers + +class WebsocketServer(threading.Thread): + def __init__(self, handler, address_info=('',0)): + threading.Thread.__init__(self) + self.wsd = None + self.handler = handler + self._start_server(address_info) + + def _start_server(self, address_info): + #Starts the server at object init + try: + self.wsd = WebSocketTCPServer( address_info, self.handler) + print( self.get_address()) + except Exception as e: + print("Error! - Websocket Server Not Started!", e) + + def get_address(self): + #returns string of the servers address or None + if self.wsd is not None: + return 'ws://{host}:{port}/'.format(host=socket.gethostbyname(self.wsd.server_name), port=self.wsd.server_port) + else: + return None + + def run(self): + if self.wsd is not None: + self.wsd.serve_forever() + else: + print("The WebSocket Server is NULL") + + def stop(self): + print("Killing WebSocket Server ...") + if self.wsd is not None: + for h in self.wsd.handlers: + h.alive.clear() + + self.wsd.shutdown() + print("Done") + + def get_handlers(self): + #returns the list of handlers + return self.wsd.get_handlers() + + def send(self, msg): + for h in self.wsd.get_handlers(): + h.send_message(msg) + +class WebSocketHttpServer(): + def __init__(self, handler_class = WebSocketHandler, http_address=('',0), ws_address=('',0) ): + self.http_address = http_address + self.ws_address = ws_address + self.handler = handler_class + self.httpServer = None + self.wsServer = None + + def _clean_server_temp_dir(self): + os.chdir(self.cwd) + shutil.rmtree(self.tempdir) + os.rmdir(self.tempdir) + + def _make_server_temp_dir(self): + #make the new temp directory + self.cwd = os.path.dirname(os.path.realpath(__file__)) + self.tempdir = tempfile.mkdtemp() + os.chdir(self.tempdir) + print("New temp dir:", self.tempdir) + + def _make_webpage(self): + writeWebsite(self.tempdir + "/index.html" , self.wsServer.get_address()) + + def stop(self): + try: + self.httpServer.stop() + self.wsServer.stop() + self._clean_server_temp_dir() + except Exception as e: + print("The Servers were never started") + + def start(self): + try: + # make directory and copy files + self._make_server_temp_dir() + self.httpServer = HTTPServer(self.http_address) + self.wsServer = WebsocketServer(self.handler, self.ws_address) + if self.wsServer is not None and self.httpServer is not None: + self.httpServer.start() + self.wsServer.start() + self._make_webpage() + return True + else: + print("Error Starting The Servers, Something is not Initialized!") + return False + except Exception as e: + print() + print("Error!!!, There is some error!") + print(e) + print() + return False + + def send(self, msg): + self.wsServer.send(msg) + + def launch_webpage(self): + #Copies all the resource over to the temp dir + webbrowser.open(self.httpServer.get_address() + "index.html") + + def status(self): + if self.wsServer is None or self.httpServer is None: + return False + else: + return True + +if __name__ == '__main__': + print("No Main Program!") + diff --git a/Python/server_test.py b/Python/server_test.py new file mode 100644 index 0000000..ddd251e --- /dev/null +++ b/Python/server_test.py @@ -0,0 +1,38 @@ +#! /usr/bin/env python3 + +# Copyright (C) <2014> +# 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 WebSocketHttpServer +from server import WebSocketHandler + +def main(): + run = True + print() + print() + print("*** Starting Websocket Server ***") + print() + print("Press Any Key To Quit...") + print() + server = WebSocketHttpServer(WebSocketHandler, http_address=('',8000)) + if server.start(): + print() + server.launch_webpage() + else: + print("Error Starting Server") + while run: + i = input("Enter Command:") + if i == "q": + server.stop() + run = False + else: + if i: + server.send(i) + print("Good Bye") + +if __name__ == '__main__': + main() diff --git a/Python/startServers.py b/Python/startServers.py new file mode 100644 index 0000000..1822715 --- /dev/null +++ b/Python/startServers.py @@ -0,0 +1,47 @@ +#! /usr/bin/env python3 + +# Copyright (C) <2014> +# 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 WebSocketHttpServer +from handler import BlenderHandler +import threading + + +def run_server(): + if "Running" in bge.logic.globalDict: + return + if "Server" in bge.logic.globalDict: + if not bge.logic.globalDict["Server"].status(): + print() + print() + print("*** Starting Websocket Server ***") + print() + print("Press Any Key To Quit...") + print() + if bge.logic.globalDict["Server"].start(): + cont = bge.logic.getCurrentController() + cont.owner["Website Address"] = bge.logic.globalDict["Server"].httpServer.get_address() + cont.owner["Socket Address"] = bge.logic.globalDict["Server"].wsServer.get_address() + bge.logic.globalDict["Server"].launch_webpage() + bge.logic.globalDict["Running"] = True + else: + print("Error Starting Server") + else: + print("Server Not Defined!") + +def main(): + cont = bge.logic.getCurrentController() + scene = bge.logic.getCurrentScene() + scene.active_camera = scene.objects['ControllerView'] + http_addr = ('', cont.owner["Website Port"]) + ws_addr = ('', cont.owner["Socket Port"]) + bge.logic.globalDict["Server"] = WebSocketHttpServer(BlenderHandler, http_address=http_addr, ws_address=ws_addr) + run_server() + +if __name__ == '__main__': + main() diff --git a/README.md b/README.md index 4ce18fd..77593fa 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,14 @@ Short Description This is a [Blender](http://blender.org) add-on that use your smartphone to control the view of model in the Blender Game Engine. -Feature Description +Description --------------------- This Blender add-on allows someone to present a 3D model and to control the camera positioning using a website. The add-on will create a new camera in the Blender scene and all the necessary scripts and logic to control it. It then create server to host the controlling website and a web-socket to communicate with the BGE. The website also also master/everyone locking, so a unique user or everyone can control the model. This add-on is developed at the University of Toledo as a research project. The goal of this project is to make a piece of software that would make it easy for professor to present a 3D model and control it in front of students. The master/everyone control is so the professor can lock control allowing only himself or any one of his students to control the model. -**Main Features:** +Main Feature List: +------------------------------------------ 1. Control the BGE using your smartphone! 2. Everything included in the add-on, use only the standard python modules (website does use CDN so internet connection is required) @@ -25,17 +26,26 @@ This add-on is developed at the University of Toledo as a research project. The How to use ------------------------- - 8. Install and enable the "WebControllerAddon.py" add-on into blender. _Search Google for how to do this_ - 9. Under Render Settings tab, there should now be a "Web Control" panel. Click on the "Setup" button. This will generate all the needed objects, scripts and logic. - 10. Reposition the camera and empty as needed - 11. Click "p" to start the game engine - 12. Use website to control the model +_Note: The add-on "WebControllerAddon.py" is in the built_files folder_ + 1. Install and enable the "WebControllerAddon.py" add-on into blender. _Search Google for how to do this_ + 2. Under Render Settings tab, there should now be a "Web Control" panel. Click on the "Setup" button. This will generate all the needed objects, scripts and logic. + 3. Reposition the camera and empty as needed + 4. Click "p" to start the game engine + 5. Use website to control the model + +Hack or Modify +------------------------------------ +If you want to hack/modify/fix/etc on this script, please feel free to do so. There is a build script that will auto minify and merge the files. This script does need some external python libraries with are listed in the script. FAQ ------ -**Not Connect Banner on website load** +**Can I run this Anaglyph or Stereoscopic 3D modes?** + +Yes! Blender Game Engine has this featuer built in. Under the game settings, there is an option for this. Consult other references if you need help. + +**What are the funny strings of text in the top-right hand corner? Can I remove them?** -There is a nasty bug that is preventing the website from using the correct web-socket address. If you have the console open you will see that Blender has started the web-socket on port #####. Under the connections tab on the website, enter that number into the web-socket input field and click on connect +These strings are debug properties. They show the current ip:port addresses that the script is currently hosting. If you would like to turn them off, under the Game menu, Select "Show Debug properties". (Game menu will only show up if you have the Game Engine Slected as your render type. This can be done with the dark pulldown to the right) **I can't connect to website.** @@ -57,3 +67,6 @@ Please submit a GitHub issues! This project is licensed under the the MIT license. See included file for more details Copyright (c) <2014, Joseph Livecchi> + + + \ No newline at end of file diff --git a/WebControllerAddon.py b/WebControllerAddon.py deleted file mode 100644 index be4c3a4..0000000 --- a/WebControllerAddon.py +++ /dev/null @@ -1,314 +0,0 @@ -bl_info = { - "name": "Blender Web Controller", - "category": "Game Engine", -} - -import bpy -import math - -class BlenderWebController_pl(bpy.types.Panel): - bl_idname = "game.panel.webcontroller" - bl_label = "Web Controller" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "render" - - def draw(self, context): - layout = self.layout - - obj = context.object - row = layout.row() - row.operator('game.webcontroller') - - - -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. - 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' } - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - - #when to show this add on - @classmethod - def poll(self, context): - return True - - - def execute(self, context): - bpy.context.scene.render.engine = 'BLENDER_GAME' - #-------------- Create Text Files -------------------------- - bpy.ops.text.new() - bpy.data.texts[-1].name = "Read Me" - bpy.data.texts["Read Me"].write("Hello World") - print("Created the Read me file") - - bpy.ops.text.new() - bpy.data.texts[-1].name = 'StartServer' - bpy.data.texts['StartServer'].from_string(""" -from server import WebSocketHttpServer -from handler import BlenderHandler -import threading -def run_server(): - if 'Running' in bge.logic.globalDict:return - if 'Server' in bge.logic.globalDict: - if not bge.logic.globalDict['Server'].status(): - print();print();print('*** Starting Websocket Server ***');print();print('Press Any Key To Quit...');print() - if bge.logic.globalDict['Server'].start():print();bge.logic.globalDict['Server'].launch_webpage();bge.logic.globalDict['Running']=True - else:print('Error Starting Server') - else:print('Server Not Defined!') -def main():cont=bge.logic.getCurrentController();scene=bge.logic.getCurrentScene();scene.active_camera=scene.objects['ControllerView'];bge.logic.globalDict['Server']=WebSocketHttpServer(BlenderHandler,http_address=('',8000));run_server() -if __name__=='__main__':main() -""") - bpy.ops.text.new() - bpy.data.texts[-1].name = "EndServer" - bpy.data.texts["EndServer"].from_string(""" -import bge -if "Server" in bge.logic.globalDict: - bge.logic.globalDict["Server"].stop() - cont = bge.logic.getCurrentController() - cont.activate(cont.actuators["Quit"]) -""") - bpy.ops.text.new() - bpy.data.texts[-1].name = "handler.py" - bpy.data.texts["handler.py"].use_module = True - bpy.data.texts["handler.py"].from_string(""" -import server -import bge -import json -from time import sleep -class BlenderHandler(server.WebSocketHandler): - def on_message(self,msg): - cont=bge.logic.getCurrentController();msg_info=msg - if 'Actuator' in msg_info: - direction=msg_info['Actuator'];print('Moving:',direction) - if 'Speed' in msg_info: - speed=msg_info['Speed'];print('Speed: ',speed);rot=cont.actuators[direction].dRot;loc=cont.actuators[direction].dLoc;print(sum(rot)) - if sum(rot)!=0:rot=[x/abs(sum(rot)) for x in rot] - print(sum(loc)) - if sum(loc)!=0:loc=[x/abs(sum(loc)) for x in loc] - cont.actuators[direction].dLoc=[x*speed for x in loc];cont.actuators[direction].dRot=[x*speed for x in rot] - cont.activate(cont.actuators[direction]) - if 'Stop' in msg_info: - for act in cont.actuators:cont.deactivate(act) - if 'Reset' in msg_info: - try:cont.owner.worldOrientation=[[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]];scene=bge.logic.getCurrentScene();scene.objects['ControllerView'].localPosition=0.,-2.5,0. - except e:print('Error: '+e) - def send_message(self,msg):pass - def on_close(self):cont=bge.logic.getCurrentController();cont.activate(cont.actuators['QuitMsg']) - - """) - bpy.ops.text.new() - bpy.data.texts[-1].name = "server.py" - bpy.data.texts["server.py"].use_module = True - bpy.data.texts["server.py"].from_string(""" - -import tempfile -import http.server -import threading -import os -import socket -import socketserver -import base64 -import hashlib -import struct -import shutil -import webbrowser -import json -from io import StringIO -from string import Template -def writeWebsite(file,new_address):site_part1=' BlenderWebController
 

Button Control

Close

Adjust Movement Sensitivity

 

Swipe Control

Not Connected

Swipe Controls

  • TAP: Stop
  • SWIPE: Rotate
  • PINCH: Zoom
  • ROTATE: Rotate Z Axis
Close

Adjust Movement Sensitivity

 

Connection Settings

 

Not Connected

Http & Websocket Address

Close

Scan to connect

 

Swipe Control

Not Connected

Received Messages

    ';site_part2=Template(' ';html=open(file,'w');html.write(site_part1);html.write(site_part2);html.write(site_part3);html.close() -class QuiteCGIHandler(http.server.CGIHTTPRequestHandler): - def log_message(self,format,*args):pass -class WebSocketHandler(socketserver.BaseRequestHandler): - def on_message(self,msg):print(msg) - def handle_message(self,msg): - if self._hasLock(): - msg_data=json.loads(msg) - if 'MASTER_REQUEST' in msg_data: - if msg_data['MASTER_REQUEST']:WebSocketHandler.lock_id=threading.current_thread().ident;self.send_json(dict(MASTER_STATUS=True));print('Locking to thread: ',WebSocketHandler.lock_id,' : ',self.id);self.broadcast_all(dict(SLAVE=True)) - else:WebSocketHandler.lock_id=None;self.send_json(dict(MASTER_STATUS=False));self.broadcast_all(dict(SLAVE=False)) - self.on_message(msg_data) - else:self.send_json(dict(SLAVE=True));print('Locked, trashing: ',msg) - def on_close(self):print('Server: Closing Connection for ',self.client_address);self.send_message('Server: Closing Connection') - def send_message(self,message):print('Sending: ',message);self.send_json(dict(MESSAGE=message)) - def send_json(self,data):self.request.sendall(self._pack(json.dumps(data))) - def broadcast_all(self,data): - for t in WebSocketHandler.connections: - if t.id==self.id:continue - t.send_json(data) - magic=b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';lock_id=None;connections=[] - def _hasLock(self):return not WebSocketHandler.lock_id or WebSocketHandler.lock_id==self.id - def setup(self):print('\\nConnection Established',self.client_address);self.closeHandle=False;self.id=threading.current_thread().ident;self.alive=threading.Event();self.alive.set();WebSocketHandler.connections.append(self) - def handle(self): - try:self.handshake() - except:print('HANDSHAKE ERROR! - Try using FireFox') - def run(self): - while self.alive.isSet(): - msg=self.request.recv(2) - if not msg or self.closeHandle or msg[0]==136:print('Received Closed');break - length=msg[1]&127 - if length==126:length=struct.unpack('>H',self.request.recv(2))[0] - elif length==127:length=struct.unpack('>Q',self.request.recv(8))[0] - masks=self.request.recv(4);decoded='' - for char in self.request.recv(length):decoded+=chr(char^masks[len(decoded)%4]) - self.handle_message(decoded) - self.close() - def close(self,message='Cxn Closed'):self.closeHandle=True;self.request.sendall(self._pack(message,True));self.on_close() - def handshake(self): - key=None;data=self.request.recv(1024).strip() - for line in data.splitlines(): - if b'Upgrade:' in line: - upgrade=line.split(b': ')[1] - if not upgrade==b'websocket':raise Exception('Upgrade is Not a websocket!',data) - if b'Sec-WebSocket-Key:' in line:key=line.split(b': ')[1];break - if key is None:raise Exception("Couldn't find the key?:",data) - print('Handshaking... ',end='');digest=self._websocketHash(key);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') - def _websocketHash(self,key):result_string=key+self.magic;sha1_digest=hashlib.sha1(result_string).digest();response_data=base64.encodestring(sha1_digest).strip();response_string=response_data.decode('utf8');return response_string - def _get_framehead(self,close=False): - frame_head=bytearray(2);frame_head[0]=frame_head[0]|1<<7 - if close:frame_head[0]=frame_head[0]|8<<0 - else:frame_head[0]=frame_head[0]|1<<0 - return frame_head - def _pack(self,data,close=False): - frame_head=self._get_framehead(close) - if len(data)<126:frame_head[1]=len(data) - elif len(data)<2**16-1:frame_head[1]=126;frame_head+=int_to_bytes(len(data),2) - elif len(data)<2**64-1:frame_head[1]=127;frame_head+=int_to_bytes(len(data),8) - frame=frame_head+data.encode('utf-8');return frame -class HTTPServer(threading.Thread): - def __init__(self,address_info=('',0)):threading.Thread.__init__(self);self.httpd=None;self._start_server(address_info) - def _start_server(self,address_info): - try:self.httpd=http.server.HTTPServer(address_info,QuiteCGIHandler);print('HTTP Server on : ',self.get_address()) - except Exception as e:print('The HTTP server could not be started') - def get_address(self): - if self.httpd is not None:return 'http://{host}:{port}/'.format(host=socket.gethostbyname(self.httpd.server_name),port=self.httpd.server_port) - else:return None - def run(self): - if self.httpd is not None:self.httpd.serve_forever() - else:print('Error! - HTTP Server is NULL') - def stop(self): - print('Killing Http Server ...') - if self.httpd is not None:self.httpd.shutdown() - print('Done') -class WebSocketTCPServer(socketserver.ThreadingMixIn,http.server.HTTPServer): - def __init__(self,server_address,RequestHandlerClass,bind_and_activate=True):socketserver.TCPServer.__init__(self,server_address,RequestHandlerClass,bind_and_activate);self.handlers=[];self.daemon_threads=True - def finish_request(self,request,client_address):print('launching a new request');t=self.RequestHandlerClass(request,client_address,self);print('Request:',t);self.handlers.append(t);print('Num request:',len(self.handlers));t.run() - def get_handlers(self):return self.handlers -class WebsocketServer(threading.Thread): - def __init__(self,handler,address_info=('',0)):threading.Thread.__init__(self);self.wsd=None;self.handler=handler;self._start_server(address_info) - def _start_server(self,address_info): - try:self.wsd=WebSocketTCPServer(address_info,self.handler);print(self.get_address()) - except Exception as e:print('Error! - Websocket Server Not Started!',e) - def get_address(self): - if self.wsd is not None:return 'ws://{host}:{port}/'.format(host=socket.gethostbyname(self.wsd.server_name),port=self.wsd.server_port) - else:return None - def run(self): - if self.wsd is not None:self.wsd.serve_forever() - else:print('The WebSocket Server is NULL') - def stop(self): - print('Killing WebSocket Server ...') - if self.wsd is not None: - for h in self.wsd.handlers:h.alive.clear() - self.wsd.shutdown() - print('Done') - def get_handlers(self):return self.wsd.get_handlers() - def send(self,msg): - for h in self.wsd.get_handlers():h.send_message(msg) -class WebSocketHttpServer: - def __init__(self,handler_class=WebSocketHandler,http_address=('',0),ws_address=('',0)):self.http_address=http_address;self.ws_address=ws_address;self.handler=handler_class;self.httpServer=None;self.wsServer=None - def _clean_server_temp_dir(self):os.chdir(self.cwd);shutil.rmtree(self.tempdir);os.rmdir(self.tempdir) - def _make_server_temp_dir(self):self.cwd=os.path.dirname(os.path.realpath(__file__));self.tempdir=tempfile.mkdtemp();os.chdir(self.tempdir);print('New temp dir:',self.tempdir) - def _make_webpage(self):writeWebsite(self.tempdir+'/index.html',self.wsServer.get_address()) - def stop(self): - try:self.httpServer.stop();self.wsServer.stop();self._clean_server_temp_dir() - except Exception as e:print('The Servers were never started') - def start(self): - try: - self._make_server_temp_dir();self.httpServer=HTTPServer(self.http_address);self.wsServer=WebsocketServer(self.handler,self.ws_address) - if self.wsServer is not None and self.httpServer is not None:self.httpServer.start();self.wsServer.start();self._make_webpage();return True - else:print('Error Starting The Servers, Something is not Initialized!');return False - except Exception as e:print();print('Error!!!, There is some error!');print(e);print();return False - def send(self,msg):self.wsServer.send(msg) - def launch_webpage(self):webbrowser.open(self.httpServer.get_address()+'index.html') - def status(self): - if self.wsServer is None or self.httpServer is None:return False - else:return True -if __name__=='__main__':print('No Main Program!')""") - - - print("Done Creating Files") - for x in bpy.data.texts: - print(x.name) - - #-------------- Add empty Controller ------------------------ - bpy.ops.object.add(type='EMPTY') - bpy.context.active_object.name = "Controller" - bpy.data.objects["Controller"].location = (0.0, 0.0, 0.0) - - bpy.ops.logic.sensor_add( type="DELAY", name="StartServer") - bpy.ops.logic.controller_add( type="PYTHON", name="Sever") - bpy.ops.logic.actuator_add( type="MOTION", name="RotateRight") - bpy.ops.logic.actuator_add( type="MOTION", name="RotateLeft") - bpy.ops.logic.actuator_add( type="MOTION", name="RotateUp") - bpy.ops.logic.actuator_add( type="MOTION", name="RotateDown") - bpy.ops.logic.actuator_add( type="MOTION", name="ZRotateLeft") - bpy.ops.logic.actuator_add( type="MOTION", name="ZRotateRight") - - #------------- Add Camera ---------------- - bpy.ops.object.add(type='CAMERA') - bpy.context.active_object.name = "ControllerView" - bpy.data.objects["ControllerView"].location = (0.0, -5.0, 0.0) - bpy.data.objects["ControllerView"].rotation_euler = (math.radians(90),0.0,0.0) - bpy.data.objects["ControllerView"].parent = bpy.data.objects["Controller"] - bpy.ops.logic.actuator_add(type="MOTION", name="ZoomOut") - bpy.ops.logic.actuator_add(type="MOTION", name="ZoomIn") - con = bpy.data.objects["ControllerView"].constraints.new('DAMPED_TRACK') - con.name = "ViewLock" - con.target = bpy.data.objects["Controller"] - con.track_axis = 'TRACK_NEGATIVE_Z' - bpy.context.scene.camera = bpy.data.objects["ControllerView"] - - #------------------ Add Logic Block Info ----------------------- - bpy.data.objects["Controller"].game.sensors["StartServer"].use_repeat = True - bpy.data.objects['Controller'].game.controllers['Sever'].use_priority = True - bpy.data.objects['Controller'].game.controllers['Sever'].text = bpy.data.texts['StartServer'] - bpy.data.objects["Controller"].game.actuators["RotateRight"].offset_rotation = (0.0, 0.0, math.radians(-1)) - bpy.data.objects["Controller"].game.actuators["RotateLeft"].offset_rotation = (0.0, 0.0, math.radians(1)) - bpy.data.objects["Controller"].game.actuators["RotateUp"].offset_rotation = (math.radians(1), 0.0, 0.0) - bpy.data.objects["Controller"].game.actuators["RotateDown"].offset_rotation = (math.radians(-1), 0.0, 0.0) - bpy.data.objects["Controller"].game.actuators["ZRotateLeft"].offset_rotation = (0.0, math.radians(1), 0.0) - bpy.data.objects["Controller"].game.actuators["ZRotateRight"].offset_rotation = (0.0, math.radians(-1), 0.0) - 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) - - c = bpy.data.objects['Controller'].game.controllers['Sever'] - bpy.data.objects['Controller'].game.sensors['StartServer'].link(c) - bpy.data.objects['Controller'].game.actuators['RotateRight'].link(c) - bpy.data.objects['Controller'].game.actuators['RotateLeft'].link(c) - bpy.data.objects['Controller'].game.actuators['RotateUp'].link(c) - bpy.data.objects['Controller'].game.actuators['RotateDown'].link(c) - bpy.data.objects['Controller'].game.actuators['ZRotateLeft'].link(c) - bpy.data.objects['Controller'].game.actuators['ZRotateRight'].link(c) - bpy.data.objects["ControllerView"].game.actuators["ZoomIn"].link(c) - bpy.data.objects["ControllerView"].game.actuators["ZoomOut"].link(c) - - return {'FINISHED'} - -def register(): - bpy.utils.register_class(BlenderWebController_op) - bpy.utils.register_class(BlenderWebController_pl) - - -def unregister(): - bpy.utils.unregister_class(BlenderWebController_op) - bpy.utils.unregister_class(BlenderWebController_pl) - - -# This allows you to run the script directly from blenders text editor -# to test the addon without having to install it. -if __name__ == "__main__": - register() diff --git a/build.py b/build.py new file mode 100644 index 0000000..6d8059a --- /dev/null +++ b/build.py @@ -0,0 +1,146 @@ +#! /usr/bin/env python3 + +# Copyright (C) <2014> +# 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. + + + +#needed libraries +# https://github.com/mankyd/htmlmin +# https://github.com/brettcannon/mnfy +# https://github.com/zacharyvoase/cssmin +# https://github.com/rspivak/slimit + +import slimit +import cssmin +import htmlmin +import mnfy +import ast +import os +import shutil +from html.parser import HTMLParser +from string import Template + +#output directory +output = "built_files/" +temp = "tmp/" +output_files = ["WebControllerAddon.py"] +#List of ile to be minified +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"] +py_files_with_web_subs = ["server.py"] +py_files_with_py_subs = ["WebControllerAddon.py"] + + + +#Replacements in python files +web_replacements = {"_WEBSITE": "index.html"} +py_replacements = {"_START_SERVER":"startServers.py", "_END_SERVER":"endServers.py", "_HANDLER":"handler.py", "_SERVER":"server.py"} + + +class HTMLBuilder(HTMLParser): + def __init__(self): + super( HTMLBuilder, self ).__init__() + self._replace = "" + + def handle_starttag(self, tag, attrs): + self._replace = None + for attr in attrs: + key, value = attr + if tag == "link" and "href" in key: + if value in css_files: + self._replace = "style", value + if tag == "script" and "src" in key: + if value in js_files: + self._replace = "script", value + + def replacement(self): + #Returns what should be replaced + if self._replace: + tag, file = self._replace + return "<"+tag+">" + open(temp + file).read() +"" + else: + return None + + + +def main(): + # Makes the output dir + if os.path.exists(output): + shutil.rmtree(output) + os.makedirs(output) + + if os.path.exists(temp): + shutil.rmtree(temp) + os.makedirs(temp) + + #minify the CSS files + for file in css_files: + with open(temp + file, "w") as f: + f.write( cssmin.cssmin(open("web/" + file).read()) ) + #Minify the Javascript files + for file in js_files: + with open( temp + file, "w") as f: + f.write( slimit.minify(open("web/"+file).read(), mangle=True, mangle_toplevel=True) ) + #Adds the minify CSS & JS file in HTML, minfy the HTML + for file in html_files: + content = open("web/"+ file).readlines() + new_content = "" + p = HTMLBuilder() + for line in content: + line = line.strip() + if line : + p.feed(line) + replacement = p.replacement() + if replacement: + line = replacement + new_content += line + f = open(temp + file, "w") + f.write( htmlmin.minify(new_content, remove_comments=True, remove_empty_space=True, )) + + #Preforms minify python files + for file in py_files: + content = open("python/" + file).read() + minifier = mnfy.SourceCode() + minifier.visit(ast.parse(content)) + with open(temp + file, "w") as f: + f.write( str(minifier) ) + + #Build the website subsitute dictionary + subs_web= dict() + for i in web_replacements: + subs_web[i] = '"""' + open(temp + web_replacements[i]).read() + '"""' + #Preforms Website Subsitutes on Pythons files, Minify them + for file in py_files_with_web_subs: + content = open("python/" + file).read() + new_content = Template(content).safe_substitute(subs_web) + minifier = mnfy.SourceCode() + minifier.visit(ast.parse(new_content)) + with open(temp + file, "w") as f: + f.write( str(minifier) ) + + #Build subsitute dictionary + subs_py = dict() + for i in py_replacements: + s = open(temp + py_replacements[i] ).read() + s = s.replace("\\n", "\\\\n").replace("\\d", "\\\\d").replace("\\'", "\\\\'") + subs_py[i] = """'''""" + s + """'''""" + #Preforms Subsitutes on Pythons files, Minify them + for file in py_files_with_py_subs: + content = open("python/" + file).read() + new_content = Template(content).safe_substitute(subs_py) + minifier = mnfy.SourceCode() + minifier.visit(ast.parse(new_content)) + with open(temp + file, "w") as f: + f.write( str(minifier) ) + + #Copy the final output + for i in output_files: + shutil.copyfile(temp + i, output + i) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/built_files/WebControllerAddon.py b/built_files/WebControllerAddon.py new file mode 100644 index 0000000..91da797 --- /dev/null +++ b/built_files/WebControllerAddon.py @@ -0,0 +1,14 @@ +bl_info={'name':'Blender Web Controller','category':'Game Engine'} +import bpy +import math +class BlenderWebController_pl(bpy.types.Panel): + bl_idname='game.panel.webcontroller';bl_label='Web Controller';bl_space_type='PROPERTIES';bl_region_type='WINDOW';bl_context='render' + def draw(self,context):layout=self.layout;obj=context.object;row=layout.row();row.operator('game.webcontroller') +class BlenderWebController_op(bpy.types.Operator): + 'Launches a website to contoller a new BGE camera';bl_idname='game.webcontroller';bl_label='Setup Web Controller';bl_options={'REGISTER','UNDO'};bl_space_type='PROPERTIES';bl_region_type='WINDOW' + @classmethod + def poll(self,context):return True + def execute(self,context):bpy.context.scene.render.engine='BLENDER_GAME';bpy.context.scene.game_settings.show_debug_properties=True;bpy.ops.text.new();bpy.data.texts[-1].name='StartServer';bpy.data.texts['StartServer'].from_string("from server import WebSocketHttpServer\nfrom handler import BlenderHandler\nimport threading\ndef run_server():\n if 'Running' in bge.logic.globalDict:return\n if 'Server' in bge.logic.globalDict:\n if not bge.logic.globalDict['Server'].status():\n print();print();print('*** Starting Websocket Server ***');print();print('Press Any Key To Quit...');print()\n if bge.logic.globalDict['Server'].start():cont=bge.logic.getCurrentController();cont.owner['Website Address']=bge.logic.globalDict['Server'].httpServer.get_address();cont.owner['Socket Address']=bge.logic.globalDict['Server'].wsServer.get_address();bge.logic.globalDict['Server'].launch_webpage();bge.logic.globalDict['Running']=True\n else:print('Error Starting Server')\n else:print('Server Not Defined!')\ndef main():cont=bge.logic.getCurrentController();scene=bge.logic.getCurrentScene();scene.active_camera=scene.objects['ControllerView'];http_addr='',cont.owner['Website Port'];ws_addr='',cont.owner['Socket Port'];bge.logic.globalDict['Server']=WebSocketHttpServer(BlenderHandler,http_address=http_addr,ws_address=ws_addr);run_server()\nif __name__=='__main__':main()");bpy.ops.text.new();bpy.data.texts[-1].name='EndServer';bpy.data.texts['EndServer'].from_string("import bge\nif 'Server' in bge.logic.globalDict:bge.logic.globalDict['Server'].stop();cont=bge.logic.getCurrentController();cont.activate(cont.actuators['Quit'])");bpy.ops.text.new();bpy.data.texts[-1].name='handler.py';bpy.data.texts['handler.py'].use_module=True;bpy.data.texts['handler.py'].from_string("import server\nimport bge\nimport json\nfrom time import sleep\nclass BlenderHandler(server.WebSocketHandler):\n def on_message(self,msg):\n cont=bge.logic.getCurrentController();msg_info=msg\n if 'Actuator' in msg_info:\n direction=msg_info['Actuator'];print('Moving:',direction)\n if 'Speed' in msg_info:\n speed=msg_info['Speed'];print('Speed: ',speed);rot=cont.actuators[direction].dRot;loc=cont.actuators[direction].dLoc;print(sum(rot))\n if sum(rot)!=0:rot=[x/abs(sum(rot)) for x in rot]\n print(sum(loc))\n if sum(loc)!=0:loc=[x/abs(sum(loc)) for x in loc]\n cont.actuators[direction].dLoc=[x*speed for x in loc];cont.actuators[direction].dRot=[x*speed for x in rot]\n cont.activate(cont.actuators[direction])\n if 'Stop' in msg_info:\n for act in cont.actuators:cont.deactivate(act)\n if 'Reset' in msg_info:\n try:cont.owner.worldOrientation=[[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]];scene=bge.logic.getCurrentScene();scene.objects['ControllerView'].localPosition=0.,-2.5,0.\n except e:print('Error: '+e)\n def send_message(self,msg):pass\n def on_close(self):cont=bge.logic.getCurrentController();cont.activate(cont.actuators['QuitMsg'])");bpy.ops.text.new();bpy.data.texts[-1].name='server.py';bpy.data.texts['server.py'].use_module=True;bpy.data.texts['server.py'].from_string('import tempfile\nimport http.server\nimport threading\nimport os\nimport socket\nimport socketserver\nimport base64\nimport hashlib\nimport struct\nimport shutil\nimport webbrowser\nimport json\nfrom io import StringIO\nfrom string import Template\ndef writeWebsite(file,new_address):site=Template(\'\\nBlenderWebController
     

    Button Control

    Close

    Adjust Movement Sensitivity

     

    Swipe Control

    Not Connected

    Swipe Controls

    • TAP: Stop
    • SWIPE: Rotate
    • PINCH: Zoom
    • ROTATE: Rotate Z Axis
    Close

    Adjust Movement Sensitivity

     

    Connection Settings

     

    Not Connected

    Http & Websocket Address

    Close

    Scan to connect

     

    Swipe Control

    Not Connected

    Received Messages

      \').safe_substitute(address=new_address);html=open(file,\'w\');html.write(site);html.close()\nclass QuiteCGIHandler(http.server.CGIHTTPRequestHandler):\n def log_message(self,format,*args):pass\nclass WebSocketHandler(socketserver.BaseRequestHandler):\n def on_message(self,msg):print(msg)\n def handle_message(self,msg):\n if self._hasLock():\n msg_data=json.loads(msg)\n if \'MASTER_REQUEST\' in msg_data:\n if msg_data[\'MASTER_REQUEST\']:WebSocketHandler.lock_id=threading.current_thread().ident;self.send_json(dict(MASTER_STATUS=True));print(\'Locking to thread: \',WebSocketHandler.lock_id,\' : \',self.id);self.broadcast_all(dict(SLAVE=True))\n else:WebSocketHandler.lock_id=None;self.send_json(dict(MASTER_STATUS=False));self.broadcast_all(dict(SLAVE=False))\n self.on_message(msg_data)\n else:self.send_json(dict(SLAVE=True));print(\'Locked, trashing: \',msg)\n def on_close(self):print(\'Server: Closing Connection for \',self.client_address);self.send_message(\'Server: Closing Connection\')\n def send_message(self,message):print(\'Sending: \',message);self.send_json(dict(MESSAGE=message))\n def send_json(self,data):self.request.sendall(self._pack(json.dumps(data)))\n def broadcast_all(self,data):\n for t in WebSocketHandler.connections:\n if t.id==self.id:continue\n t.send_json(data)\n magic=b\'258EAFA5-E914-47DA-95CA-C5AB0DC85B11\';lock_id=None;connections=[]\n def _hasLock(self):return not WebSocketHandler.lock_id or WebSocketHandler.lock_id==self.id\n def setup(self):print(\'\\nConnection Established\',self.client_address);self.closeHandle=False;self.id=threading.current_thread().ident;self.alive=threading.Event();self.alive.set();WebSocketHandler.connections.append(self)\n def handle(self):\n try:self.handshake()\n except:print(\'HANDSHAKE ERROR! - Try using FireFox\')\n def run(self):\n while self.alive.isSet():\n msg=self.request.recv(2)\n if not msg or self.closeHandle or msg[0]==136:print(\'Received Closed\');break\n length=msg[1]&127\n if length==126:length=struct.unpack(\'>H\',self.request.recv(2))[0]\n elif length==127:length=struct.unpack(\'>Q\',self.request.recv(8))[0]\n masks=self.request.recv(4);decoded=\'\'\n for char in self.request.recv(length):decoded+=chr(char^masks[len(decoded)%4])\n self.handle_message(decoded)\n self.close()\n def close(self,message=\'Cxn Closed\'):self.closeHandle=True;self.request.sendall(self._pack(message,True));self.on_close()\n def handshake(self):\n key=None;data=self.request.recv(1024).strip()\n for line in data.splitlines():\n if b\'Upgrade:\' in line:\n upgrade=line.split(b\': \')[1]\n if not upgrade==b\'websocket\':raise Exception(\'Upgrade is Not a websocket!\',data)\n if b\'Sec-WebSocket-Key:\' in line:key=line.split(b\': \')[1];break\n if key is None:raise Exception("Couldn\'t find the key?:",data)\n 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;self.handshake_done=self.request.send(response.encode());print(\'Sending Connected Message... \',end=\'\')\n if self.handshake_done:self.send_message(\'Connected!\')\n print(\'Connected!\\n\')\n def _websocketHash(self,key):result_string=key+self.magic;sha1_digest=hashlib.sha1(result_string).digest();response_data=base64.encodestring(sha1_digest).strip();response_string=response_data.decode(\'utf8\');return response_string\n def _get_framehead(self,close=False):\n frame_head=bytearray(2);frame_head[0]=frame_head[0]|1<<7\n if close:frame_head[0]=frame_head[0]|8<<0\n else:frame_head[0]=frame_head[0]|1<<0\n return frame_head\n def _pack(self,data,close=False):\n frame_head=self._get_framehead(close)\n if len(data)<126:frame_head[1]=len(data)\n elif len(data)<2**16-1:frame_head[1]=126;frame_head+=int_to_bytes(len(data),2)\n elif len(data)<2**64-1:frame_head[1]=127;frame_head+=int_to_bytes(len(data),8)\n frame=frame_head+data.encode(\'utf-8\');return frame\nclass HTTPServer(threading.Thread):\n def __init__(self,address_info=(\'\',0)):threading.Thread.__init__(self);self.httpd=None;self._start_server(address_info)\n def _start_server(self,address_info):\n try:self.httpd=http.server.HTTPServer(address_info,QuiteCGIHandler);print(\'HTTP Server on : \',self.get_address())\n except Exception as e:print(\'The HTTP server could not be started\')\n def get_address(self):\n if self.httpd is not None:return \'http://{host}:{port}/\'.format(host=socket.gethostbyname(self.httpd.server_name),port=self.httpd.server_port)\n else:return None\n def run(self):\n if self.httpd is not None:self.httpd.serve_forever()\n else:print(\'Error! - HTTP Server is NULL\')\n def stop(self):\n print(\'Killing Http Server ...\')\n if self.httpd is not None:self.httpd.shutdown()\n print(\'Done\')\nclass WebSocketTCPServer(socketserver.ThreadingMixIn,http.server.HTTPServer):\n def __init__(self,server_address,RequestHandlerClass,bind_and_activate=True):socketserver.TCPServer.__init__(self,server_address,RequestHandlerClass,bind_and_activate);self.handlers=[];self.daemon_threads=True\n def finish_request(self,request,client_address):print(\'launching a new request\');t=self.RequestHandlerClass(request,client_address,self);print(\'Request:\',t);self.handlers.append(t);print(\'Num request:\',len(self.handlers));t.run()\n def get_handlers(self):return self.handlers\nclass WebsocketServer(threading.Thread):\n def __init__(self,handler,address_info=(\'\',0)):threading.Thread.__init__(self);self.wsd=None;self.handler=handler;self._start_server(address_info)\n def _start_server(self,address_info):\n try:self.wsd=WebSocketTCPServer(address_info,self.handler);print(self.get_address())\n except Exception as e:print(\'Error! - Websocket Server Not Started!\',e)\n def get_address(self):\n if self.wsd is not None:return \'ws://{host}:{port}/\'.format(host=socket.gethostbyname(self.wsd.server_name),port=self.wsd.server_port)\n else:return None\n def run(self):\n if self.wsd is not None:self.wsd.serve_forever()\n else:print(\'The WebSocket Server is NULL\')\n def stop(self):\n print(\'Killing WebSocket Server ...\')\n if self.wsd is not None:\n for h in self.wsd.handlers:h.alive.clear()\n self.wsd.shutdown()\n print(\'Done\')\n def get_handlers(self):return self.wsd.get_handlers()\n def send(self,msg):\n for h in self.wsd.get_handlers():h.send_message(msg)\nclass WebSocketHttpServer:\n def __init__(self,handler_class=WebSocketHandler,http_address=(\'\',0),ws_address=(\'\',0)):self.http_address=http_address;self.ws_address=ws_address;self.handler=handler_class;self.httpServer=None;self.wsServer=None\n def _clean_server_temp_dir(self):os.chdir(self.cwd);shutil.rmtree(self.tempdir);os.rmdir(self.tempdir)\n def _make_server_temp_dir(self):self.cwd=os.path.dirname(os.path.realpath(__file__));self.tempdir=tempfile.mkdtemp();os.chdir(self.tempdir);print(\'New temp dir:\',self.tempdir)\n def _make_webpage(self):writeWebsite(self.tempdir+\'/index.html\',self.wsServer.get_address())\n def stop(self):\n try:self.httpServer.stop();self.wsServer.stop();self._clean_server_temp_dir()\n except Exception as e:print(\'The Servers were never started\')\n def start(self):\n try:\n self._make_server_temp_dir();self.httpServer=HTTPServer(self.http_address);self.wsServer=WebsocketServer(self.handler,self.ws_address)\n if self.wsServer is not None and self.httpServer is not None:self.httpServer.start();self.wsServer.start();self._make_webpage();return True\n else:print(\'Error Starting The Servers, Something is not Initialized!\');return False\n except Exception as e:print();print(\'Error!!!, There is some error!\');print(e);print();return False\n def send(self,msg):self.wsServer.send(msg)\n def launch_webpage(self):webbrowser.open(self.httpServer.get_address()+\'index.html\')\n def status(self):\n if self.wsServer is None or self.httpServer is None:return False\n else:return True\nif __name__==\'__main__\':print(\'No Main Program!\')');bpy.ops.object.add(type='EMPTY');bpy.context.active_object.name='Controller';bpy.ops.object.game_property_new(type='INT',name='Website Port');bpy.ops.object.game_property_new(type='INT',name='Socket Port');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.;bpy.data.objects['Controller'].game.properties['Website Port'].value=8000;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;bpy.ops.logic.sensor_add(type='DELAY',name='StartServer');bpy.ops.logic.controller_add(type='PYTHON',name='Sever');bpy.ops.logic.actuator_add(type='MOTION',name='RotateRight');bpy.ops.logic.actuator_add(type='MOTION',name='RotateLeft');bpy.ops.logic.actuator_add(type='MOTION',name='RotateUp');bpy.ops.logic.actuator_add(type='MOTION',name='RotateDown');bpy.ops.logic.actuator_add(type='MOTION',name='ZRotateLeft');bpy.ops.logic.actuator_add(type='MOTION',name='ZRotateRight');bpy.ops.object.add(type='CAMERA');bpy.context.active_object.name='ControllerView';bpy.data.objects['ControllerView'].location=0.,-5.,0.;bpy.data.objects['ControllerView'].rotation_euler=math.radians(90),0.,0.;bpy.data.objects['ControllerView'].parent=bpy.data.objects['Controller'];bpy.ops.logic.actuator_add(type='MOTION',name='ZoomOut');bpy.ops.logic.actuator_add(type='MOTION',name='ZoomIn');con=bpy.data.objects['ControllerView'].constraints.new('DAMPED_TRACK');con.name='ViewLock';con.target=bpy.data.objects['Controller'];con.track_axis='TRACK_NEGATIVE_Z';bpy.context.scene.camera=bpy.data.objects['ControllerView'];bpy.data.objects['Controller'].game.sensors['StartServer'].use_repeat=True;bpy.data.objects['Controller'].game.controllers['Sever'].use_priority=True;bpy.data.objects['Controller'].game.controllers['Sever'].text=bpy.data.texts['StartServer'];bpy.data.objects['Controller'].game.actuators['RotateRight'].offset_rotation=0.,0.,math.radians(-1);bpy.data.objects['Controller'].game.actuators['RotateLeft'].offset_rotation=0.,0.,math.radians(1);bpy.data.objects['Controller'].game.actuators['RotateUp'].offset_rotation=math.radians(1),0.,0.;bpy.data.objects['Controller'].game.actuators['RotateDown'].offset_rotation=math.radians(-1),0.,0.;bpy.data.objects['Controller'].game.actuators['ZRotateLeft'].offset_rotation=0.,math.radians(1),0.;bpy.data.objects['Controller'].game.actuators['ZRotateRight'].offset_rotation=0.,math.radians(-1),0.;bpy.data.objects['ControllerView'].game.actuators['ZoomIn'].offset_location=0.,0.,.01;bpy.data.objects['ControllerView'].game.actuators['ZoomOut'].offset_location=0.,0.,-.01;c=bpy.data.objects['Controller'].game.controllers['Sever'];bpy.data.objects['Controller'].game.sensors['StartServer'].link(c);bpy.data.objects['Controller'].game.actuators['RotateRight'].link(c);bpy.data.objects['Controller'].game.actuators['RotateLeft'].link(c);bpy.data.objects['Controller'].game.actuators['RotateUp'].link(c);bpy.data.objects['Controller'].game.actuators['RotateDown'].link(c);bpy.data.objects['Controller'].game.actuators['ZRotateLeft'].link(c);bpy.data.objects['Controller'].game.actuators['ZRotateRight'].link(c);bpy.data.objects['ControllerView'].game.actuators['ZoomIn'].link(c);bpy.data.objects['ControllerView'].game.actuators['ZoomOut'].link(c);bpy.ops.object.select_all(action='DESELECT');bpy.data.objects['Controller'].select=True;return {'FINISHED'} +def register():bpy.utils.register_class(BlenderWebController_op);bpy.utils.register_class(BlenderWebController_pl) +def unregister():bpy.utils.unregister_class(BlenderWebController_op);bpy.utils.unregister_class(BlenderWebController_pl) +if __name__=='__main__':register() \ No newline at end of file diff --git a/main.py b/main.py index 50b2ede..ddd251e 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,11 @@ #! /usr/bin/env python3 -# /* - # * ---------------------------------------------------------------------------- - # * "THE BEER-WARE LICENSE" (Revision 42): - # * wrote this file. As long as you retain this notice you - # * can do whatever you want with this stuff. If we meet some day, and you think - # * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - # * ---------------------------------------------------------------------------- - # */ +# Copyright (C) <2014> +# 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 WebSocketHttpServer from server import WebSocketHandler diff --git a/mini/handler.py b/mini/handler.py deleted file mode 100644 index 1d1ecab..0000000 --- a/mini/handler.py +++ /dev/null @@ -1,23 +0,0 @@ -import server -import bge -import json -from time import sleep -class BlenderHandler(server.WebSocketHandler): - def on_message(self,msg): - cont=bge.logic.getCurrentController();msg_info=msg - if 'Actuator' in msg_info: - direction=msg_info['Actuator'];print('Moving:',direction) - if 'Speed' in msg_info: - speed=msg_info['Speed'];print('Speed: ',speed);rot=cont.actuators[direction].dRot;loc=cont.actuators[direction].dLoc;print(sum(rot)) - if sum(rot)!=0:rot=[x/abs(sum(rot)) for x in rot] - print(sum(loc)) - if sum(loc)!=0:loc=[x/abs(sum(loc)) for x in loc] - cont.actuators[direction].dLoc=[x*speed for x in loc];cont.actuators[direction].dRot=[x*speed for x in rot] - cont.activate(cont.actuators[direction]) - if 'Stop' in msg_info: - for act in cont.actuators:cont.deactivate(act) - if 'Reset' in msg_info: - try:cont.owner.worldOrientation=[[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]];scene=bge.logic.getCurrentScene();scene.objects['ControllerView'].localPosition=0.,-2.5,0. - except e:print('Error: '+e) - def send_message(self,msg):pass - def on_close(self):cont=bge.logic.getCurrentController();cont.activate(cont.actuators['QuitMsg']) diff --git a/mini/main.py b/mini/main.py deleted file mode 100644 index 50b2ede..0000000 --- a/mini/main.py +++ /dev/null @@ -1,40 +0,0 @@ -#! /usr/bin/env python3 - -# /* - # * ---------------------------------------------------------------------------- - # * "THE BEER-WARE LICENSE" (Revision 42): - # * wrote this file. As long as you retain this notice you - # * can do whatever you want with this stuff. If we meet some day, and you think - # * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - # * ---------------------------------------------------------------------------- - # */ - -from server import WebSocketHttpServer -from server import WebSocketHandler - -def main(): - run = True - print() - print() - print("*** Starting Websocket Server ***") - print() - print("Press Any Key To Quit...") - print() - server = WebSocketHttpServer(WebSocketHandler, http_address=('',8000)) - if server.start(): - print() - server.launch_webpage() - else: - print("Error Starting Server") - while run: - i = input("Enter Command:") - if i == "q": - server.stop() - run = False - else: - if i: - server.send(i) - print("Good Bye") - -if __name__ == '__main__': - main() diff --git a/mini/server.py b/mini/server.py deleted file mode 100644 index 0cebe41..0000000 --- a/mini/server.py +++ /dev/null @@ -1,134 +0,0 @@ -import tempfile -import http.server -import threading -import os -import socket -import socketserver -import base64 -import hashlib -import struct -import shutil -import webbrowser -import json -from io import StringIO -from string import Template -def writeWebsite(file,new_address):site_part1=' BlenderWebController
       

      Button Control

      Close

      Adjust Movement Sensitivity

       

      Swipe Control

      Not Connected

      Swipe Controls

      • TAP: Stop
      • SWIPE: Rotate
      • PINCH: Zoom
      • ROTATE: Rotate Z Axis
      Close

      Adjust Movement Sensitivity

       

      Connection Settings

       

      Not Connected

      Http & Websocket Address

      Close

      Scan to connect

       

      Swipe Control

      Not Connected

      Received Messages

        ';site_part2=Template(' ';html=open(file,'w');html.write(site_part1);html.write(site_part2);html.write(site_part3);html.close() -class QuiteCGIHandler(http.server.CGIHTTPRequestHandler): - def log_message(self,format,*args):pass -class WebSocketHandler(socketserver.BaseRequestHandler): - def on_message(self,msg):print(msg) - def handle_message(self,msg): - if self._hasLock(): - msg_data=json.loads(msg) - if 'MASTER_REQUEST' in msg_data: - if msg_data['MASTER_REQUEST']:WebSocketHandler.lock_id=threading.current_thread().ident;self.send_json(dict(MASTER_STATUS=True));print('Locking to thread: ',WebSocketHandler.lock_id,' : ',self.id);self.broadcast_all(dict(SLAVE=True)) - else:WebSocketHandler.lock_id=None;self.send_json(dict(MASTER_STATUS=False));self.broadcast_all(dict(SLAVE=False)) - self.on_message(msg_data) - else:self.send_json(dict(SLAVE=True));print('Locked, trashing: ',msg) - def on_close(self):print('Server: Closing Connection for ',self.client_address);self.send_message('Server: Closing Connection') - def send_message(self,message):print('Sending: ',message);self.send_json(dict(MESSAGE=message)) - def send_json(self,data):self.request.sendall(self._pack(json.dumps(data))) - def broadcast_all(self,data): - for t in WebSocketHandler.connections: - if t.id==self.id:continue - t.send_json(data) - magic=b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';lock_id=None;connections=[] - def _hasLock(self):return not WebSocketHandler.lock_id or WebSocketHandler.lock_id==self.id - def setup(self):print('\nConnection Established',self.client_address);self.closeHandle=False;self.id=threading.current_thread().ident;self.alive=threading.Event();self.alive.set();WebSocketHandler.connections.append(self) - def handle(self): - try:self.handshake() - except:print('HANDSHAKE ERROR! - Try using FireFox') - def run(self): - while self.alive.isSet(): - msg=self.request.recv(2) - if not msg or self.closeHandle or msg[0]==136:print('Received Closed');break - length=msg[1]&127 - if length==126:length=struct.unpack('>H',self.request.recv(2))[0] - elif length==127:length=struct.unpack('>Q',self.request.recv(8))[0] - masks=self.request.recv(4);decoded='' - for char in self.request.recv(length):decoded+=chr(char^masks[len(decoded)%4]) - self.handle_message(decoded) - self.close() - def close(self,message='Cxn Closed'):self.closeHandle=True;self.request.sendall(self._pack(message,True));self.on_close() - def handshake(self): - key=None;data=self.request.recv(1024).strip() - for line in data.splitlines(): - if b'Upgrade:' in line: - upgrade=line.split(b': ')[1] - if not upgrade==b'websocket':raise Exception('Upgrade is Not a websocket!',data) - if b'Sec-WebSocket-Key:' in line:key=line.split(b': ')[1];break - if key is None:raise Exception("Couldn't find the key?:",data) - print('Handshaking... ',end='');digest=self._websocketHash(key);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') - def _websocketHash(self,key):result_string=key+self.magic;sha1_digest=hashlib.sha1(result_string).digest();response_data=base64.encodestring(sha1_digest).strip();response_string=response_data.decode('utf8');return response_string - def _get_framehead(self,close=False): - frame_head=bytearray(2);frame_head[0]=frame_head[0]|1<<7 - if close:frame_head[0]=frame_head[0]|8<<0 - else:frame_head[0]=frame_head[0]|1<<0 - return frame_head - def _pack(self,data,close=False): - frame_head=self._get_framehead(close) - if len(data)<126:frame_head[1]=len(data) - elif len(data)<2**16-1:frame_head[1]=126;frame_head+=int_to_bytes(len(data),2) - elif len(data)<2**64-1:frame_head[1]=127;frame_head+=int_to_bytes(len(data),8) - frame=frame_head+data.encode('utf-8');return frame -class HTTPServer(threading.Thread): - def __init__(self,address_info=('',0)):threading.Thread.__init__(self);self.httpd=None;self._start_server(address_info) - def _start_server(self,address_info): - try:self.httpd=http.server.HTTPServer(address_info,QuiteCGIHandler);print('HTTP Server on : ',self.get_address()) - except Exception as e:print('The HTTP server could not be started') - def get_address(self): - if self.httpd is not None:return 'http://{host}:{port}/'.format(host=socket.gethostbyname(self.httpd.server_name),port=self.httpd.server_port) - else:return None - def run(self): - if self.httpd is not None:self.httpd.serve_forever() - else:print('Error! - HTTP Server is NULL') - def stop(self): - print('Killing Http Server ...') - if self.httpd is not None:self.httpd.shutdown() - print('Done') -class WebSocketTCPServer(socketserver.ThreadingMixIn,http.server.HTTPServer): - def __init__(self,server_address,RequestHandlerClass,bind_and_activate=True):socketserver.TCPServer.__init__(self,server_address,RequestHandlerClass,bind_and_activate);self.handlers=[];self.daemon_threads=True - def finish_request(self,request,client_address):print('launching a new request');t=self.RequestHandlerClass(request,client_address,self);print('Request:',t);self.handlers.append(t);print('Num request:',len(self.handlers));t.run() - def get_handlers(self):return self.handlers -class WebsocketServer(threading.Thread): - def __init__(self,handler,address_info=('',0)):threading.Thread.__init__(self);self.wsd=None;self.handler=handler;self._start_server(address_info) - def _start_server(self,address_info): - try:self.wsd=WebSocketTCPServer(address_info,self.handler);print(self.get_address()) - except Exception as e:print('Error! - Websocket Server Not Started!',e) - def get_address(self): - if self.wsd is not None:return 'ws://{host}:{port}/'.format(host=socket.gethostbyname(self.wsd.server_name),port=self.wsd.server_port) - else:return None - def run(self): - if self.wsd is not None:self.wsd.serve_forever() - else:print('The WebSocket Server is NULL') - def stop(self): - print('Killing WebSocket Server ...') - if self.wsd is not None: - for h in self.wsd.handlers:h.alive.clear() - self.wsd.shutdown() - print('Done') - def get_handlers(self):return self.wsd.get_handlers() - def send(self,msg): - for h in self.wsd.get_handlers():h.send_message(msg) -class WebSocketHttpServer: - def __init__(self,handler_class=WebSocketHandler,http_address=('',0),ws_address=('',0)):self.http_address=http_address;self.ws_address=ws_address;self.handler=handler_class;self.httpServer=None;self.wsServer=None - def _clean_server_temp_dir(self):os.chdir(self.cwd);shutil.rmtree(self.tempdir);os.rmdir(self.tempdir) - def _make_server_temp_dir(self):self.cwd=os.path.dirname(os.path.realpath(__file__));self.tempdir=tempfile.mkdtemp();os.chdir(self.tempdir);print('New temp dir:',self.tempdir) - def _make_webpage(self):writeWebsite(self.tempdir+'/index.html',self.wsServer.get_address()) - def stop(self): - try:self.httpServer.stop();self.wsServer.stop();self._clean_server_temp_dir() - except Exception as e:print('The Servers were never started') - def start(self): - try: - self._make_server_temp_dir();self.httpServer=HTTPServer(self.http_address);self.wsServer=WebsocketServer(self.handler,self.ws_address) - if self.wsServer is not None and self.httpServer is not None:self.httpServer.start();self.wsServer.start();self._make_webpage();return True - else:print('Error Starting The Servers, Something is not Initialized!');return False - except Exception as e:print();print('Error!!!, There is some error!');print(e);print();return False - def send(self,msg):self.wsServer.send(msg) - def launch_webpage(self):webbrowser.open(self.httpServer.get_address()+'index.html') - def status(self): - if self.wsServer is None or self.httpServer is None:return False - else:return True -if __name__=='__main__':print('No Main Program!') diff --git a/mini/startServers.py b/mini/startServers.py deleted file mode 100644 index 298e7c9..0000000 --- a/mini/startServers.py +++ /dev/null @@ -1,13 +0,0 @@ -from server import WebSocketHttpServer -from Handler import BlenderHandler -import threading -def run_server(): - if 'Running' in bge.logic.globalDict:return - if 'Server' in bge.logic.globalDict: - if not bge.logic.globalDict['Server'].status(): - print();print();print('*** Starting Websocket Server ***');print();print('Press Any Key To Quit...');print() - if bge.logic.globalDict['Server'].start():print();bge.logic.globalDict['Server'].launch_webpage();bge.logic.globalDict['Running']=True - else:print('Error Starting Server') - else:print('Server Not Defined!') -def main():cont=bge.logic.getCurrentController();scene=bge.logic.getCurrentScene();scene.active_camera=scene.objects['ControllerView'];bge.logic.globalDict['Server']=WebSocketHttpServer(BlenderHandler,http_address=('',8000));run_server() -if __name__=='__main__':main() diff --git a/server.py b/server.py deleted file mode 100644 index 1e2e471..0000000 --- a/server.py +++ /dev/null @@ -1,389 +0,0 @@ -#! /usr/bin/env python3 - -# /* - # * ---------------------------------------------------------------------------- - # * "THE BEER-WARE LICENSE" (Revision 42): - # * wrote this file. As long as you retain this notice you - # * can do whatever you want with this stuff. If we meet some day, and you think - # * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - # * ---------------------------------------------------------------------------- - # */ - -import tempfile -import http.server -import threading -import os -import socket -import socketserver -import base64 -import hashlib -import struct -import shutil -import webbrowser -import json -from io import StringIO -from string import Template - -def writeWebsite(file, new_address): - site_part1 = """ BlenderWebController
         

        Button Control

        Close

        Adjust Movement Sensitivity

         

        Swipe Control

        Not Connected

        Swipe Controls

        • TAP: Stop
        • SWIPE: Rotate
        • PINCH: Zoom
        • ROTATE: Rotate Z Axis
        Close

        Adjust Movement Sensitivity

         

        Connection Settings

         

        Not Connected

        Http & Websocket Address

        Close

        Scan to connect

         

        Swipe Control

        Not Connected

        Received Messages

          """ - site_part2 = Template(""" """ - html = open(file ,"w") - html.write(site_part1) - html.write(site_part2) - html.write(site_part3) - html.close() - - -class QuiteCGIHandler(http.server.CGIHTTPRequestHandler): - def log_message(self, format, *args): - pass #Hides all messages for Request Handler - -# Inherit this class to handle the websocket connection -class WebSocketHandler(socketserver.BaseRequestHandler): - -#-------------- Over ride these ---------------------------------------- - def on_message(self, msg): - #msg is a array, decoded from JSON - #Override this function to handle the input from webcontroller - print(msg) - #self.send_message("Got :" + ast.literal_eval(msg)) - - def handle_message(self, msg): - #only the user with the lock can control - if self._hasLock(): - msg_data = json.loads(msg) - if "MASTER_REQUEST" in msg_data: - if msg_data["MASTER_REQUEST"]: - WebSocketHandler.lock_id = threading.current_thread().ident - self.send_json(dict(MASTER_STATUS=True)); - print("Locking to thread: " ,WebSocketHandler.lock_id, " : ", self.id) - self.broadcast_all(dict(SLAVE=True)) - else: - 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)); - print("Locked, trashing: ", msg) - - def on_close(self): - print("Server: Closing Connection for ", self.client_address) - self.send_message("Server: Closing Connection") - - def send_message(self, message): - print("Sending: ", message) - self.send_json(dict(MESSAGE=message)) - - def send_json(self, data): - #sends a python dict as a json object - self.request.sendall(self._pack(json.dumps(data))) - - def broadcast_all(self, data): - #send a araay converted into JSON to every thread - for t in WebSocketHandler.connections: - if t.id == self.id: - continue - t.send_json(data) - -#------------------------------------------------------------------- - - magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' - 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 - print("\nConnection Established", self.client_address) - self.closeHandle = False - self.id = threading.current_thread().ident - self.alive = threading.Event() - self.alive.set() - WebSocketHandler.connections.append(self) - - def handle(self): - #handles the handshake with the server - #Overwrtien function from socketserver - try: - self.handshake() - except: - print("HANDSHAKE ERROR! - Try using FireFox") - #return - - def run(self): - #runs the handler in a thread - while self.alive.isSet(): - msg = self.request.recv(2) - if not msg or self.closeHandle or msg[0] == 136: - print("Received Closed") - break - length = msg[1] & 127 - if length == 126: - length = struct.unpack(">H", self.request.recv(2))[0] - elif length == 127: - length = struct.unpack(">Q", self.request.recv(8))[0] - masks = self.request.recv(4) - decoded = "" - for char in self.request.recv(length): - decoded += chr(char ^ masks[len(decoded) % 4]) - self.handle_message(decoded) - - #WebSocketHandler.broadcast_all.wait(0.01) - - self.close() - - def close(self, message="Cxn Closed"): - self.closeHandle = True - self.request.sendall(self._pack(message, True)) - self.on_close() - - def handshake(self): - key = None - data = self.request.recv(1024).strip() - for line in data.splitlines(): - if b'Upgrade:' in line: - upgrade = line.split(b': ')[1] - if not upgrade == b'websocket': - raise Exception("Upgrade is Not a websocket!", data) - if b'Sec-WebSocket-Key:' in line: - key = line.split(b': ')[1] - break - if key is None: - raise Exception("Couldn't find the key?:", data) - print('Handshaking... ', end = '') - digest = self._websocketHash(key) - 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") - - def _websocketHash(self, key): - result_string = key + self.magic - sha1_digest = hashlib.sha1(result_string).digest() - response_data = base64.encodestring(sha1_digest).strip() - response_string = response_data.decode('utf8') - return response_string - - def _get_framehead(self, close=False): - #Gets the frame header for sending data, set final fragment & opcode - frame_head = bytearray(2) - frame_head[0] = frame_head[0] | (1 << 7) - if close: - # send the close connection frame - frame_head[0] = frame_head[0] | (8 << 0) - else: - #send the default text frame - frame_head[0] = frame_head[0] | (1 << 0) - return frame_head - - def _pack(self, data ,close=False): - #pack bytes for sending to client - frame_head = self._get_framehead(close) - # payload length - if len(data) < 126: - frame_head[1] = len(data) - elif len(data) < ((2**16) - 1): - # 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) - 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 = frame_head + data.encode('utf-8') - return frame - -class HTTPServer(threading.Thread): - def __init__(self, address_info=('',0)): - threading.Thread.__init__(self) - self.httpd = None - self._start_server( address_info ) - - def _start_server(self, address_info): - #Starts the server at object init - try: - #Using std CGI Handler - self.httpd = http.server.HTTPServer(address_info, QuiteCGIHandler) - print("HTTP Server on : ", self.get_address() ) - except Exception as e: - print("The HTTP server could not be started") - - def get_address(self): - #returns string of the servers address or None - if self.httpd is not None: - return 'http://{host}:{port}/'.format(host=socket.gethostbyname(self.httpd.server_name), port=self.httpd.server_port) - else: - return None - - def run(self): - #Overwrtien from Threading.Thread - if self.httpd is not None : - self.httpd.serve_forever() - else: - print("Error! - HTTP Server is NULL") - - def stop(self): - #Overwrtien from Threading.Thread - print("Killing Http Server ...") - if self.httpd is not None: - self.httpd.shutdown() - print("Done") - -class WebSocketTCPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - #Added a list of current handlers so they can be closed - def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): - socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate) - self.handlers = [] - self.daemon_threads = True - - def finish_request(self, request, client_address): - #Finish one request by instantiating RequestHandlerClass - print("launching a new request") - t = self.RequestHandlerClass(request, client_address, self) - print("Request:" , t) - self.handlers.append(t) - print("Num request:" ,len(self.handlers)) - t.run() - - # def process_request(self, request, client_address): - # #Start a new thread to process the request - # t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) - # t.daemon = True - # t.start() - - def get_handlers(self): - #returns the list of handlers - return self.handlers - -class WebsocketServer(threading.Thread): - def __init__(self, handler, address_info=('',0)): - threading.Thread.__init__(self) - self.wsd = None - self.handler = handler - self._start_server(address_info) - - def _start_server(self, address_info): - #Starts the server at object init - try: - self.wsd = WebSocketTCPServer( address_info, self.handler) - print( self.get_address()) - except Exception as e: - print("Error! - Websocket Server Not Started!", e) - - def get_address(self): - #returns string of the servers address or None - if self.wsd is not None: - return 'ws://{host}:{port}/'.format(host=socket.gethostbyname(self.wsd.server_name), port=self.wsd.server_port) - else: - return None - - def run(self): - if self.wsd is not None: - self.wsd.serve_forever() - else: - print("The WebSocket Server is NULL") - - def stop(self): - print("Killing WebSocket Server ...") - if self.wsd is not None: - for h in self.wsd.handlers: - h.alive.clear() - - self.wsd.shutdown() - print("Done") - - def get_handlers(self): - #returns the list of handlers - return self.wsd.get_handlers() - - def send(self, msg): - for h in self.wsd.get_handlers(): - h.send_message(msg) - -class WebSocketHttpServer(): - def __init__(self, handler_class = WebSocketHandler, http_address=('',0), ws_address=('',0) ): - self.http_address = http_address - self.ws_address = ws_address - self.handler = handler_class - self.httpServer = None - self.wsServer = None - - def _clean_server_temp_dir(self): - os.chdir(self.cwd) - shutil.rmtree(self.tempdir) - os.rmdir(self.tempdir) - - def _make_server_temp_dir(self): - #make the new temp directory - self.cwd = os.path.dirname(os.path.realpath(__file__)) - self.tempdir = tempfile.mkdtemp() - os.chdir(self.tempdir) - print("New temp dir:", self.tempdir) - - def _make_webpage(self): - writeWebsite(self.tempdir + "/index.html" , self.wsServer.get_address()) - - def stop(self): - try: - self.httpServer.stop() - self.wsServer.stop() - self._clean_server_temp_dir() - except Exception as e: - print("The Servers were never started") - - def start(self): - try: - # make directory and copy files - self._make_server_temp_dir() - self.httpServer = HTTPServer(self.http_address) - self.wsServer = WebsocketServer(self.handler, self.ws_address) - if self.wsServer is not None and self.httpServer is not None: - self.httpServer.start() - self.wsServer.start() - self._make_webpage() - return True - else: - print("Error Starting The Servers, Something is not Initialized!") - return False - except Exception as e: - print() - print("Error!!!, There is some error!") - print(e) - print() - return False - - def send(self, msg): - self.wsServer.send(msg) - - def launch_webpage(self): - #Copies all the resource over to the temp dir - webbrowser.open(self.httpServer.get_address() + "index.html") - - def status(self): - if self.wsServer is None or self.httpServer is None: - return False - else: - return True - -if __name__ == '__main__': - print("No Main Program!") - diff --git a/startServers.py b/startServers.py deleted file mode 100644 index e31d0f6..0000000 --- a/startServers.py +++ /dev/null @@ -1,45 +0,0 @@ -#! /usr/bin/env python3 - -# /* - # * ---------------------------------------------------------------------------- - # * "THE BEER-WARE LICENSE" (Revision 42): - # * wrote this file. As long as you retain this notice you - # * can do whatever you want with this stuff. If we meet some day, and you think - # * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - # * ---------------------------------------------------------------------------- - # */ - -from server import WebSocketHttpServer -from Handler import BlenderHandler -import threading - - -def run_server(): - if "Running" in bge.logic.globalDict: - return - if "Server" in bge.logic.globalDict: - if not bge.logic.globalDict["Server"].status(): - print() - print() - print("*** Starting Websocket Server ***") - print() - print("Press Any Key To Quit...") - print() - if bge.logic.globalDict["Server"].start(): - print() - bge.logic.globalDict["Server"].launch_webpage() - bge.logic.globalDict["Running"] = True - else: - print("Error Starting Server") - else: - print("Server Not Defined!") - -def main(): - cont = bge.logic.getCurrentController() - scene = bge.logic.getCurrentScene() - scene.active_camera = scene.objects['ControllerView'] - bge.logic.globalDict["Server"] = WebSocketHttpServer(BlenderHandler, http_address=('',8000)) - run_server() - -if __name__ == '__main__': - main() diff --git a/web/controller.js b/web/controller.js index 604379f..f858f82 100644 --- a/web/controller.js +++ b/web/controller.js @@ -1,17 +1,15 @@ /* - * ---------------------------------------------------------------------------- - * "THE BEER-WARE LICENSE" (Revision 42): - * wrote this file. As long as you retain this notice you - * can do whatever you want with this stuff. If we meet some day, and you think - * this stuff is worth it, you can buy me a beer in return Joseph Livecchi - * ---------------------------------------------------------------------------- +# Copyright (C) <2014> +# 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. */ /* -------------------------- Global Varibles ---------------------------- */ var s = null; var somekeyDown = 0; var isMaster = false; - +var ipRegex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})/ window.onbeforeunload = close; /* -------------------------- Websocket Fucntions ---------------------------- @@ -22,11 +20,13 @@ function log(msg, title ){ $("#DebugMsgList").prepend('
        • '+title+'

          '+ msg +'

        • ').listview( "refresh" ); } function open (e) { + $("#ToggleCxnBtn").text("Disconnect"); log("Connected!"); $(".ErrMsg").fadeOut(); }; function close(e) { log("Connection Closed!"); + $("#ToggleCxnBtn").text("Connect"); $(".ErrMsg").text("Not Connected").fadeIn(); }; function msg (e) { @@ -39,7 +39,8 @@ function msg (e) { log(e.data); }; function error(e) { - log("Error: "+ e ); + log("Error: "+ e ); + $("#ToggleCxnBtn").text("Connect"); $(".ErrMsg").text("Error! - Check Msg").fadeIn(); }; function send(key,msg,speed){ @@ -58,18 +59,16 @@ function toggleConnection() { //Opens and closes a conenction using the address in #CxnWSAddress if(s){ s.close(1000, "Try to Close"); - $("#ToggleCxnBtn").text("Connect"); s = null; }else{ try { address = $("#CxnWSAddress").val(); - if (address != "" || address != "$address") { + if ( ipRegex.test(address)) { s = new WebSocket(address); s.onopen = open; s.onclose = close; s.onmessage = msg; s.onerror = error; - $("#ToggleCxnBtn").text("Disconnect"); } } catch (ex) { error("Could Not Connected"); diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..45c84fa --- /dev/null +++ b/web/index.html @@ -0,0 +1,197 @@ + + + + + + + BlenderWebController + + + + + + + + + + + +
          + +
          + + +
          + +
          +   +

          Button Control

          + +
          + +
          +

          Not Connected

          +
          +
          +
          + +
          +
          + + +
          +
          + +
          +
          +
          +
          + + +
          +
          + + +
          +
          +
          + +
          + + +
          + Close +

          Adjust Movement Sensitivity

          +
          + + +
          +
          +
          + + + +
          +
          + +   +

          Swipe Control

          + +
          + +
          +

          Not Connected

          +
          +

          Swipe Controls

          +

            +
          • TAP: Stop
          • +
          • SWIPE: Rotate
          • +
          • PINCH: Zoom
          • +
          • ROTATE: Rotate Z Axis
          • +
          +
          +
          + + +
          + Close +

          Adjust Movement Sensitivity

          +
          + + +
          +
          +
          + + +
          +
          + +   +

          Connection Settings

          +   +
          + +
          +

          Not Connected

          +

          Http & Websocket Address

          +
          + + + + + +
          +
          +
          +
          +
          + Close +

          Scan to connect

          + +
          +
          +
          +
          + + + +
          +
          + +   +

          Swipe Control

          + +
          + +
          +

          Not Connected

          +

          Received Messages

          +
            + +
            +
            + + + + + + + + diff --git a/web/index.min.html b/web/index.min.html deleted file mode 100644 index 07cbabb..0000000 --- a/web/index.min.html +++ /dev/null @@ -1,3 +0,0 @@ - BlenderWebController
             

            Button Control

            Close

            Adjust Movement Sensitivity

             

            Swipe Control

            Not Connected

            Swipe Controls

            • TAP: Stop
            • SWIPE: Rotate
            • PINCH: Zoom
            • ROTATE: Rotate Z Axis
            Close

            Adjust Movement Sensitivity

             

            Connection Settings

             

            Not Connected

            Http & Websocket Address

            Close

            Scan to connect

             

            Swipe Control

            Not Connected

            Received Messages

              - \ No newline at end of file diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..a5cf80f --- /dev/null +++ b/web/style.css @@ -0,0 +1,43 @@ +/* +# Copyright (C) <2014> +# 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. + */ + +body { -webkit-touch-callout: none !important; + height: 100%; +} +a { -webkit-user-select: none !important; } + +.ctrlBtn{ + padding-top: 0.7em; +} +#SwipeControl{ + height: 100%; +} +#Page-Swipe .fullHeight { + position : absolute; + top : 40px; + right : 0; + bottom : 70px; + left : 0; +} +.iconButton { + -webkit-border-radius: .3125em !important; + border-radius: .3125em !important; +} +.ui-footer{ + position: fixed; + bottom: 0px; + left: 0px; + right: 0px; +} + +#popup-strength input{ + display: none; +} + +#popup-strength .ui-slider-track { + margin-left: 15px; +} \ No newline at end of file