From c5c7051ddc3cbbcf1dee921f294ded2a09716a23 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 22 Sep 2020 12:00:02 +0200 Subject: [PATCH] Better redis doc --- README.md | 16 ++- plugins/custom1.py | 277 ++++++++++++++++++++++++++++++++++++++++++++ plugins/square.py | 279 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 566 insertions(+), 6 deletions(-) create mode 100644 plugins/custom1.py create mode 100644 plugins/square.py diff --git a/README.md b/README.md index bbf8966..4c0b6e2 100644 --- a/README.md +++ b/README.md @@ -172,18 +172,18 @@ A "plugin" is a software that send any number of pointlist(s). LJ comes with dif # Client Side : Program your own "plugin" # -The server approach is based on redis, so you can write and run your laser client software in any redis capable programming langage (50+ : https://redis.io/clients). An external program that just send pointlists is a "client". If you want some interactions from the webUI, like text status area support, crash detection, launch,... it's a "plugin" and some default code is needed. See custom1.py, a basic plugin you can modiffy. LJ and plugins signaling is mainly over OSC. +The server approach is based on redis, so you can write and run your laser client software in any redis capable programming langage (50+ : https://redis.io/clients). An external program that just send pointlists to redis is a "client". If you want some interactions from the webUI, like text status area support, crash detection, autostart,... it's a "plugin" and some default code is needed. LJ and plugins signaling is over OSC. - Read all this readme ;-) -- Generate at least one pointlist array (say a square) with *enough points*, one point is likely to fail for buffering reason. See command reference below for more. -- Feed your point list array in string format to redis server. i.e use "/pl/0/1" redis key to feed scene 0, laser 1. +- Generate at least one pointlist array (say a square) with *enough points*, one point is likely to fail for buffering reason. +- Feed your point list array in string format to redis server. i.e use "/pl/0/1" redis key to feed scene 0, laser 1. See /pl/ command in command reference below how to send your pointlist to i.e /pl/0/1 redis key. - Tell LJ.conf your plugin configuration : OSC port and command line to start it. - At least a plugin must reply /pong to OSC request /ping. Currently the WebUI (www/index.html) is static. # -# Client side dope mode : How to use lj23 (python3) +# Client side dope mode for python 3 generators : How to use lj23 # lj23 have many very useful functions to not reinvent the wheel for advanced points generation "client" side : layers, sprites, built in rotations,.. @@ -191,7 +191,7 @@ lj23 have many very useful functions to not reinvent the wheel for advanced poin 4 Great TRICKS with lj23 : -First open square.py and learn how to declare different objects. square.py is a 2D shape example in 3D rotation (red/green anaglyph rendering) that use 2 layers : one for left eye and one for right eye. +First open custom1.py and learn how to declare different objects. custom1.py is a 2D shape example in 3D rotation (red/green anaglyph rendering) that use 2 layers : one for left eye and one for right eye. custom1 is a copy of square.py 1/ How to have another laser drawing the same thing ? @@ -370,12 +370,16 @@ Generic : # LJ commands reference # -All commands are available via OSC (port 8002) or websocket (port 9001) +All these commands are available via OSC (port 8002) or websocket (port 9001) /pl/scenenumber/lasernumber value : value is the pointlist to draw as string. Example : /pl/0/0 "[(150.0, 230.0, 65280), (170.0, 170.0, 65280), (230.0, 170.0, 65280), (210.0, 230.0, 65280), (150.0, 230.0, 65280)]" +Use the same syntax if you send your pointlist directly in redis : "/pl/0/0" is the key and value is "[(150.0,..." + +Every point must be : (x,y,color). Color is the hex color #FFFFFF in decimal. + /scale/X/lasernumber value diff --git a/plugins/custom1.py b/plugins/custom1.py new file mode 100644 index 0000000..75506c3 --- /dev/null +++ b/plugins/custom1.py @@ -0,0 +1,277 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- + + +''' + +custom1 +v0.1.0 + +A copy of square.py you can modify to code your plugin. +custom1 has necessary hooks in LJ.conf, webui and so on. + + +LICENCE : CC + +by Sam Neurohack + + +''' +import sys +import os +print() +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../libs/') + +#import from LJ +sys.path.append(ljpath +'/libs/') +print(ljpath+'/../libs/') + +import lj23layers as lj + +sys.path.append('../libs') +from OSC3 import OSCServer, OSCClient, OSCMessage +import redis +import math +import time +import argparse + +OSCinPort = 8014 + +print ("") +print ("Arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Custom1 example for LJ") +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-s","--scene",help="LJ scene number (0 by default)",type=int) +#argsparser.add_argument("-l","--laser",help="Laser number to be displayed (0 by default)",type=int) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) + +args = argsparser.parse_args() + +if args.scene: + ljscene = args.scene +else: + ljscene = 0 +''' +if args.laser: + plnumber = args.laser +else: + plnumber = 0 +''' + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +print("redisIP",redisIP) + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +print("myIP",myIP) +if args.verbose: + debug = args.verbose +else: + debug = 0 + +# Useful variables init. +white = lj.rgb2int(255,255,255) +red = lj.rgb2int(255,0,0) +blue = lj.rgb2int(0,0,255) +green = lj.rgb2int(0,255,0) + +width = 800 +height = 600 +centerX = width / 2 +centerY = height / 2 + +# 3D to 2D projection parameters +fov = 256 +viewer_distance = 2.2 + +# Anaglyph computation parameters for right and left eyes. +# algorythm come from anaglyph geo maps +eye_spacing = 100 +nadir = 0.5 +observer_altitude = 30000 +map_layerane_altitude = 0.0 + +# square coordinates : vertices that compose each of the square. +vertices = [ + (- 1.0, 1.0,- 1.0), + ( 1.0, 1.0,- 1.0), + ( 1.0,- 1.0,- 1.0), + (- 1.0,- 1.0,- 1.0) + ] + +face = [0,1,2,3] + +# +# LJ inits +# + +layer = 0 + +# Setup LJ library mandatory properties for this plugin +lj.Config(redisIP, ljscene, "custom1") + +# Define properties for each drawn "element" : name, intensity, active, xy, color, red, green, blue, layer , closed +Leftsquare = lj.FixedObject('Leftsquare', True, 255, [], red, 255, 0, 0, layer , True) +Rightsquare = lj.FixedObject('Rightsquare', True, 255, [], green, 0, 255, 0, layer , True) + +# 'Destination' for given layer : name, number, active, layer , scene, laser +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) # Dest0 will send layer 0 points to scene 0, laser 0 + + +# +# Anaglyph computation : different X coordinate for each eye +# + +def LeftShift(elevation): + + diff = elevation - map_layerane_altitude + return nadir * eye_spacing * diff / (observer_altitude - elevation) + +def RightShift(elevation): + + diff = map_layerane_altitude - elevation + return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 + + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# OSC callbacks + +# /custom1/ljscene +def OSCljscene(path, tags, args, source): + + print("Got /custom1/ljscene with value", args[0]) + lj.WebStatus("custom1 to virtual "+ str(args[0])) + ljscene = args[0] + lj.Ljscene(ljscene) + + +def Proj(x,y,z,angleX,angleY,angleZ): + + rad = angleX * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + y2 = y + y = y2 * cosa - z * sina + z = y2 * sina + z * cosa + + rad = angleY * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + z2 = z + z = z2 * cosa - x * sina + x = z2 * sina + x * cosa + + rad = angleZ * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + x2 = x + x = x2 * cosa - y * sina + y = x2 * sina + y * cosa + + + """ Transforms this 3D point to 2D using a perspective projection. """ + factor = fov / (viewer_distance + z) + x = x * factor + centerX + y = - y * factor + centerY + return (x,y) + + +# +# Main +# + +def Run(): + + + Left = [] + Right = [] + counter =0 + #lj.SendLJ("/custom1/start 1") + + # OSC Server callbacks + print("Starting OSC server at",myIP," port",OSCinPort,"...") + oscserver.addMsgHandler( "/custom1/ljscene", OSCljscene ) + + # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) + + try: + + while lj.oscrun: + + lj.OSCframe() + Left = [] + Right = [] + + x = vertices[0][0] + y = vertices[0][1] + z = vertices[0][2] + + # LJ tracers will "move" the laser to this first point in black, then move to the next with second point color. + # For more accuracy in dac emulator, repeat this first point. + + # Generate all points in square. + for point in face: + x = vertices[point][0] + y = vertices[point][1] + z = vertices[point][2] + + Left.append(Proj(x+LeftShift(z*25),y,z,0,counter,0)) + Right.append(Proj(x+RightShift(z*25),y,z,0,counter,0)) + + + lj.PolyLineOneColor(Left, c = Leftsquare.color , layer = Leftsquare.layer, closed = Leftsquare.closed) + lj.PolyLineOneColor(Right, c = Rightsquare.color , layer = Rightsquare.layer, closed = Rightsquare.closed) + + lj.DrawDests() + + + time.sleep(0.1) + + counter += 1 + if counter > 360: + counter = 0 + + except KeyboardInterrupt: + pass + + # Gently stop on CTRL C + + finally: + + lj.ClosePlugin() + + +Run() diff --git a/plugins/square.py b/plugins/square.py new file mode 100644 index 0000000..a9f6c34 --- /dev/null +++ b/plugins/square.py @@ -0,0 +1,279 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- + + +''' + +Square +v0.1.0 + +Anaglyphed rotating square (for red and green glasses) + +This scene uses the drawing functions provided by LJ in lj23.py + +LICENCE : CC + +by Sam Neurohack + + +''' +import sys +import os +print() +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../libs/') + +#import from LJ +sys.path.append(ljpath +'/libs/') +print(ljpath+'/../libs/') + +import lj23layers as lj + +sys.path.append('../libs') +from OSC3 import OSCServer, OSCClient, OSCMessage +import redis +import math +import time +import argparse + +OSCinPort = 8013 + +print ("") +print ("Arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Square example for LJ") +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-s","--scene",help="LJ scene number (0 by default)",type=int) +#argsparser.add_argument("-l","--laser",help="Laser number to be displayed (0 by default)",type=int) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) + +args = argsparser.parse_args() + +if args.scene: + ljscene = args.scene +else: + ljscene = 0 +''' +if args.laser: + plnumber = args.laser +else: + plnumber = 0 +''' + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +print("redisIP",redisIP) + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +print("myIP",myIP) + +if args.verbose: + debug = args.verbose +else: + debug = 0 + +# Useful variables init. +white = lj.rgb2int(255,255,255) +red = lj.rgb2int(255,0,0) +blue = lj.rgb2int(0,0,255) +green = lj.rgb2int(0,255,0) + +width = 800 +height = 600 +centerX = width / 2 +centerY = height / 2 + +# 3D to 2D projection parameters +fov = 256 +viewer_distance = 2.2 + +# Anaglyph computation parameters for right and left eyes. +# algorythm come from anaglyph geo maps +eye_spacing = 100 +nadir = 0.5 +observer_altitude = 30000 +map_layerane_altitude = 0.0 + +# square coordinates : vertices that compose each of the square. +vertices = [ + (- 1.0, 1.0,- 1.0), + ( 1.0, 1.0,- 1.0), + ( 1.0,- 1.0,- 1.0), + (- 1.0,- 1.0,- 1.0) + ] + +face = [0,1,2,3] + +# +# LJ inits +# + +layer = 0 + +# Setup LJ library mandatory properties. +lj.Config(redisIP, ljscene, "square") + +# You can define properties for each drawn "element" : name, intensity, active, xy, color, red, green, blue, layer , closed +Leftsquare = lj.FixedObject('Leftsquare', True, 255, [], red, 255, 0, 0, layer , True) +Rightsquare = lj.FixedObject('Rightsquare', True, 255, [], blue, 0, 255, 0, layer , True) + +# 'Destination' for given layer : name, number, active, layer , scene, laser +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) # Dest0 will send layer 0 points to scene 0, laser 0 + + +# +# Anaglyph computation : different X coordinate for each eye +# + +def LeftShift(elevation): + + diff = elevation - map_layerane_altitude + return nadir * eye_spacing * diff / (observer_altitude - elevation) + +def RightShift(elevation): + + diff = map_layerane_altitude - elevation + return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 + + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# OSC callbacks + +# /square/ljscene +def OSCljscene(path, tags, args, source): + + print("Got /square/ljscene with value", args[0]) + lj.WebStatus("square to virtual "+ str(args[0])) + ljscene = args[0] + lj.Ljscene(ljscene) + + +def Proj(x,y,z,angleX,angleY,angleZ): + + rad = angleX * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + y2 = y + y = y2 * cosa - z * sina + z = y2 * sina + z * cosa + + rad = angleY * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + z2 = z + z = z2 * cosa - x * sina + x = z2 * sina + x * cosa + + rad = angleZ * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + x2 = x + x = x2 * cosa - y * sina + y = x2 * sina + y * cosa + + + """ Transforms this 3D point to 2D using a perspective projection. """ + factor = fov / (viewer_distance + z) + x = x * factor + centerX + y = - y * factor + centerY + return (x,y) + + +# +# Main +# + +def Run(): + + + Left = [] + Right = [] + counter =0 + lj.WebStatus("Square") + lj.SendLJ("/square/start 1") + + # OSC Server callbacks + print("Starting OSC server at",myIP," port",OSCinPort,"...") + oscserver.addMsgHandler( "/square/ljscene", OSCljscene ) + + # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) + + try: + + while lj.oscrun: + + lj.OSCframe() + Left = [] + Right = [] + + x = vertices[0][0] + y = vertices[0][1] + z = vertices[0][2] + + # LJ tracers will "move" the laser to this first point in black, then move to the next with second point color. + # For more accuracy in dac emulator, repeat this first point. + + # Generate all points in square. + for point in face: + x = vertices[point][0] + y = vertices[point][1] + z = vertices[point][2] + + Left.append(Proj(x+LeftShift(z*25),y,z,0,counter,0)) + Right.append(Proj(x+RightShift(z*25),y,z,0,counter,0)) + + + lj.PolyLineOneColor(Left, c = Leftsquare.color , layer = Leftsquare.layer, closed = Leftsquare.closed) + lj.PolyLineOneColor(Right, c = Rightsquare.color , layer = Rightsquare.layer, closed = Rightsquare.closed) + + lj.DrawDests() + + + time.sleep(0.1) + + counter += 1 + if counter > 360: + counter = 0 + + except KeyboardInterrupt: + pass + + # Gently stop on CTRL C + + finally: + + lj.ClosePlugin() + + +Run()