diff --git a/direct/src/configfiles/direct.pth b/direct/src/configfiles/direct.pth index 09c841d4d0..8be6d58c37 100644 --- a/direct/src/configfiles/direct.pth +++ b/direct/src/configfiles/direct.pth @@ -2,6 +2,7 @@ lib lib/py src/actor src/directtools +src/directdevices src/directnotify src/distributed src/ffi diff --git a/direct/src/directdevices/DirectDeviceManager.py b/direct/src/directdevices/DirectDeviceManager.py new file mode 100644 index 0000000000..6e48b032c7 --- /dev/null +++ b/direct/src/directdevices/DirectDeviceManager.py @@ -0,0 +1,193 @@ +""" Class used to create and control vrpn devices """ +from PandaObject import * + +class DirectDeviceManager(VrpnClient, PandaObject): + def __init__(self, server = None): + # Determine which server to use + if server != None: + # One give as constructor argument + self.server = server + else: + # Check config file, if that fails, use default + self.server = base.config.GetString('vrpn-server', 'spacedyne') + + # Create a vrpn client + VrpnClient.__init__(self, self.server) + + def createButtons(self, device): + return DirectButtons(self, device) + + def createAnalogs(self, device): + return DirectAnalogs(self, device) + + def createDials(self, device): + return DirectDials(self, device) + + def createTimecodeReader(self, device): + return DirectTimecodeReader(self, device) + +class DirectButtons(ButtonNode, PandaObject): + buttonCount = 0 + def __init__(self, vrpnClient, device): + # Keep track of number of buttons created + DirectButtons.buttonCount += 1 + # Create a unique name for this button object + self.name = 'DirectButtons-' + `DirectButtons.buttonCount` + # Create a new button node for the given device + ButtonNode.__init__(self, vrpnClient, device) + # Attach node to data graph + self.nodePath = base.dataRoot.attachNewNode(self) + + def __getitem__(self, index): + if (index < 0) | (index > self.getNumButtons()): + raise IndexError + return self.getButtonState(index) + + def __len__(self): + return self.getNumButtons() + + def enable(self): + self.nodePath.reparentTo(base.dataRoot) + + def disable(self): + self.nodePath.reparentTo(base.dataUnused) + + def getName(self): + return self.name + + def getNodePath(self): + return self.nodePath + + def __repr__(self): + str = self.name + ': ' + for val in self: + str = str + '%d' % val + ' ' + return str + +class DirectAnalogs(AnalogNode, PandaObject): + analogCount = 0 + def __init__(self, vrpnClient, device): + # Keep track of number of analogs created + DirectAnalogs.analogCount += 1 + # Create a unique name for this analog object + self.name = 'DirectAnalogs-' + `DirectAnalogs.analogCount` + # Create a new analog node for the given device + AnalogNode.__init__(self, vrpnClient, device) + # Attach node to data graph + self.nodePath = base.dataRoot.attachNewNode(self) + + def __getitem__(self, index): + if (index < 0) | index > self.getNumControls(): + raise IndexError + return self.getControlState(index) + + def __len__(self): + return self.getNumControls() + + def enable(self): + self.nodePath.reparentTo(base.dataRoot) + + def disable(self): + self.nodePath.reparentTo(base.dataUnused) + + def getName(self): + return self.name + + def getNodePath(self): + return self.nodePath + + def __repr__(self): + str = self.name + ': ' + for val in self: + str = str + '%.3f' % val + ' ' + return str + +class DirectDials(DialNode, PandaObject): + dialCount = 0 + def __init__(self, vrpnClient, device): + # Keep track of number of dials created + DirectDials.dialCount += 1 + # Create a unique name for this dial object + self.name = 'DirectDials-' + `DirectDials.dialCount` + # Create a new dial node for the given device + DialNode.__init__(self, vrpnClient, device) + # Attach node to data graph + self.nodePath = base.dataRoot.attachNewNode(self) + + def __getitem__(self, index): + if (index < 0) | (index > self.getNumDials()): + raise IndexError + return self.readDial(index) + + def __len__(self): + return self.getNumDials() + + def enable(self): + self.nodePath.reparentTo(base.dataRoot) + + def disable(self): + self.nodePath.reparentTo(base.dataUnused) + + def getName(self): + return self.name + + def getNodePath(self): + return self.nodePath + + def __repr__(self): + str = self.name + ': ' + for val in self: + str = str + '%.3f' % val + ' ' + return str + +class DirectTimecodeReader(AnalogNode, PandaObject): + timecodeReaderCount = 0 + def __init__(self, vrpnClient, device): + # Keep track of number of timecodeReader created + DirectTimecodeReader.timecodeReaderCount += 1 + # Create a unique name for this dial object + self.name = ('DirectTimecodeReader-' + + `DirectTimecodeReader.timecodeReaderCount`) + # Initialize components of timecode + self.frames = 0 + self.seconds = 0 + self.minutes = 0 + self.hours = 0 + # Create a new dial node for the given device + AnalogNode.__init__(self, vrpnClient, device) + # Attach node to data graph + self.nodePath = base.dataRoot.attachNewNode(self) + + def enable(self): + self.nodePath.reparentTo(base.dataRoot) + + def disable(self): + self.nodePath.reparentTo(base.dataUnused) + + def getName(self): + return self.name + + def getNodePath(self): + return self.nodePath + + def getTime(self): + # Assume only one card, use channel 0 + timeBits = int(self.getControlState(0)) + self.frames = ((timeBits & 0xF) + + (((timeBits & 0xF0) >> 4) * 10)) + self.seconds = (((timeBits & 0x0F00) >> 8) + + (((timeBits & 0xF000) >> 12) * 10)) + self.minutes = ((((timeBits & 0x0F0000) >> 16) * 10) + + ((timeBits & 0xF00000) >> 20)) + self.hours = ((((timeBits & 0xF0000000) >> 24) * 10) + + ((timeBits & 0xF0000000) >> 28)) + self.totalSeconds = ((self.hours * 3600) + + (self.minutes * 60) + + self.seconds + + (self.frames / 30.0)) + return (self.hours, self.minutes, self.seconds, self.frames, + self.totalSeconds) + + def __repr__(self): + str = ('%s: %d:%d:%d:%d' % ((self.name,) + self.getTime()[:-1])) + return str diff --git a/direct/src/directdevices/DirectJoybox.py b/direct/src/directdevices/DirectJoybox.py new file mode 100644 index 0000000000..2dfbc21527 --- /dev/null +++ b/direct/src/directdevices/DirectJoybox.py @@ -0,0 +1,209 @@ +""" Class used to create and control joybox device """ +from PandaObject import * +from DirectDeviceManager import * +from DirectGeometry import CLAMP +import OnscreenText + +JOY_MIN = -0.95 +JOY_MAX = 0.95 +JOY_RANGE = JOY_MAX - JOY_MIN +JOY_DEADBAND = 0.05 +# BUTTONS +L_STICK = 0 +L_UPPER = 1 +L_LOWER = 2 +R_STICK = 3 +R_UPPER = 4 +R_LOWER = 5 +# ANALOGS +L_LEFT_RIGHT = 0 +L_FWD_BACK = 1 +L_TWIST = 2 +L_SLIDE = 3 +R_LEFT_RIGHT = 4 +R_FWD_BACK = 5 +R_TWIST = 6 +R_SLIDE = 7 + +class DirectJoybox(PandaObject): + joyboxCount = 0 + xyzScale = 1.0 + hprScale = 1.0 + def __init__(self, nodePath = direct.camera): + # See if device manager has been initialized + if direct.deviceManager == None: + direct.deviceManager = DirectDeviceManager() + # Set name + self.name = 'Joybox-' + `DirectJoybox.joyboxCount` + # Get buttons and analogs + self.device = base.config.GetString('joybox-device', 'CerealBox') + self.analogs = direct.deviceManager.createAnalogs(self.device) + self.buttons = direct.deviceManager.createButtons(self.device) + self.aList = [0,0,0,0,0,0,0,0] + self.bList = [0,0,0,0,0,0,0,0] + self.mapping = [0,1,2,4,5,6] + self.modifier = [1,1,1,1,1,1] + # Button registry + self.addButtonEvents() + # Initialize time + self.lastTime = globalClock.getTime() + # Record node path + self.nodePath = nodePath + # Text object to display current mode + self.readout = OnscreenText.OnscreenText( '', -0.9, -0.95 ) + # Pick initial mode + self.updateFunc = self.joeFly + # Spawn update task + self.enable() + + def enable(self): + taskMgr.spawnMethodNamed(self.updateTask, self.name + '-updateTask') + + def disable(self): + taskMgr.removeTasksNamed(self.name + '-updateTask') + + def addButtonEvents(self): + self.breg = ButtonRegistry.ptr() + # MRM: Hard coded! + for i in range(8): + self.buttons.setButtonMap( + i, self.breg.getButton(self.getEventName(i))) + self.eventThrower = self.buttons.getNodePath().attachNewNode( + ButtonThrower()) + + def setNodePath(self, nodePath): + self.nodePath = nodePath + def getNodePath(self): + return self.nodePath + def getEventName(self, index): + return self.name + '-button-' + `index` + + def updateTask(self, state): + self.updateVals() + self.updateFunc() + return Task.cont + + def updateVals(self): + # Update delta time + cTime = globalClock.getTime() + self.deltaTime = cTime - self.lastTime + self.lastTime = cTime + # Update analogs + for i in range(len(self.analogs)): + try: + self.aList[i] = self.normalizeAnalogChannel(i) + except IndexError: + # That channel may not have been updated yet + pass + # Update buttons + for i in range(len(self.buttons)): + try: + self.bList[i] = self.buttons[i] + except IndexError: + # That channel may not have been updated yet + pass + + def normalizeAnalog(self, val, min = -1, max = -1): + val = CLAMP(val, JOY_MIN, JOY_MAX) + if abs(val) < JOY_DEADBAND: + val = 0.0 + return ((max - min) * ((val - JOY_MIN) / JOY_RANGE)) + min + + def normalizeAnalogChannel(self, chan, min = -1, max = 1): + if (chan == 2) | (chan == 6): + return self.normalizeAnalog(self.analogs[chan] * 3.0, min, max) + else: + return self.normalizeAnalog(self.analogs[chan], min, max) + + def showMode(self, modeText): + def hideText(state, s = self): + s.readout.setText('') + return Task.done + taskMgr.removeTasksNamed(self.name + '-showMode') + # Update display + self.readout.setText(modeText) + t = taskMgr.doMethodLater(3.0, hideText, self.name + '-showMode') + t.uponDeath = hideText + + def setMode(self, func, name): + self.disable() + self.updateFunc = func + self.showMode(name) + self.enable() + + def joeMode(self): + self.setMode(self.joeFly, 'Joe Mode') + + def joeFly(self): + hprScale = (self.normalizeAnalogChannel(3, 0.1, 200) * + DirectJoybox.hprScale) + posScale = (self.normalizeAnalogChannel(7, 0.1, 100) * + DirectJoybox.xyzScale) + # XYZ + x = self.aList[4] + y = self.aList[5] + if self.bList[L_STICK]: + z = 0.0 + else: + z = self.aList[L_FWD_BACK] + pos = Vec3(x,y,z) * (posScale * self.deltaTime) + # HPR + h = -1 * self.aList[R_TWIST] + if self.bList[L_STICK]: + p = -1 * self.aList[L_FWD_BACK] + else: + p = 0.0 + r = 0.0 + hpr = Vec3(h,p,r) * (hprScale * self.deltaTime) + # Move node path + self.nodePath.setPosHpr(self.nodePath, pos, hpr) + + def joyboxFly(self): + hprScale = (self.normalizeAnalogChannel(3, 0.1, 200) * + DirectJoybox.hprScale) + posScale = (self.normalizeAnalogChannel(7, 0.1, 100) * + DirectJoybox.xyzScale) + x = self.analogs[self.mapping[0]] * self.modifier[0] + y = self.analogs[self.mapping[1]] * self.modifier[1] + z = self.analogs[self.mapping[2]] * self.modifier[2] + pos = Vec3(x,y,z) * (posScale * self.deltaTime) + + h = self.analogs[self.mapping[3]] * self.modifier[3] + p = self.analogs[self.mapping[4]] * self.modifier[4] + r = self.analogs[self.mapping[5]] * self.modifier[5] + hpr = Vec3(h,p,r) * (hprScale * self.deltaTime) + # Move node path + self.nodePath.setPosHpr(self.nodePath, pos, hpr) + + def demoMode(self): + self.mapping = [4,5,1,6,0,0] + self.modifier = [1,1,1,-1,0,0] + self.setMode(self.joyboxFly, 'Demo Mode') + + def driveMode(self): + self.mapping = [0,5,1,4,1,0] + self.modifier = [1,1,1,-1,0,0] + self.setMode(self.joyboxFly, 'Drive Mode') + + def hprXyzMode(self): + self.mapping = [4,5,6,2,1,0] + self.modifier = [1,1,-1,-1,-1,1] + self.setMode(self.joyboxFly, 'HprXyz Mode') + + def lookaroundMode(self): + self.mapping = [0,0,0,4,5,0] + self.modifier = [0,0,0,-1,-1,0] + self.setMode(self.joyboxFly, 'Lookaround Mode') + + def walkthruMode(self): + self.mapping = [4,5,2,6,1,0] + self.modifier = [1,1,-1,-1,-1, 1] + self.setMode(self.joyboxFly, 'Walkthru Mode') + +def jbTest(): + jb = DirectJoybox() + jb.joeMode() + jb.accept(jb.getEventName(R_UPPER), jb.joeMode) + direct.cameraControl.accept(jb.getEventName(L_UPPER), + direct.cameraControl.orbitUprightCam) + return jb diff --git a/direct/src/directtools/DirectSession.py b/direct/src/directtools/DirectSession.py index 51918d24a5..bd2e68c255 100644 --- a/direct/src/directtools/DirectSession.py +++ b/direct/src/directtools/DirectSession.py @@ -6,6 +6,7 @@ from DirectGrid import * from DirectGeometry import * from DirectLights import * from DirectSessionPanel import * +from DirectDeviceManager import * import Placer import OnscreenText import types @@ -50,6 +51,12 @@ class DirectSession(PandaObject): # self.readout.textNode.setCardColor(0.5, 0.5, 0.5, 0.5) self.readout.reparentTo( hidden ) + # Create a vrpn client vrpn-server or default + if base.config.GetBool('want-vrpn', 0): + self.deviceManager = DirectDeviceManager() + else: + self.deviceManager = None + self.fControl = 0 self.fAlt = 0 self.fShift = 0 diff --git a/direct/src/particles/ForceGroup.py b/direct/src/particles/ForceGroup.py index dc6b136b43..aa432b34fe 100644 --- a/direct/src/particles/ForceGroup.py +++ b/direct/src/particles/ForceGroup.py @@ -71,6 +71,8 @@ class ForceGroup(DirectObject): # Utility functions def __getitem__(self, index): """__getItem__(self, index)""" + if (index < 0) | (index > self.node.getNumForces()): + raise IndexError return self.node.getForce(index) def __len__(self): diff --git a/direct/src/particles/ParticleEffect.py b/direct/src/particles/ParticleEffect.py index d435d91d99..6ca7ffe55d 100644 --- a/direct/src/particles/ParticleEffect.py +++ b/direct/src/particles/ParticleEffect.py @@ -49,8 +49,7 @@ class ParticleEffect(NodePath): self.forceGroupDict[forceGroup.getName()] = forceGroup # Associate the force group with all particles - flist = forceGroup.asList() - for f in flist: + for f in forceGroup: self.addForce(f) def addForce(self, force): @@ -65,8 +64,7 @@ class ParticleEffect(NodePath): self.forceGroupDict[forceGroup.getName()] = None # Remove forces from all particles - flist = forceGroup.asList() - for f in flist: + for f in forceGroup: self.removeForce(f) def removeForce(self, force): @@ -81,8 +79,7 @@ class ParticleEffect(NodePath): # Associate all forces in all force groups with the particles for fg in self.forceGroupDict.values(): - flist = fg.asList() - for f in flist: + for f in fg: particles.addForce(f) def removeParticles(self, particles): @@ -92,8 +89,7 @@ class ParticleEffect(NodePath): # Remove all forces from the particles for fg in self.forceGroupDict.values(): - flist = fg.asList() - for f in flist: + for f in fg: particles.removeForce(f) def getParticlesList(self): diff --git a/direct/src/tkpanels/ParticlePanel.py b/direct/src/tkpanels/ParticlePanel.py index 9a132e1886..cb99487011 100644 --- a/direct/src/tkpanels/ParticlePanel.py +++ b/direct/src/tkpanels/ParticlePanel.py @@ -1627,7 +1627,7 @@ class ParticlePanel(AppShell): forceGroup.getName()) self.forcePage = self.forceGroupNotebook.add(self.forcePageName) self.forcePagesDict[self.forcePageName] = self.forcePage - for force in forceGroup.asList(): + for force in forceGroup: self.addForceWidget(forceGroup, force) def addForceWidget(self, forceGroup, force): @@ -1635,7 +1635,7 @@ class ParticlePanel(AppShell): pageName = self.forcePageName # How many forces of the same type in the force group object count = 0 - for f in forceGroup.asList(): + for f in forceGroup: if f.getClassType().eq(force.getClassType()): count += 1 if isinstance(force, LinearVectorForce):