#!/usr/bin/python3 # -*- coding: utf-8 -*- # -*- mode: Python -*- ''' Maxwell v0.1.0 This client uses the relative drawing functions (rpolyline) -> your coordinates must be centered around 0,0. - Mixer : LRmixer : right curve percent LRtype : "add", "minus", "multiply" - points numbers - Curve objects : required parameters : name, curvetype, freqlimit, freq, inversion - Scaler : Everything is resize by a constant - Translator : translations X Y Z by a constant - Rotator : rotations X Y Z by a constant - Duplicator : duplicators, duplicatorsAngle Duplicators are extra rpolyline with Angle added as global rotation parameter along *all* axis, this need to be checked conceptually TODO : - in Curve Objects : amptype, amp, phasemodtype, phasemodspeed, phasemodspeed, phaseoffsettype, phaseoffset - Other functions for lfos, translator, rotator, scaler, duplicator,... - CC inputs for all parameters 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 ('Maxwell plugin startup') import lj23 as lj sys.path.append('../libs') from OSC3 import OSCServer, OSCClient, OSCMessage import redis import socketserver import math import time import argparse import numpy as np import weakref from math import pi as PI import scipy.signal as signal import midi3 OSCinPort = 8012 #oscrun = True # myIP = "127.0.0.1" PL = 0 print ("") print ("Arguments parsing if needed...") argsparser = argparse.ArgumentParser(description="Text Cycling 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("-c","--client",help="LJ client 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.client: ljclient = args.client else: ljclient = 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 lj.Config(redisIP,ljclient,"maxw") white = lj.rgb2int(255,255,255) red = lj.rgb2int(255,0,0) blue = lj.rgb2int(0,0,255) green = lj.rgb2int(0,255,0) points = 512 # maxpoints = 500 samples = points samparray = [0] * samples # Channel 1 midi CC cc1 = [0]*127 # Channel 2 midi CC cc2 = [0]*127 # Mixer LRmixer = 0 LRtype = 'minus' # "minus", 'multiply' # Duplicators duplicators = 1 duplicatorsAngle = 30 Oscillators = [] LFOs = [] Rotators = [] Translators = [] 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. eye_spacing = 100 nadir = 0.5 observer_altitude = 30000 #observer_altitude = 10000 # elevation = z coordinate # 0.0, -2000 pop out map_plane_altitude = 0.0 # Relative Object (name, active, intensity, xy, color, red, green, blue, PL , closed, xpos , ypos , resize , rotx , roty , rotz) #Leftshape = lj.RelativeObject('Leftshape', True, 255, [], red, 255, 0, 0, PL , True, 100 , 100 , 1 , 0 , 0 , 0) # name, intensity, active, xy, color, red, green, blue, PL , closed): Leftshape = lj.FixedObject('Leftshape', True, 255, [], red, 255, 0, 0, PL , False) #Rightshape = lj.FixedObject('Rightshape', True, 255, [], green, 0, 255, 0, PL , False) # 'Destination' for each PL # name, number, active, PL , scene, laser # PL 0 Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) #Dest1 = lj.DestObject('1', 1, True, 0 , 1, 1) ''' viewgen3Lasers = [True,False,False,False] # Add here, one by one, as much destination as you want for each PL. # LJ and OSC can remotely add/delete destinations here. lj.Dests = { "0": {"PL": 0, "scene": 0, "laser": 0}, "1": {"PL": 0, "scene": 1, "laser": 1} } ''' # # 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 # /viewgen/ljclient def OSCljclient(path, tags, args, source): print("Got /viewgen/ljclient with values", args[0]) lj.WebStatus("viewgen to virtual "+ str(args[0])) ljclient = args[0] lj.LjClient(ljclient) # /noteon note velocity def OSCnoteon(path, tags, args, source): note = args[0] velocity = args[1] # Do something with it # /noteoff note def OSCnoteoff(path, tags, args, source): note = args[0] # Do something with it # /cc number value def OSCcc(path, tags, args, source): cc1[args[0]]= args[1] #cc = args[0] #value = args[1] # # CC functions # # /cc cc number value def cc(ccnumber, value): if ccnumber > 127: cc2[ccnumber - 127]= value else: midichannel = basemidichannel cc1[ccnumber]= value #print("Sending Midi channel", midichannel, "cc", ccnumber, "value", value) #midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1,ccnumber,value], learner) def FindCC(FunctionName): for Maxfunction in range(len(maxwell['ccs'])): if FunctionName == maxwell['ccs'][Maxfunction]['Function']: #print(FunctionName, "is CC", Maxfunction) return Maxfunction def LoadCC(): global maxwell print("Loading Maxwell CCs Functions...") f=open("maxwell.json","r") s = f.read() maxwell = json.loads(s) print(len(maxwell['ccs']),"Functions") print("Loaded.") def SendCC(path,init): funcpath = path.split("/") func = funcpath[len(funcpath)-1] if func in specificvalues: value = specificvalues[func][init] else: value = int(init) #print("sending CC", FindCC(path), "with value", value) cc(FindCC(path),value) time.sleep(0.005) # # computing functions # def ssawtooth(samples, freq, phase): samparray = [0] * samples t = np.linspace(0+phase, 1+phase, samples) for ww in range(samples): samparray[ww] = signal.sawtooth(2 * np.pi * freq * t[ww]) return samparray def ssquare(samples, freq, phase): samparray = [0] * samples t = np.linspace(0+phase, 1+phase, samples) for ww in range(samples): samparray[ww] = signal.square(2 * np.pi * freq * t[ww]) return samparray def ssine(samples, freq, phase): samparray = [0] * samples t = np.linspace(0+phase, 1+phase, samples) for ww in range(samples): samparray[ww] = np.sin(2 * np.pi * freq * t[ww]) return samparray ''' def sline(samples, 1): samparray = [0] * samples for ww in range(samples): samparray[ww] = ww return samparray ''' def slinear(samples, min, max): samparray = [0] * samples linearinc = (max-min)/samples for ww in range(samples): if ww == 0: samparray[ww] = min else: samparray[ww] = samparray[ww-1] + linearinc #print('linear min max', min, max) #print ('linear',samparray) return samparray def sconstant(samples, values): samparray = [0] * samples for ww in range(samples): samparray[ww] = values return samparray def remap(s,min1,max1, min2, max2): a1, a2 = min1, max1 b1, b2 = min2, max2 return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) def cc2range(s,min,max): a1, a2 = 0,127 b1, b2 = min, max return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) def range2cc(s,min,max): a1, a2 = min, max b1, b2 = 0,127 return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) def LeftShift(elevation): diff = elevation - map_plane_altitude return nadir * eye_spacing * diff / (observer_altitude - elevation) def RightShift(elevation): diff = map_plane_altitude - elevation return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) 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 Oscillators # specificvalues = { "sine": 0, "saw": 33, "square": 95, "linear": 127, "constant" : 128, "1": 0, "4": 26, "16": 52, "32": 80, "127": 127, "solid": 0, "lfo": 127, "off": 0, "on": 127, "add": 0, "minus": 50, "multiply": 127, "lfo1": 33, "lfo2": 95, "lfo3": 127, "manual": 0, } ''' "colormodtype": { "sin": 0, "linear": 127 } ''' # return v1,v2,v3 or v4 according to "value" 0-127 def vals4(value,v1,v2,v3,v4): if value < 32: return v1 if value > 31 and value < 64: return v2 if value > 61 and value < 96: return v3 if value > 95: return v4 # return v1,v2,v3,v4 or v5 according to "value" 0-127 def vals5(value,v1,v2,v3,v4,v5): if value < 26: return v1 if value > 25 and value < 52: return v2 if value > 51 and value < 80: return v3 if value > 79 and value < 104: return v4 if value > 104: return v5 class OsciObject: _instances = set() counter = 0 kind = 'fixed' def __init__(self, name, curvetype, amp, inversion, baseCC): self.name = name self.baseCC = baseCC # Amplitude 64 values positive and 64 values negative -256 to +256 self.amp = amp # curvetypes : sine, saw, square, linear, constant ? self.curvetype = curvetype self.freqlimit = 4 # Curvetype frequency : 128 possible values between 1 - freqlimit self.freq = 2 # Amplitude Curvetype : constant, lfo1, lfo2, lfo3 self.amptype = 'constant' # Phase modification type : linear or sine. self.phasemodtype = 'linear' # Phase modification 64 speed forward and 64 speed backward. # Speed is increment browsing self.phasemodspeed = 1 #self.phasemodspeed = 0 self.phaseoffsettype = 'manual' self.phaseoffset = 200 self.ampoffset = cc2range(cc1[self.baseCC + 9],0,32) self.ampoffsettype = cc1[self.baseCC + 10] self.inversion = inversion self.phasemodcurve = [0]*points # ssine(points, self.freq, self.phasemodspeed) self.phaseoffsetcurve = ssine(points, self.freq, self.phasemodspeed) self.values = ssine(points, self.freq, self.phasemodspeed) self.counter = 0 self.samples = samples self._instances.add(weakref.ref(self)) OsciObject.counter += 1 # print(self.name, "kind", self.kind, "port", self.port) @classmethod def getinstances(cls): dead = set() for ref in cls._instances: obj = ref() if obj is not None: yield obj else: dead.add(ref) cls._instances -= dead def __del__(self): OsciObject.counter -= 1 def CC2VAR(self): if cc1[self.baseCC] == 128: self.curvetype = "constant" else: self.curvetype = vals4(cc1[self.baseCC], "sine", "saw","square","linear") self.freqlimit = vals5(cc1[self.baseCC + 2],"1","4","16","32","127") self.freq = cc2range(cc1[self.baseCC + 1], 0, self.freqlimit) self.amptype = vals4(cc1[self.baseCC + 4], "constant", "lfo1","lfo2","lfo3") self.amp = cc2range(cc1[self.baseCC + 3] , -256, 256) if cc1[self.baseCC]+ 6 < 64: self.phasemodtype = "linear" else: self.phasemodtype ="sine" # phasemodspeed : 0 to 32 ?, because why not 32 ? to test. self.phasemodspeed = cc2range(cc1[self.baseCC + 5], 0, 32) self.phaseoffsettype = vals4(cc1[self.baseCC + 8], "manual", "lfo1","lfo2","lfo3") # phaseoffset : between 0 to 10 ? self.phaseoffset = cc2range(cc1[self.baseCC + 7], 0, 10) self.ampoffsettype = vals4(cc1[self.baseCC + 10], "manual", "lfo1","lfo2","lfo3") # ampoffset : between 0 to 10 ? self.ampoffset = cc2range(cc1[self.baseCC + 9], 0, 10) self.inversion = cc1[self.baseCC + 11] def VAR2CC(self): ''' /osc/left/X/curvetype is Artnet 0 MIDI Channel 1 CC 0 "sine"/0 - "saw"/33 - "square"/95 - "linear"/127 - "constant"/128 /osc/left/X/freq is Artnet 1 MIDI Channel 1 CC 1 0 - freqlimit /osc/left/X/freqlimit is Artnet 2 MIDI Channel 1 CC 2 "1"/0 - "4"/26 - "16"/52 - "32"/80 - "127"/127 /osc/left/X/amp is Artnet 3 MIDI Channel 1 CC 3 0/-256 - 127/256 /osc/left/X/amptype is Artnet 4 MIDI Channel 1 CC 4 "constant"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 /osc/left/X/phasemodspeed is Artnet 5 MIDI Channel 1 CC 5 0 - 32 ? /osc/left/X/phasemodtype is Artnet 6 MIDI Channel 1 CC 6 : "linear"/ - "sin"/ /osc/left/X/phaseoffset is Artnet 7 MIDI Channel 1 CC 7 0 - 10 ? /osc/left/X/phaseoffsettype is Artnet 8 MIDI Channel 1 CC 8 "manual"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 /osc/left/X/ampoffset is Artnet 9 MIDI Channel 1 CC 9 0 - 10 ? /osc/left/X/ampoffsettype is Artnet 10 MIDI Channel 1 CC 10 "manual"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 /osc/left/X/inversion is Artnet 11 MIDI Channel 1 CC 11 : "off"/0 - "on"/127 ''' cc1[self.baseCC + 3] = range2cc(self.amp, -256, 256) cc1[self.baseCC] = specificvalues[self.curvetype] cc1[self.baseCC + 2] = specificvalues[str(self.freqlimit)] cc1[self.baseCC + 1] = range2cc(self.freq, 0, self.freqlimit) if self.amptype == 'constant': cc1[self.baseCC + 4] = 0 else: cc1[self.baseCC + 4] = specificvalues[self.amptype] # Phase modification type : linear or sine. if self.phasemodtype == 'linear': cc1[self.baseCC + 6] = 0 else: cc1[self.baseCC + 6] = 90 cc1[self.baseCC + 5] = range2cc(self.phasemodspeed, 0, 32) # Phase offset cc1[self.baseCC + 8] = specificvalues[self.phaseoffsettype] cc1[self.baseCC + 7] = range2cc(self.phaseoffset, 0, 10) # Amp offset cc1[self.baseCC + 9] = range2cc(self.ampoffset, 0, 10) cc1[self.baseCC + 10] = specificvalues[self.ampoffsettype] if self.inversion == True: cc1[self.baseCC + 11] = 127 else: cc1[self.baseCC + 11] = 0 def Curve(self): self.values = [0] * points self.ampcurve = [0] * points self.phasemodcurve = [0] * points self.phaseoffsetcurve = [0] * points self.counter += 1 #print ('counter', self.counter) if self.counter == points: self.counter = 0 # Phase offset curve #self.phasemodcurve = slinear(points, -PI, PI) # ssine(points, self.freq, self.phasemodspeed) if self.phaseoffsettype == 'manual': self.phaseoffsetcurve = sconstant(points, self.phaseoffset) if self.phaseoffsettype == 'lfo1': self.phaseoffsetcurve = lfo1.Curve() if self.phaseoffsettype == 'lfo2': self.phaseoffsetcurve = lfo2.Curve() if self.phaseoffsettype == 'lfo3': self.phaseoffsetcurve = lfo3.Curve() # Phase mod curve : phasemodspeed is 'speed' of change if self.phasemodtype == 'linear': self.phasemodcurve = slinear(points, -PI*self.phasemodspeed, PI*self.phasemodspeed) if self.phasemodtype == 'lfo1': self.phasemodcurve = lfo1.Curve() if self.phasemodtype == 'lfo2': self.phasemodcurve = lfo2.Curve() if self.phasemodtype == 'lfo3': self.phasemodcurve = lfo3.Curve() self.phasemodspeed = self.phasemodcurve[self.counter] #print('counter', self.counter, 'phasemod',self.phasemodspeed) # Base values curve, trigo functions between -1 and + 1 if self.curvetype == 'sine': self.ampcurve = ssine(points, self.freq, self.phasemodspeed) if self.curvetype == 'saw': self.ampcurve = ssawtooth(points, self.freq, self.phasemodspeed) if self.curvetype == 'square': self.ampcurve = ssquare(points, self.freq, self.phasemodspeed) if self.curvetype == 'linear': self.ampcurve = slinear(points, -1, 1) if self.curvetype == 'constant': self.ampcurve = sconstant(points, self.freq) for point in range(points): # curve points = base curve * amp + curve modifier if self.amptype == 'constant': self.values[point] = self.ampcurve[point] * self.amp if self.amptype == 'lfo1': self.values[point] = (self.ampcurve[point] * self.amp) + (lfo1.values[point] * self.amp) if self.amptype == 'lfo2': self.values[point] = (self.ampcurve[point] * self.amp) + (lfo2.values[point] * self.amp) if self.amptype == 'lfo3': self.values[point] = (self.ampcurve[point] * self.amp) + (lfo3.values[point] * self.amp) if self.inversion == True: self.values = self.values[::-1] # # LFOs # class LFObject: _instances = set() counter = 0 kind = 'fixed' def __init__(self, name): self.name = name self.freqlimit = 4 self.freq = 1 self.curvetype = 'sine' # -1 1 self.phasemodspeed = 0 self.inversion = False self.values = ssine(points, self.freq, self.phasemodspeed) self._instances.add(weakref.ref(self)) LFObject.counter += 1 #print(self.name, "type", self.curvetype, "freq", self.freq) @classmethod def getinstances(cls): dead = set() for ref in cls._instances: obj = ref() if obj is not None: yield obj else: dead.add(ref) cls._instances -= dead def Curve(self): #print(self.name, "type", self.curvetype, "freq", self.freq) self.values = [0]*points if self.curvetype == 'sine': self.values = ssine(points, self.freq, self.phasemodspeed) if self.curvetype == 'saw': self.values = ssawtooth(points, self.freq, self.phasemodspeed) if self.curvetype == 'square': self.values = ssquare(points, self.freq, self.phasemodspeed) if self.curvetype == 'linear': self.values = slinear(points, self.freq) if self.curvetype == 'constant': self.values = sconstant(points, self.freq) if self.inversion == True: self.values = self.values[::-1] def __del__(self): LFObject.counter -= 1 # # Rotators # class RotatorObject: ''' # anim format (name, xpos, ypos, resize, currentframe, totalframe, count, speed) # 0 1 2 3 4 5 6 7 # total frames is fetched from directory by lengthPOSE() #anims[0] = ['boredhh' , xy_center[0] - 100, xy_center[1] + 30, 550, 0, 0, 0, animspeed] anim[4] = anim[4]+anim[7] if anim[4] >= anim[5]: anim[4] = 0 ''' _instances = set() counter = 0 kind = 'fixed' def __init__(self, name): self.name = name self.curvetype = 'constant' self.speed = 0 self.lfo = False self.direction = 0 self._instances.add(weakref.ref(self)) RotatorObject.counter += 1 @classmethod def getinstances(cls): dead = set() for ref in cls._instances: obj = ref() if obj is not None: yield obj else: dead.add(ref) cls._instances -= dead def __del__(self): RotatorObject.counter -= 1 def Curve(self): self.values = [0]*points if self.curvetype == 'sine': self.values = ssine(points, self.direction, self.phasemodspeed) if self.curvetype == 'saw': self.values = ssawtooth(points, self.direction, self.phasemodspeed) if self.curvetype == 'square': self.values = ssquare(points, self.direction, self.phasemodspeed) if self.curvetype == 'linear': self.values = slinear(points, self.direction) if self.curvetype == 'constant': self.values = sconstant(points, 0) # # Translators # class TranslatorObject: _instances = set() counter = 0 kind = 'fixed' def __init__(self, name, amt, speed): self.name = name self.curvetype = 'constant' self.speed = speed self.lfo = False self.amt = amt #self.values = ssine(points, self.amt, self.speed) self.values = sconstant(points, self.amt) self._instances.add(weakref.ref(self)) TranslatorObject.counter += 1 @classmethod def getinstances(cls): dead = set() for ref in cls._instances: obj = ref() if obj is not None: yield obj else: dead.add(ref) cls._instances -= dead def __del__(self): TranslatorObject.counter -= 1 def Curve(self): self.values = [0]*points if self.curvetype == 'sine': self.values = ssine(points, self.amt, self.speed) if self.curvetype == 'saw': self.values = ssawtooth(points, self.amt, self.speed) if self.curvetype == 'square': self.values = ssquare(points, self.amt, self.speed) if self.curvetype == 'linear': self.values = slinear(points, self.amt) if self.curvetype == 'constant': self.values = sconstant(points, self.amt) # # Scaler # Scalercurvetype = 'constant' Scalercurve = [0.05] * points Scalerspeed = 0 Scalerbutton = False Scalerwidth = 0 Scaleramt = 0 def ScalerCurve(): Scalercurve = [0]*points if Scalercurvetype == 'sine': Scalercurve = ssine(points, Scaleramt, Scalerspeed) if Scalercurvetype == 'saw': Scalercurve = ssawtooth(points, Scaleramt, Scalerspeed) if Scalercurvetype == 'square': Scalercurve = ssquare(points, Scaleramt, Scalerspeed) if Scalercurvetype == 'linear': Scalercurve = slinear(points, Scaleramt) if Scalercurvetype == 'constant': Scalercurve = sconstant(points, 0.05) # # Main # def Run(): Left = [] Right = [] counter =0 lj.WebStatus("Maxwellator") lj.SendLJ("/maxw/start 1") # OSC # OSC Server callbacks print("Starting OSC server at",myIP," port",OSCinPort,"...") oscserver.addMsgHandler( "/maxw/ljclient", OSCljclient ) # You will receive midi callbacks in OSC messages form if this plugin is in midi2OSC list in midi3.py and midi3.py is imported somewhere oscserver.addMsgHandler( "/noteon", OSCnoteon) oscserver.addMsgHandler( "/noteoff", OSCnoteoff) oscserver.addMsgHandler( "/cc", OSCnoteon) # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest lj.addOSCdefaults(oscserver) # Drawing parameters # LFOs lfo1 = LFObject("lfo1") lfo2 = LFObject("lfo2") lfo3 = LFObject("lfo3") # Rotators rotX = RotatorObject("rotX") rotY = RotatorObject("rotY") rotZ = RotatorObject("rotZ") # Translators : name amount speed transX = TranslatorObject("transX",0,0) transY = TranslatorObject("transY",0,0) transZ = TranslatorObject("transZ",0,0) Scaler = ScalerCurve() # Left parameters : name, type, amp, inversion, base midi CC leftX = OsciObject("leftX", "sine", 30, False, 0) leftY = OsciObject("leftY", "sine", 30, True, 12) leftZ = OsciObject("leftZ", "constant", 0, False, 24) # Right parameters : name, type, amp, inversion, base midi CC rightX = OsciObject("rightX", "saw", 30, False, 36) rightY = OsciObject("rightY", 'saw', 30, True, 48) rightZ = OsciObject("rightZ", 'constant', 0, False, 60) try: while lj.oscrun: lj.OSCframe() Left = [] Right = [] lfo1.Curve() lfo2.Curve() lfo3.Curve() transX.Curve() transY.Curve() transZ.Curve() rotX.Curve() rotY.Curve() rotZ.Curve() leftX.Curve() leftY.Curve() leftZ.Curve() rightX.Curve() rightY.Curve() rightZ.Curve() for point in range(points): if LRtype == 'add': CurveX = (leftX.values[point]*(100-LRmixer)/100) + (rightX.values[point]*LRmixer/100) + transX.values[point] CurveY = (leftY.values[point]*(100-LRmixer)/100) + (rightY.values[point]*LRmixer/100) + transY.values[point] CurveZ = (leftZ.values[point]*(100-LRmixer)/100) + (rightZ.values[point]*LRmixer/100) + transZ.values[point] if LRtype == 'minus': CurveX = (leftX.values[point]*(100-LRmixer)/100) - (rightX.values[point]*LRmixer/100) + transX.values[point] CurveY = (leftY.values[point]*(100-LRmixer)/100) - (rightY.values[point]*LRmixer/100) + transY.values[point] CurveZ = (leftZ.values[point]*(100-LRmixer)/100) - (rightZ.values[point]*LRmixer/100) + transZ.values[point] if LRtype == 'multiply': CurveX = (leftX.values[point]*(100-LRmixer)/100) * (rightX.values[point]*LRmixer/100) + transX.values[point] CurveY = (leftY.values[point]*(100-LRmixer)/100) * (rightY.values[point]*LRmixer/100) + transY.values[point] CurveZ = (leftZ.values[point]*(100-LRmixer)/100) * (rightZ.values[point]*LRmixer/100) + transZ.values[point] Left.append(Proj(CurveX+LeftShift(CurveZ*25), CurveY, CurveZ, 0, 0, 0)) #Right.append(Proj(CurveX+RightShift(CurveZ*25), CurveY, CurveZ, 0, 0, 0)) for clone in range(duplicators): # Drawing step, 2 possibilities # Red and Green drawn by laser 0 lj.rPolyLineOneColor(Left, c = Leftshape.color , PL = Leftshape.PL, closed = Leftshape.closed, xpos = 350, ypos = 350, resize = Scalercurve[0], rotx = rotX.values[0] + (clone * duplicatorsAngle), roty = rotY.values[0] + (clone * duplicatorsAngle), rotz = rotZ.values[0] + (clone * duplicatorsAngle)) #lj.PolyLineOneColor(Right, c = Rightshape.color , PL = Rightshape.PL, closed = Rightshape.closed) #lj.DrawPL(PL) lj.DrawDests() time.sleep(0.01) counter += 1 if counter > 360: counter = 0 except KeyboardInterrupt: pass # Gently stop on CTRL C finally: lj.ClosePlugin() Run()