diff --git a/direct/src/directutil/DirectCameraControl.py b/direct/src/directutil/DirectCameraControl.py index 465b033807..911e7ec86d 100644 --- a/direct/src/directutil/DirectCameraControl.py +++ b/direct/src/directutil/DirectCameraControl.py @@ -1,449 +1,448 @@ -from PandaObject import * - -CAM_MOVE_DURATION = 1.0 -Y_AXIS = Vec3(0,1,0) - -class DirectCameraControl(PandaObject): - def __init__(self, direct): - # Create the grid - self.direct = direct - self.chan = direct.chan - self.camera = self.chan.camera - self.orthoViewRoll = 0.0 - self.lastView = 0 - self.coa = Point3(0) - self.coaMarker = loader.loadModel('models/misc/sphere') - self.coaMarker.setColor(1,0,0) - self.coaMarkerPos = Point3(0) - self.relNodePath = render.attachNewNode(NamedNode('targetNode')) - self.zeroBaseVec = VBase3(0) - self.zeroVector = Vec3(0) - self.centerVec = Vec3(0, 1, 0) - self.zeroPoint = Point3(0) - - def mouseFlyStart(self, chan): - # Record starting mouse positions - self.initMouseX = chan.mouseX - self.initMouseY = chan.mouseY - # Where are we in the channel? - if ((abs(self.initMouseX) < 0.9) & (abs(self.initMouseY) < 0.9)): - # MOUSE IS IN CENTRAL REGION - # Hide the marker for this kind of motion - self.coaMarker.hide() - # See if the shift key is pressed - if (self.direct.fShift): - # If shift key is pressed, just perform horiz and vert pan: - self.spawnHPPan() - else: - # Otherwise, check for a hit point based on - # current mouse position - # And then spawn task to determine mouse mode - numEntries = self.direct.iRay.pickGeom( - render,chan.mouseX,chan.mouseY) - # Filter out hidden nodes from entry list - indexList = [] - for i in range(0,numEntries): - entry = self.direct.iRay.cq.getEntry(i) - node = entry.getIntoNode() - if node.isHidden(): - pass - else: - # Not one of the widgets, use it - indexList.append(i) - coa = Point3(0) - if(indexList): - # Start off with first point - minPt = indexList[0] - # Find hit point in camera's space - hitPt = self.direct.iRay.camToHitPt(minPt) - coa.set(hitPt[0],hitPt[1],hitPt[2]) - coaDist = Vec3(coa - self.zeroPoint).length() - # Check other intersection points, sorting them - # TBD: Use TBS C++ function to do this - if len(indexList) > 1: - for i in range(1,len(indexList)): - entryNum = indexList[i] - hitPt = self.direct.iRay.camToHitPt(entryNum) - dist = Vec3(hitPt - self.zeroPoint).length() - if (dist < coaDist): - coaDist = dist - coa.set(hitPt[0],hitPt[1],hitPt[2]) - minPt = i - - # Handle case of bad coa point (too close or too far) - if ((coaDist < (1.1 * self.chan.near)) | - (coaDist > self.chan.far)): - # Put it out in front of the camera - coa.set(0,100,0) - coaDist = 100 - else: - # If no intersection point: - # Put coa out in front of the camera - coa.set(0,100,0) - coaDist = 100 - - # Update coa and marker - self.updateCoa(coa, coaDist) - # Now spawn task to determine mouse fly mode - self.determineMouseFlyMode() - # END MOUSE IN CENTRAL REGION - else: - # Mouse is in outer frame, spawn mouseRotateTask - self.spawnMouseRotateTask() - - def mouseFlyStop(self): - taskMgr.removeTasksNamed('determineMouseFlyMode') - taskMgr.removeTasksNamed('manipulateCamera') - # Show the marker - self.coaMarker.show() - # Resize it - self.updateCoaMarkerSize() - - def determineMouseFlyMode(self): - # Otherwise, determine mouse fly mode - t = Task.Task(self.determineMouseFlyModeTask) - taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode') - - def determineMouseFlyModeTask(self, state): - deltaX = self.chan.mouseX - self.initMouseX - deltaY = self.chan.mouseY - self.initMouseY - if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)): - return Task.cont - else: - if (abs(deltaY) > 0.1): - self.spawnHPanYZoom() - else: - self.spawnXZTranslate() - return Task.done - - def updateCoa(self, cam2point, coaDist = None): - self.coa.set(cam2point[0], cam2point[1], cam2point[2]) - if coaDist: - self.coaDist = coaDist - else: - self.coaDist = Vec3(self.coa - self.zeroPoint).length() - # Place the marker in render space - self.coaMarker.setPos(self.camera,self.coa) - # Resize it - self.updateCoaMarkerSize(coaDist) - # Record marker pos in render space - self.coaMarkerPos.assign(self.coaMarker.getPos()) - - def updateCoaMarkerSize(self, coaDist = None): - if not coaDist: - coaDist = Vec3(self.coaMarker.getPos( self.chan.camera )).length() - self.coaMarker.setScale(0.01 * coaDist * - math.tan(deg2Rad(self.chan.fovV))) - - def homeCam(self, chan): - chan.camera.setMat(Mat4.identMat()) - - def uprightCam(self, chan): - taskMgr.removeTasksNamed('manipulateCamera') - currH = chan.camera.getH() - chan.camera.lerpHpr(currH, 0, 0, - CAM_MOVE_DURATION, - other = render, - blendType = 'easeInOut', - task = 'manipulateCamera') - - def centerCam(self, chan): - # Chan is a display region context - self.centerCamIn(chan, 1.0) - - def centerCamNow(self, chan): - self.centerCamIn(chan, 0.) - - def centerCamIn(self, chan, t): - # Chan is a display region context - taskMgr.removeTasksNamed('manipulateCamera') - markerToCam = self.coaMarker.getPos( chan.camera ) - dist = Vec3(markerToCam - self.zeroPoint).length() - scaledCenterVec = self.centerVec * dist - delta = markerToCam - scaledCenterVec - self.relNodePath.setPosHpr(chan.camera, Point3(0), Point3(0)) - chan.camera.lerpPos(Point3(delta), - CAM_MOVE_DURATION, - other = self.relNodePath, - blendType = 'easeInOut', - task = 'manipulateCamera') - - def zoomCam(self, chan, zoomFactor, t): - taskMgr.removeTasksNamed('manipulateCamera') - # Find a point zoom factor times the current separation - # of the widget and cam - zoomPtToCam = self.coaMarker.getPos(chan.camera) * zoomFactor - # Put a target nodePath there - self.relNodePath.setPos(chan.camera, zoomPtToCam) - # Move to that point - chan.camera.lerpPos(self.zeroPoint, - CAM_MOVE_DURATION, - other = self.relNodePath, - blendType = 'easeInOut', - task = 'manipulateCamera') - - def SpawnMoveToView(self, chan, view): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # Calc hprOffset - hprOffset = VBase3() - if view == 8: - # Try the next roll angle - self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0 - # but use the last view - view = self.lastView - else: - self.orthoViewRoll = 0.0 - # Adjust offset based on specified view - if view == 1: - hprOffset.set(180., 0., 0.) - elif view == 2: - hprOffset.set(0., 0., 0.) - elif view == 3: - hprOffset.set(90., 0., 0.) - elif view == 4: - hprOffset.set(-90., 0., 0.) - elif view == 5: - hprOffset.set(0., -90., 0.) - elif view == 6: - hprOffset.set(0., 90., 0.) - elif view == 7: - hprOffset.set(135., -35.264, 0.) - # Position target - self.relNodePath.setPosHpr(self.coaMarker, self.zeroBaseVec, - hprOffset) - # Scale center vec by current distance to target - offsetDistance = Vec3(chan.camera.getPos(self.relNodePath) - - self.zeroPoint).length() - scaledCenterVec = self.centerVec * (-1.0 * offsetDistance) - # Now put the relNodePath at that point - self.relNodePath.setPosHpr(self.relNodePath, - scaledCenterVec, - self.zeroBaseVec) - # Record view for next time around - self.lastView = view - chan.camera.lerpPosHpr(self.zeroPoint, - VBase3(0,0,self.orthoViewRoll), - CAM_MOVE_DURATION, - other = self.relNodePath, - blendType = 'easeInOut', - task = 'manipulateCamera') - - - def swingCamAboutWidget(self, chan, degrees, t): - # Remove existing camera manipulation task - taskMgr.removeTasksNamed('manipulateCamera') - - # Coincident with widget - self.relNodePath.setPos(self.coaMarker, self.zeroPoint) - # But aligned with render space - self.relNodePath.setHpr(self.zeroPoint) - - parent = self.camera.getParent() - self.camera.wrtReparentTo(self.relNodePath) - - manipTask = self.relNodePath.lerpHpr(VBase3(degrees,0,0), - CAM_MOVE_DURATION, - blendType = 'easeInOut', - task = 'manipulateCamera') - # Upon death, reparent Cam to parent - manipTask.parent = parent - manipTask.uponDeath = self.reparentCam - - def reparentCam(self, state): - self.camera.wrtReparentTo(state.parent) - - def spawnHPanYZoom(self): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # hide the marker - self.coaMarker.hide() - # Negate vec to give it the correct sense for mouse motion below - targetVector = self.coa * -1 - t = Task.Task(self.HPanYZoomTask) - t.targetVector = targetVector - taskMgr.spawnTaskNamed(t, 'manipulateCamera') - - def HPanYZoomTask(self,state): - targetVector = state.targetVector - distToMove = targetVector * self.chan.mouseDeltaY - self.camera.setPosHpr(self.camera, - distToMove[0], - distToMove[1], - distToMove[2], - (0.5 * self.chan.mouseDeltaX * - self.chan.fovH), - 0.0, 0.0) - return Task.cont - - - def spawnXZTranslateOrHPPan(self): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # Hide the marker - self.coaMarker.hide() - t = Task.Task(self.XZTranslateOrHPPanTask) - t.scaleFactor = (self.coaDist / self.chan.near) - taskMgr.spawnTaskNamed(t, 'manipulateCamera') - - def XZTranslateOrHPPanTask(self, state): - if self.direct.fShift: - self.camera.setHpr(self.camera, - (0.5 * self.chan.mouseDeltaX * - self.chan.fovH), - (-0.5 * self.chan.mouseDeltaY * - self.chan.fovV), - 0.0) - else: - self.camera.setPos(self.camera, - (-0.5 * self.chan.mouseDeltaX * - self.chan.nearWidth * - state.scaleFactor), - 0.0, - (-0.5 * self.chan.mouseDeltaY * - self.chan.nearHeight * - state.scaleFactor)) - return Task.cont - - def spawnXZTranslate(self): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # Hide the marker - self.coaMarker.hide() - t = Task.Task(self.XZTranslateTask) - t.scaleFactor = (self.coaDist / self.chan.near) - taskMgr.spawnTaskNamed(t, 'manipulateCamera') - - def XZTranslateTask(self,state): - self.camera.setPos(self.camera, - (-0.5 * self.chan.mouseDeltaX * - self.chan.nearWidth * - state.scaleFactor), - 0.0, - (-0.5 * self.chan.mouseDeltaY * - self.chan.nearHeight * - state.scaleFactor)) - return Task.cont - - def spawnMouseRotateTask(self): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # Set at markers position in render coordinates - self.relNodePath.setPos(self.coaMarkerPos) - self.relNodePath.setHpr(self.camera, self.zeroPoint) - t = Task.Task(self.mouseRotateTask) - t.wrtMat = self.camera.getMat( self.relNodePath ) - taskMgr.spawnTaskNamed(t, 'manipulateCamera') - - def mouseRotateTask(self, state): - wrtMat = state.wrtMat - self.relNodePath.setHpr(self.relNodePath, - (-0.5 * self.chan.mouseDeltaX * 180.0), - (0.5 * self.chan.mouseDeltaY * 180.0), - 0.0) - self.camera.setMat(self.relNodePath, wrtMat) - return Task.cont - - def spawnHPPan(self): - # Kill any existing tasks - taskMgr.removeTasksNamed('manipulateCamera') - # Hide the marker - self.coaMarker.hide() - t = Task.Task(self.HPPanTask) - taskMgr.spawnTaskNamed(t, 'manipulateCamera') - - def HPPanTask(self, state): - self.camera.setHpr(self.camera, - (0.5 * self.chan.mouseDeltaX * - self.chan.fovH), - (-0.5 * self.chan.mouseDeltaY * - self.chan.fovV), - 0.0) - return Task.cont - - def fitOnWidget(self): - # Fit the node on the screen - - # stop any ongoing tasks - taskMgr.removeTasksNamed('manipulateCamera') - - # How big is the node? - nodeScale = self.direct.widget.scalingNode.getScale(render) - maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2]) - maxDim = min(self.chan.nearWidth, self.chan.nearHeight) - - # At what distance does the object fill 30% of the screen? - # Assuming radius of 1 on widget - camY = self.chan.near * (2.0 * maxScale)/(0.3 * maxDim) - - # What is the vector through the center of the screen? - centerVec = Y_AXIS * camY - - # Where is the node relative to the viewpoint - vWidget2Camera = self.direct.widget.getPos(self.camera) - - # How far do you move the camera to be this distance from the node? - deltaMove = vWidget2Camera - centerVec - - # Move a target there - self.relNodePath.setPos(self.camera, deltaMove) - - parent = self.camera.getParent() - self.camera.wrtReparentTo(self.relNodePath) - fitTask = self.camera.lerpPos(Point3(0,0,0), - CAM_MOVE_DURATION, - blendType = 'easeInOut', - task = 'manipulateCamera') - # Upon death, reparent Cam to parent - fitTask.parent = parent - fitTask.uponDeath = self.reparentCam - - def enableMouseFly(self): - self.enableMouseInteraction() - self.enableHotKeys() - self.coaMarker.reparentTo(render) - - def enableMouseInteraction(self): - # disable C++ fly interface - base.disableMouse() - # Accept middle mouse events - self.accept('handleMouse2', self.mouseFlyStart, [self.chan]) - self.accept('handleMouse2Up', self.mouseFlyStop) - - def enableHotKeys(self): - t = CAM_MOVE_DURATION - self.accept('u', self.uprightCam, [self.chan]) - self.accept('c', self.centerCamIn, [self.chan, 0.5]) - self.accept('h', self.homeCam, [self.chan]) - self.accept('f', self.fitOnWidget) - for i in range(1,9): - self.accept(`i`, self.SpawnMoveToView, [self.chan, i]) - self.accept('9', self.swingCamAboutWidget, [self.chan, -90.0, t]) - self.accept('0', self.swingCamAboutWidget, [self.chan, 90.0, t]) - self.accept('`', self.removeManipulateCameraTask) - self.accept('=', self.zoomCam, [self.chan, 0.5, t]) - self.accept('+', self.zoomCam, [self.chan, 0.5, t]) - self.accept('-', self.zoomCam, [self.chan, -2.0, t]) - self.accept('_', self.zoomCam, [self.chan, -2.0, t]) - - def disableMouseFly(self): - # Hide the marker - self.coaMarker.reparentTo(hidden) - # Ignore middle mouse events - self.ignore('handleMouse2') - self.ignore('handleMouse2Up') - self.ignore('u') - self.ignore('c') - self.ignore('h') - self.ignore('f') - for i in range(0,10): - self.ignore(`i`) - self.ignore('=') - self.ignore('+') - self.ignore('-') - self.ignore('_') - self.ignore('`') - - def removeManipulateCameraTask(self): - taskMgr.removeTasksNamed('manipulateCamera') - +from PandaObject import * + +CAM_MOVE_DURATION = 1.0 +Y_AXIS = Vec3(0,1,0) + +class DirectCameraControl(PandaObject): + def __init__(self): + # Create the grid + self.chan = direct.chan + self.camera = self.chan.camera + self.orthoViewRoll = 0.0 + self.lastView = 0 + self.coa = Point3(0) + self.coaMarker = loader.loadModel('models/misc/sphere') + self.coaMarker.setColor(1,0,0) + self.coaMarkerPos = Point3(0) + self.relNodePath = render.attachNewNode(NamedNode('targetNode')) + self.zeroBaseVec = VBase3(0) + self.zeroVector = Vec3(0) + self.centerVec = Vec3(0, 1, 0) + self.zeroPoint = Point3(0) + + def mouseFlyStart(self, chan): + # Record starting mouse positions + self.initMouseX = chan.mouseX + self.initMouseY = chan.mouseY + # Where are we in the channel? + if ((abs(self.initMouseX) < 0.9) & (abs(self.initMouseY) < 0.9)): + # MOUSE IS IN CENTRAL REGION + # Hide the marker for this kind of motion + self.coaMarker.hide() + # See if the shift key is pressed + if (direct.fShift): + # If shift key is pressed, just perform horiz and vert pan: + self.spawnHPPan() + else: + # Otherwise, check for a hit point based on + # current mouse position + # And then spawn task to determine mouse mode + numEntries = direct.iRay.pickGeom( + render,chan.mouseX,chan.mouseY) + # Filter out hidden nodes from entry list + indexList = [] + for i in range(0,numEntries): + entry = direct.iRay.cq.getEntry(i) + node = entry.getIntoNode() + if node.isHidden(): + pass + else: + # Not one of the widgets, use it + indexList.append(i) + coa = Point3(0) + if(indexList): + # Start off with first point + minPt = indexList[0] + # Find hit point in camera's space + hitPt = direct.iRay.camToHitPt(minPt) + coa.set(hitPt[0],hitPt[1],hitPt[2]) + coaDist = Vec3(coa - self.zeroPoint).length() + # Check other intersection points, sorting them + # TBD: Use TBS C++ function to do this + if len(indexList) > 1: + for i in range(1,len(indexList)): + entryNum = indexList[i] + hitPt = direct.iRay.camToHitPt(entryNum) + dist = Vec3(hitPt - self.zeroPoint).length() + if (dist < coaDist): + coaDist = dist + coa.set(hitPt[0],hitPt[1],hitPt[2]) + minPt = i + + # Handle case of bad coa point (too close or too far) + if ((coaDist < (1.1 * self.chan.near)) | + (coaDist > self.chan.far)): + # Put it out in front of the camera + coa.set(0,100,0) + coaDist = 100 + else: + # If no intersection point: + # Put coa out in front of the camera + coa.set(0,100,0) + coaDist = 100 + + # Update coa and marker + self.updateCoa(coa, coaDist) + # Now spawn task to determine mouse fly mode + self.determineMouseFlyMode() + # END MOUSE IN CENTRAL REGION + else: + # Mouse is in outer frame, spawn mouseRotateTask + self.spawnMouseRotateTask() + + def mouseFlyStop(self): + taskMgr.removeTasksNamed('determineMouseFlyMode') + taskMgr.removeTasksNamed('manipulateCamera') + # Show the marker + self.coaMarker.show() + # Resize it + self.updateCoaMarkerSize() + + def determineMouseFlyMode(self): + # Otherwise, determine mouse fly mode + t = Task.Task(self.determineMouseFlyModeTask) + taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode') + + def determineMouseFlyModeTask(self, state): + deltaX = self.chan.mouseX - self.initMouseX + deltaY = self.chan.mouseY - self.initMouseY + if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)): + return Task.cont + else: + if (abs(deltaY) > 0.1): + self.spawnHPanYZoom() + else: + self.spawnXZTranslate() + return Task.done + + def updateCoa(self, cam2point, coaDist = None): + self.coa.set(cam2point[0], cam2point[1], cam2point[2]) + if coaDist: + self.coaDist = coaDist + else: + self.coaDist = Vec3(self.coa - self.zeroPoint).length() + # Place the marker in render space + self.coaMarker.setPos(self.camera,self.coa) + # Resize it + self.updateCoaMarkerSize(coaDist) + # Record marker pos in render space + self.coaMarkerPos.assign(self.coaMarker.getPos()) + + def updateCoaMarkerSize(self, coaDist = None): + if not coaDist: + coaDist = Vec3(self.coaMarker.getPos( self.chan.camera )).length() + self.coaMarker.setScale(0.01 * coaDist * + math.tan(deg2Rad(self.chan.fovV))) + + def homeCam(self, chan): + chan.camera.setMat(Mat4.identMat()) + + def uprightCam(self, chan): + taskMgr.removeTasksNamed('manipulateCamera') + currH = chan.camera.getH() + chan.camera.lerpHpr(currH, 0, 0, + CAM_MOVE_DURATION, + other = render, + blendType = 'easeInOut', + task = 'manipulateCamera') + + def centerCam(self, chan): + # Chan is a display region context + self.centerCamIn(chan, 1.0) + + def centerCamNow(self, chan): + self.centerCamIn(chan, 0.) + + def centerCamIn(self, chan, t): + # Chan is a display region context + taskMgr.removeTasksNamed('manipulateCamera') + markerToCam = self.coaMarker.getPos( chan.camera ) + dist = Vec3(markerToCam - self.zeroPoint).length() + scaledCenterVec = self.centerVec * dist + delta = markerToCam - scaledCenterVec + self.relNodePath.setPosHpr(chan.camera, Point3(0), Point3(0)) + chan.camera.lerpPos(Point3(delta), + CAM_MOVE_DURATION, + other = self.relNodePath, + blendType = 'easeInOut', + task = 'manipulateCamera') + + def zoomCam(self, chan, zoomFactor, t): + taskMgr.removeTasksNamed('manipulateCamera') + # Find a point zoom factor times the current separation + # of the widget and cam + zoomPtToCam = self.coaMarker.getPos(chan.camera) * zoomFactor + # Put a target nodePath there + self.relNodePath.setPos(chan.camera, zoomPtToCam) + # Move to that point + chan.camera.lerpPos(self.zeroPoint, + CAM_MOVE_DURATION, + other = self.relNodePath, + blendType = 'easeInOut', + task = 'manipulateCamera') + + def SpawnMoveToView(self, chan, view): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Calc hprOffset + hprOffset = VBase3() + if view == 8: + # Try the next roll angle + self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0 + # but use the last view + view = self.lastView + else: + self.orthoViewRoll = 0.0 + # Adjust offset based on specified view + if view == 1: + hprOffset.set(180., 0., 0.) + elif view == 2: + hprOffset.set(0., 0., 0.) + elif view == 3: + hprOffset.set(90., 0., 0.) + elif view == 4: + hprOffset.set(-90., 0., 0.) + elif view == 5: + hprOffset.set(0., -90., 0.) + elif view == 6: + hprOffset.set(0., 90., 0.) + elif view == 7: + hprOffset.set(135., -35.264, 0.) + # Position target + self.relNodePath.setPosHpr(self.coaMarker, self.zeroBaseVec, + hprOffset) + # Scale center vec by current distance to target + offsetDistance = Vec3(chan.camera.getPos(self.relNodePath) - + self.zeroPoint).length() + scaledCenterVec = self.centerVec * (-1.0 * offsetDistance) + # Now put the relNodePath at that point + self.relNodePath.setPosHpr(self.relNodePath, + scaledCenterVec, + self.zeroBaseVec) + # Record view for next time around + self.lastView = view + chan.camera.lerpPosHpr(self.zeroPoint, + VBase3(0,0,self.orthoViewRoll), + CAM_MOVE_DURATION, + other = self.relNodePath, + blendType = 'easeInOut', + task = 'manipulateCamera') + + + def swingCamAboutWidget(self, chan, degrees, t): + # Remove existing camera manipulation task + taskMgr.removeTasksNamed('manipulateCamera') + + # Coincident with widget + self.relNodePath.setPos(self.coaMarker, self.zeroPoint) + # But aligned with render space + self.relNodePath.setHpr(self.zeroPoint) + + parent = self.camera.getParent() + self.camera.wrtReparentTo(self.relNodePath) + + manipTask = self.relNodePath.lerpHpr(VBase3(degrees,0,0), + CAM_MOVE_DURATION, + blendType = 'easeInOut', + task = 'manipulateCamera') + # Upon death, reparent Cam to parent + manipTask.parent = parent + manipTask.uponDeath = self.reparentCam + + def reparentCam(self, state): + self.camera.wrtReparentTo(state.parent) + + def spawnHPanYZoom(self): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # hide the marker + self.coaMarker.hide() + # Negate vec to give it the correct sense for mouse motion below + targetVector = self.coa * -1 + t = Task.Task(self.HPanYZoomTask) + t.targetVector = targetVector + taskMgr.spawnTaskNamed(t, 'manipulateCamera') + + def HPanYZoomTask(self,state): + targetVector = state.targetVector + distToMove = targetVector * self.chan.mouseDeltaY + self.camera.setPosHpr(self.camera, + distToMove[0], + distToMove[1], + distToMove[2], + (0.5 * self.chan.mouseDeltaX * + self.chan.fovH), + 0.0, 0.0) + return Task.cont + + + def spawnXZTranslateOrHPPan(self): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Hide the marker + self.coaMarker.hide() + t = Task.Task(self.XZTranslateOrHPPanTask) + t.scaleFactor = (self.coaDist / self.chan.near) + taskMgr.spawnTaskNamed(t, 'manipulateCamera') + + def XZTranslateOrHPPanTask(self, state): + if direct.fShift: + self.camera.setHpr(self.camera, + (0.5 * self.chan.mouseDeltaX * + self.chan.fovH), + (-0.5 * self.chan.mouseDeltaY * + self.chan.fovV), + 0.0) + else: + self.camera.setPos(self.camera, + (-0.5 * self.chan.mouseDeltaX * + self.chan.nearWidth * + state.scaleFactor), + 0.0, + (-0.5 * self.chan.mouseDeltaY * + self.chan.nearHeight * + state.scaleFactor)) + return Task.cont + + def spawnXZTranslate(self): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Hide the marker + self.coaMarker.hide() + t = Task.Task(self.XZTranslateTask) + t.scaleFactor = (self.coaDist / self.chan.near) + taskMgr.spawnTaskNamed(t, 'manipulateCamera') + + def XZTranslateTask(self,state): + self.camera.setPos(self.camera, + (-0.5 * self.chan.mouseDeltaX * + self.chan.nearWidth * + state.scaleFactor), + 0.0, + (-0.5 * self.chan.mouseDeltaY * + self.chan.nearHeight * + state.scaleFactor)) + return Task.cont + + def spawnMouseRotateTask(self): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Set at markers position in render coordinates + self.relNodePath.setPos(self.coaMarkerPos) + self.relNodePath.setHpr(self.camera, self.zeroPoint) + t = Task.Task(self.mouseRotateTask) + t.wrtMat = self.camera.getMat( self.relNodePath ) + taskMgr.spawnTaskNamed(t, 'manipulateCamera') + + def mouseRotateTask(self, state): + wrtMat = state.wrtMat + self.relNodePath.setHpr(self.relNodePath, + (-0.5 * self.chan.mouseDeltaX * 180.0), + (0.5 * self.chan.mouseDeltaY * 180.0), + 0.0) + self.camera.setMat(self.relNodePath, wrtMat) + return Task.cont + + def spawnHPPan(self): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Hide the marker + self.coaMarker.hide() + t = Task.Task(self.HPPanTask) + taskMgr.spawnTaskNamed(t, 'manipulateCamera') + + def HPPanTask(self, state): + self.camera.setHpr(self.camera, + (0.5 * self.chan.mouseDeltaX * + self.chan.fovH), + (-0.5 * self.chan.mouseDeltaY * + self.chan.fovV), + 0.0) + return Task.cont + + def fitOnWidget(self): + # Fit the node on the screen + + # stop any ongoing tasks + taskMgr.removeTasksNamed('manipulateCamera') + + # How big is the node? + nodeScale = direct.widget.scalingNode.getScale(render) + maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2]) + maxDim = min(self.chan.nearWidth, self.chan.nearHeight) + + # At what distance does the object fill 30% of the screen? + # Assuming radius of 1 on widget + camY = self.chan.near * (2.0 * maxScale)/(0.3 * maxDim) + + # What is the vector through the center of the screen? + centerVec = Y_AXIS * camY + + # Where is the node relative to the viewpoint + vWidget2Camera = direct.widget.getPos(self.camera) + + # How far do you move the camera to be this distance from the node? + deltaMove = vWidget2Camera - centerVec + + # Move a target there + self.relNodePath.setPos(self.camera, deltaMove) + + parent = self.camera.getParent() + self.camera.wrtReparentTo(self.relNodePath) + fitTask = self.camera.lerpPos(Point3(0,0,0), + CAM_MOVE_DURATION, + blendType = 'easeInOut', + task = 'manipulateCamera') + # Upon death, reparent Cam to parent + fitTask.parent = parent + fitTask.uponDeath = self.reparentCam + + def enableMouseFly(self): + self.enableMouseInteraction() + self.enableHotKeys() + self.coaMarker.reparentTo(render) + + def enableMouseInteraction(self): + # disable C++ fly interface + base.disableMouse() + # Accept middle mouse events + self.accept('handleMouse2', self.mouseFlyStart, [self.chan]) + self.accept('handleMouse2Up', self.mouseFlyStop) + + def enableHotKeys(self): + t = CAM_MOVE_DURATION + self.accept('u', self.uprightCam, [self.chan]) + self.accept('c', self.centerCamIn, [self.chan, 0.5]) + self.accept('h', self.homeCam, [self.chan]) + self.accept('f', self.fitOnWidget) + for i in range(1,9): + self.accept(`i`, self.SpawnMoveToView, [self.chan, i]) + self.accept('9', self.swingCamAboutWidget, [self.chan, -90.0, t]) + self.accept('0', self.swingCamAboutWidget, [self.chan, 90.0, t]) + self.accept('`', self.removeManipulateCameraTask) + self.accept('=', self.zoomCam, [self.chan, 0.5, t]) + self.accept('+', self.zoomCam, [self.chan, 0.5, t]) + self.accept('-', self.zoomCam, [self.chan, -2.0, t]) + self.accept('_', self.zoomCam, [self.chan, -2.0, t]) + + def disableMouseFly(self): + # Hide the marker + self.coaMarker.reparentTo(hidden) + # Ignore middle mouse events + self.ignore('handleMouse2') + self.ignore('handleMouse2Up') + self.ignore('u') + self.ignore('c') + self.ignore('h') + self.ignore('f') + for i in range(0,10): + self.ignore(`i`) + self.ignore('=') + self.ignore('+') + self.ignore('-') + self.ignore('_') + self.ignore('`') + + def removeManipulateCameraTask(self): + taskMgr.removeTasksNamed('manipulateCamera') + diff --git a/direct/src/directutil/DirectGrid.py b/direct/src/directutil/DirectGrid.py index 9ce8ea56f9..15a60c0c10 100644 --- a/direct/src/directutil/DirectGrid.py +++ b/direct/src/directutil/DirectGrid.py @@ -2,14 +2,11 @@ from PandaObject import * from DirectGeometry import * class DirectGrid(NodePath,PandaObject): - def __init__(self, direct): + def __init__(self): # Initialize superclass NodePath.__init__(self) self.assign(hidden.attachNewNode( NamedNode('DirectGrid'))) - # Record handle to direct session - self.direct = direct - # Load up grid parts to initialize grid object # Polygon used to mark grid plane self.gridBack = loader.loadModel('models/misc/gridBack') @@ -60,7 +57,7 @@ class DirectGrid(NodePath,PandaObject): def selectGridBackParent(self, nodePath): if nodePath.getName() == 'GridBack': - self.direct.select(self) + direct.select(self) def updateGrid(self): # Update grid lines based upon current grid spacing and grid size diff --git a/direct/src/directutil/DirectManipulation.py b/direct/src/directutil/DirectManipulation.py index 58ffb401dc..4353464e1c 100644 --- a/direct/src/directutil/DirectManipulation.py +++ b/direct/src/directutil/DirectManipulation.py @@ -1,915 +1,912 @@ -from PandaObject import * -from DirectGeometry import * - -MANIPULATION_MOVE_DELAY = 0.65 -VISIBLE_DISCS = ['x-disc-visible', 'y-disc-visible', 'z-disc-visible'] - -class DirectManipulationControl(PandaObject): - def __init__(self, direct): - # Create the grid - self.direct = direct - self.chan = direct.chan - self.camera = self.chan.camera - self.objectHandles = ObjectHandles(direct) - self.hitPt = Point3(0) - self.prevHit = Vec3(0) - self.rotationCenter = Point3(0) - self.initScaleMag = 1 - self.refNodePath = render.attachNewNode(NamedNode('refNodePath')) - self.hitPtDist = 0 - self.constraint = None - self.rotateAxis = 'x' - self.lastCrankAngle = 0 - self.fSetCoa = 0 - self.fHitInit = 1 - self.fWidgetTop = 0 - self.fFreeManip = 1 - self.fScaling = 1 - self.mode = None - - def manipulationStart(self, chan): - # Start out in select mode - self.mode = 'select' - # Check for a widget hit point - numEntries = self.direct.iRay.pickWidget( - render,chan.mouseX,chan.mouseY) - # Did we hit a widget? - if(numEntries): - # Yes! - # Entry 0 is the closest hit point if multiple hits - minPt = 0 - # Find hit point in camera's space - self.hitPt = self.direct.iRay.camToHitPt(minPt) - self.hitPtDist = Vec3(self.hitPt - ZERO_POINT).length() - # Get the associated collision queue object - entry = self.direct.iRay.cq.getEntry(minPt) - # Extract the node - node = entry.getIntoNode() - # Constraint determined by nodes name - self.constraint = node.getName() - else: - # Nope, off the widget, no constraint - self.constraint = None - # Check to see if we are moving the object - # We are moving the object if we either wait long enough - """ - taskMgr.spawnTaskNamed( - Task.doLater(MANIPULATION_MOVE_DELAY, - Task.Task(self.switchToMoveMode), - 'manip-switch-to-move'), - 'manip-move-wait') - """ - # Or if we move far enough - self.moveDir = None - watchMouseTask = Task.Task(self.watchMouseTask) - watchMouseTask.initX = self.chan.mouseX - watchMouseTask.initY = self.chan.mouseY - taskMgr.spawnTaskNamed(watchMouseTask, 'manip-watch-mouse') - - def switchToMoveMode(self, state): - taskMgr.removeTasksNamed('manip-watch-mouse') - self.mode = 'move' - self.manipulateObject() - return Task.done - - def watchMouseTask(self, state): - if (((abs (state.initX - self.chan.mouseX)) > 0.01) | - ((abs (state.initY - self.chan.mouseY)) > 0.01)): - taskMgr.removeTasksNamed('manip-move-wait') - taskMgr.removeTasksNamed('manip-switch-to-move') - self.mode = 'move' - self.manipulateObject() - return Task.done - else: - return Task.cont - - def manipulationStop(self): - taskMgr.removeTasksNamed('manipulateObject') - taskMgr.removeTasksNamed('manip-move-wait') - taskMgr.removeTasksNamed('manip-switch-to-move') - taskMgr.removeTasksNamed('manip-watch-mouse') - # depending on flag..... - if self.mode == 'select': - # Check for object under mouse - numEntries = self.direct.iRay.pickGeom( - render,self.chan.mouseX,self.chan.mouseY) - # Pick out the closest object that isn't a widget - index = -1 - for i in range(0,numEntries): - entry = self.direct.iRay.cq.getEntry(i) - node = entry.getIntoNode() - if node.isHidden(): - pass - # Is it a named node?, If so, see if it has a name - elif issubclass(node.__class__, NamedNode): - name = node.getName() - if name in VISIBLE_DISCS: - pass - else: - index = i - break - else: - # Not hidden and not one of the widgets, use it - index = i - # Did we hit an object? - if(index >= 0): - # Yes! - # Find hit point in camera's space - self.hitPt = self.direct.iRay.camToHitPt(index) - self.hitPtDist = Vec3(self.hitPt - ZERO_POINT).length() - # Find the node path from the node found above - nodePath = render.findPathDownTo(node) - # Select it - self.direct.select(nodePath) - else: - self.direct.deselectAll() - else: - self.manipulateObjectCleanup() - - def manipulateObjectCleanup(self): - if self.fScaling: - # We had been scaling, need to reset object handles - self.objectHandles.transferObjectHandlesScale() - self.fScaling = 0 - if self.fSetCoa: - self.objectHandles.manipModeColor() - self.direct.selected.highlightAll() - self.objectHandles.showAllHandles() - self.objectHandles.hideGuides() - # Restart followSelectedNodePath task - if self.direct.selected.last: - self.spawnFollowSelectedNodePathTask() - messenger.send('manipulateObjectCleanup') - - def spawnFollowSelectedNodePathTask(self): - # Where are the object handles relative to the selected object - pos = VBase3(0) - hpr = VBase3(0) - decomposeMatrix(self.direct.selected.last.mCoa2Dnp, - VBase3(0), hpr, pos, CSDefault) - # Create the task - t = Task.Task(self.followSelectedNodePathTask) - # Update state variables - t.pos = pos - t.hpr = hpr - t.base = self.direct.selected.last - # Spawn the task - taskMgr.spawnTaskNamed(t, 'followSelectedNodePath') - - def followSelectedNodePathTask(self, state): - self.direct.widget.setPosHpr(state.base, state.pos, state.hpr) - return Task.cont - - def enableManipulation(self): - # Accept mouse events - self.accept('handleMouse1', self.manipulationStart, [self.chan]) - self.accept('handleMouse1Up', self.manipulationStop) - self.enableHotKeys() - - def enableHotKeys(self): - self.accept( - '.', self.objectHandles.multiplyScalingFactorBy, [2.0]) - self.accept( - ',', self.objectHandles.multiplyScalingFactorBy, [0.5]) - self.accept('F', self.objectHandles.growToFit) - - def disableManipulation(self): - # Ignore middle mouse events - self.ignore('handleMouse1') - self.ignore('handleMouse1Up') - self.disableHotKeys() - - def disableHotKeys(self): - self.ignore('.') - self.ignore(',') - self.ignore('F') - - def removeManipulateObjectTask(self): - taskMgr.removeTasksNamed('manipulateObject') - - def manipulateObject(self): - # Only do this if something is selected - if self.direct.selected: - # Remove the task to keep the widget attached to the object - taskMgr.removeTasksNamed('followSelectedNodePath') - # and the task to highlight the widget - taskMgr.removeTasksNamed('highlightWidgetTask') - # Set manipulation flag - self.fManip = 1 - # Update object handles visibility - self.objectHandles.showGuides() - self.objectHandles.hideAllHandles() - self.objectHandles.showHandle(self.constraint) - if self.fSetCoa: - self.objectHandles.coaModeColor() - - # Record relationship between selected nodes and widget - self.direct.selected.getWrtAll() - - # hide the bbox of the selected objects during interaction - self.direct.selected.dehighlightAll() - - """ - # Push the undo dcs for the selected objects - self.direct.undo.push( - (self.direct.selected, 'dcs')) - """ - # Manipulate the real object with the constraint - # The constraint is passed as the name of the node - self.spawnManipulateObjectTask() - - def spawnManipulateObjectTask(self): - # reset hit-pt flag - self.fHitInit = 1 - # record initial offset between widget and camera - t = Task.Task(self.manipulateObjectTask) - taskMgr.spawnTaskNamed(t, 'manipulateObject') - - def manipulateObjectTask(self, state): - - if self.constraint: - type = self.constraint[2:] - if type == 'post': - self.xlate1D() - elif type == 'disc': - self.xlate2D() - elif type == 'ring': - self.rotate1D() - elif self.fFreeManip: - if self.fScaling & (not self.direct.fAlt): - # We had been scaling and changed modes, - # reset object handles - self.objectHandles.transferObjectHandlesScale() - self.fScaling = 0 - if self.direct.fControl: - self.rotate2D() - elif self.direct.fAlt: - self.fScaling = 1 - self.scale3D() - elif self.direct.fShift: - self.xlateCamXY() - else: - self.xlateCamXZ() - else: - # MRM: Needed, more elegant fallback - return Task.cont - - if self.fSetCoa: - # Update coa based on current widget position - self.direct.selected.last.mCoa2Dnp.assign( - self.direct.widget.getMat(self.direct.selected.last) - ) - else: - # Move the objects with the widget - self.direct.selected.moveWrtWidgetAll() - - # Continue - return Task.cont - - def xlate1D(self): - # Constrained 1D Translation along widget axis - # Compute nearest hit point along axis and try to keep - # that point as close to the current mouse position as possible - # what point on the axis is the mouse pointing at? - self.hitPt.assign(self.objectHandles.getAxisIntersectPt( - self.constraint[:1])) - # use it to see how far to move the widget - if self.fHitInit: - # First time through, just record that point - self.fHitInit = 0 - self.prevHit.assign(self.hitPt) - else: - # Move widget to keep hit point as close to mouse as possible - offset = self.hitPt - self.prevHit - self.direct.widget.setPos(self.direct.widget, offset) - - def xlate2D(self): - # Constrained 2D (planar) translation - # Compute point of intersection of ray from eyepoint through cursor - # to one of the three orthogonal planes on the widget. - # This point tracks all subsequent mouse movements - self.hitPt.assign(self.objectHandles.getWidgetIntersectPt( - self.direct.widget, self.constraint[:1])) - # use it to see how far to move the widget - if self.fHitInit: - # First time through just record hit point - self.fHitInit = 0 - self.prevHit.assign(self.hitPt) - else: - offset = self.hitPt - self.prevHit - self.direct.widget.setPos(self.direct.widget, offset) - - - def xlateCamXZ(self): - """Constrained 2D motion parallel to the camera's image plane - This moves the object in the camera's XZ plane""" - # reset fHitInit - # (in case we later switch to another manipulation mode) - #self.fHitInit = 1 - # Where is the widget relative to current camera view - vWidget2Camera = self.direct.widget.getPos(self.camera) - x = vWidget2Camera[0] - y = vWidget2Camera[1] - z = vWidget2Camera[2] - # Move widget (and objects) based upon mouse motion - # Scaled up accordingly based upon widget distance - chan = self.chan - self.direct.widget.setX( - self.camera, - x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near)) - self.direct.widget.setZ( - self.camera, - z + 0.5 * chan.mouseDeltaY * chan.nearHeight * (y/chan.near)) - - def xlateCamXY(self): - """Constrained 2D motion perpendicular to camera's image plane - This moves the object in the camera's XY plane""" - # Now, where is the widget relative to current camera view - vWidget2Camera = self.direct.widget.getPos(self.camera) - # If this is first time around, record initial y distance - if self.fHitInit: - self.fHitInit = 0 - # Record widget offset along y - self.initY = vWidget2Camera[1] - # Extract current values - x = vWidget2Camera[0] - y = vWidget2Camera[1] - z = vWidget2Camera[2] - # Move widget (and objects) based upon mouse motion - # Scaled up accordingly based upon widget distance - chan = self.chan - self.direct.widget.setPos( - self.camera, - x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near), - y + self.initY * chan.mouseDeltaY, - z) - - def getCrankAngle(self): - # Used to compute current angle of mouse (relative to the widget's - # origin) in screen space - x = self.chan.mouseX - self.rotationCenter[0] - y = self.chan.mouseY - self.rotationCenter[2] - return (180 + rad2Deg(math.atan2(y,x))) - - def widgetCheck(self,type): - # Utility to see if we are looking at the top or bottom of - # a 2D planar widget or if we are looking at a 2D planar widget - # edge on - # Based upon angle between view vector from eye through the - # widget's origin and one of the three principle axes - axis = self.constraint[:1] - # First compute vector from eye through widget origin - mWidget2Cam = self.direct.widget.getMat(self.camera) - # And determine where the viewpoint is relative to widget - pos = VBase3(0) - decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos, - CSDefault) - widgetDir = Vec3(pos) - widgetDir.normalize() - # Convert specified widget axis to view space - if axis == 'x': - widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS)) - elif axis == 'y': - widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS)) - elif axis == 'z': - widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS)) - widgetAxis.normalize() - if type == 'top?': - # Check sign of angle between two vectors - return (widgetDir.dot(widgetAxis) < 0.) - elif type == 'edge?': - # Checking to see if we are viewing edge-on - # Check angle between two vectors - return(abs(widgetDir.dot(widgetAxis)) < .2) - - def getWidgetsNearProjectionPoint(self): - # Find the position of the projection of the specified node path - # on the near plane - widgetOrigin = self.direct.widget.getPos(self.camera) - # project this onto near plane - return widgetOrigin * (self.chan.near / widgetOrigin[1]) - - def getScreenXY(self): - # Where does the widget's projection fall on the near plane - nearVec = self.getWidgetsNearProjectionPoint() - # Clamp these coordinates to visible screen - nearX = self.clamp(nearVec[0], self.chan.left, self.chan.right) - nearY = self.clamp(nearVec[2], self.chan.bottom, self.chan.top) - # What percentage of the distance across the screen is this? - percentX = (nearX - self.chan.left)/self.chan.nearWidth - percentY = (nearY - self.chan.bottom)/self.chan.nearHeight - # Map this percentage to the same -1 to 1 space as the mouse - screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0) - # Return the resulting value - return screenXY - - def rotate1D(self): - # Constrained 1D rotation about the widget's main axis (X,Y, or Z) - # Rotation depends upon circular motion of the mouse about the - # projection of the widget's origin on the image plane - # A complete circle about the widget results in a change in - # orientation of 360 degrees. - - # First initialize hit point/rotation angle - if self.fHitInit: - self.fHitInit = 0 - self.rotateAxis = self.constraint[:1] - self.fWidgetTop = self.widgetCheck('top?') - self.rotationCenter = self.getScreenXY() - self.lastCrankAngle = self.getCrankAngle() - - # Rotate widget based on how far cursor has swung around origin - newAngle = self.getCrankAngle() - deltaAngle = self.lastCrankAngle - newAngle - if self.fWidgetTop: - deltaAngle = -1 * deltaAngle - if self.rotateAxis == 'x': - self.direct.widget.setP(self.direct.widget, deltaAngle) - elif self.rotateAxis == 'y': - self.direct.widget.setR(self.direct.widget, -deltaAngle) - elif self.rotateAxis == 'z': - self.direct.widget.setH(self.direct.widget, deltaAngle) - # Record crank angle for next time around - self.lastCrankAngle = newAngle - - def relHpr(self, base, h, p, r): - # Compute widget2newWidget relative to base coordinate system - mWidget2Base = self.direct.widget.getMat(base) - mBase2NewBase = Mat4() - mBase2NewBase.composeMatrix( - UNIT_VEC, VBase3(h,p,r), ZERO_VEC, - CSDefault) - mBase2Widget = base.getMat(self.direct.widget) - mWidget2Parent = self.direct.widget.getMat() - # Compose the result - resultMat = mWidget2Base * mBase2NewBase - resultMat = resultMat * mBase2Widget - resultMat = resultMat * mWidget2Parent - # Extract and apply the hpr - hpr = Vec3(0) - decomposeMatrix(resultMat, VBase3(), hpr, VBase3(), - CSDefault) - self.direct.widget.setHpr(hpr) - - def rotate2D(self): - # Virtual trackball or arcball rotation of widget - # Rotation method depends upon variable dd-want-arcball - # Default is virtual trackball (handles 1D rotations better) - self.fHitInit = 1 - tumbleRate = 360 - # Mouse motion edge to edge of channel results in one full turn - self.relHpr(self.camera, - self.chan.mouseDeltaX * tumbleRate, - -self.chan.mouseDeltaY * tumbleRate, - 0) - - def scale3D(self): - # Scale the selected node based upon up down mouse motion - # Mouse motion from edge to edge results in a factor of 4 scaling - # From midpoint to edge doubles or halves objects scale - if self.fHitInit: - self.fHitInit = 0 - self.refNodePath.setPos(self.direct.widget, 0, 0, 0) - self.refNodePath.setHpr(self.camera, 0, 0, 0) - self.initScaleMag = Vec3( - self.objectHandles.getWidgetIntersectPt( - self.refNodePath, 'y')).length() - # record initial scale - self.initScale = self.direct.widget.getScale() - # Begin - # Scale factor is ratio current mag with init mag - currScale = ( - self.initScale * - (self.objectHandles.getWidgetIntersectPt( - self.refNodePath, 'y').length() / - self.initScaleMag) - ) - self.direct.widget.setScale(currScale) - - def clamp(self, val, min, max): - if val < min: - return min - elif val > max: - return max - else: - return val - - -class ObjectHandles(NodePath,PandaObject): - def __init__(self,direct): - # Record pointer to direct object - self.direct = direct - # Initialize the superclass - NodePath.__init__(self) - - # Load up object handles model and assign it to self - self.assign(loader.loadModel('models/misc/objectHandles')) - self.node().setName('objectHandles') - self.scalingNode = self.getChild(0) - self.scalingNode.node().setName('ohScalingNode') - self.ohScalingFactor = 1.0 - # To avoid recreating a vec every frame - self.hitPt = Vec3(0) - # Get a handle on the components - self.xHandles = self.find('**/X') - self.xPostGroup = self.xHandles.find('**/x-post-group') - self.xPostCollision = self.xHandles.find('**/x-post') - self.xRingGroup = self.xHandles.find('**/x-ring-group') - self.xRingCollision = self.xHandles.find('**/x-ring') - self.xDiscGroup = self.xHandles.find('**/x-disc-group') - self.xDisc = self.xHandles.find('**/x-disc-visible') - self.xDiscCollision = self.xHandles.find('**/x-disc') - - self.yHandles = self.find('**/Y') - self.yPostGroup = self.yHandles.find('**/y-post-group') - self.yPostCollision = self.yHandles.find('**/y-post') - self.yRingGroup = self.yHandles.find('**/y-ring-group') - self.yRingCollision = self.yHandles.find('**/y-ring') - self.yDiscGroup = self.yHandles.find('**/y-disc-group') - self.yDisc = self.yHandles.find('**/y-disc-visible') - self.yDiscCollision = self.yHandles.find('**/y-disc') - - self.zHandles = self.find('**/Z') - self.zPostGroup = self.zHandles.find('**/z-post-group') - self.zPostCollision = self.zHandles.find('**/z-post') - self.zRingGroup = self.zHandles.find('**/z-ring-group') - self.zRingCollision = self.zHandles.find('**/z-ring') - self.zDiscGroup = self.zHandles.find('**/z-disc-group') - self.zDisc = self.zHandles.find('**/z-disc-visible') - self.zDiscCollision = self.zHandles.find('**/z-disc') - - # Adjust visiblity, colors, and transparency - self.xPostCollision.hide() - self.xRingCollision.hide() - self.xDisc.setColor(1,0,0,.2) - self.yPostCollision.hide() - self.yRingCollision.hide() - self.yDisc.setColor(0,1,0,.2) - self.zPostCollision.hide() - self.zRingCollision.hide() - self.zDisc.setColor(0,0,1,.2) - # Augment geometry with lines - self.createObjectHandleLines() - # Create long markers to help line up in world - self.createGuideLines() - self.hideGuides() - - def coaModeColor(self): - self.setColor(.5,.5,.5,1) - - def manipModeColor(self): - self.clearColor() - - def enableHandles(self, handles): - if type(handles) == types.ListType: - for handle in handles: - self.enableHandle(handle) - elif handles == 'x': - self.enableHandles(['x-post','x-ring','x-disc']) - elif handles == 'y': - self.enableHandles(['y-post','y-ring','y-disc']) - elif handles == 'z': - self.enableHandles(['z-post','z-ring','z-disc']) - elif handles == 'post': - self.enableHandles(['x-post','y-post','z-post']) - elif handles == 'ring': - self.enableHandles(['x-ring','y-ring','z-ring']) - elif handles == 'disc': - self.enableHandles(['x-disc','y-disc','z-disc']) - elif handles == 'all': - self.enableHandles(['x-post','x-ring','x-disc', - 'y-post','y-ring','y-disc', - 'z-post','z-ring','z-disc']) - - def enableHandle(self, handle): - if handle == 'x-post': - self.xPostGroup.reparentTo(self.xHandles) - elif handle == 'x-ring': - self.xRingGroup.reparentTo(self.xHandles) - elif handle == 'x-disc': - self.xDiscGroup.reparentTo(self.xHandles) - if handle == 'y-post': - self.yPostGroup.reparentTo(self.yHandles) - elif handle == 'y-ring': - self.yRingGroup.reparentTo(self.yHandles) - elif handle == 'y-disc': - self.yDiscGroup.reparentTo(self.yHandles) - if handle == 'z-post': - self.zPostGroup.reparentTo(self.zHandles) - elif handle == 'z-ring': - self.zRingGroup.reparentTo(self.zHandles) - elif handle == 'z-disc': - self.zDiscGroup.reparentTo(self.zHandles) - - def disableHandles(self, handles): - if type(handles) == types.ListType: - for handle in handles: - self.disableHandle(handle) - elif handles == 'x': - self.disableHandles(['x-post','x-ring','x-disc']) - elif handles == 'y': - self.disableHandles(['y-post','y-ring','y-disc']) - elif handles == 'z': - self.disableHandles(['z-post','z-ring','z-disc']) - elif handles == 'post': - self.disableHandles(['x-post','y-post','z-post']) - elif handles == 'ring': - self.disableHandles(['x-ring','y-ring','z-ring']) - elif handles == 'disc': - self.disableHandles(['x-disc','y-disc','z-disc']) - elif handles == 'all': - self.disableHandles(['x-post','x-ring','x-disc', - 'y-post','y-ring','y-disc', - 'z-post','z-ring','z-disc']) - - def disableHandle(self, handle): - if handle == 'x-post': - self.xPostGroup.reparentTo(hidden) - elif handle == 'x-ring': - self.xRingGroup.reparentTo(hidden) - elif handle == 'x-disc': - self.xDiscGroup.reparentTo(hidden) - if handle == 'y-post': - self.yPostGroup.reparentTo(hidden) - elif handle == 'y-ring': - self.yRingGroup.reparentTo(hidden) - elif handle == 'y-disc': - self.yDiscGroup.reparentTo(hidden) - if handle == 'z-post': - self.zPostGroup.reparentTo(hidden) - elif handle == 'z-ring': - self.zRingGroup.reparentTo(hidden) - elif handle == 'z-disc': - self.zDiscGroup.reparentTo(hidden) - - def showAllHandles(self): - self.xPost.show() - self.xRing.show() - self.xDisc.show() - self.yPost.show() - self.yRing.show() - self.yDisc.show() - self.zPost.show() - self.zRing.show() - self.zDisc.show() - - def hideAllHandles(self): - self.xPost.hide() - self.xRing.hide() - self.xDisc.hide() - self.yPost.hide() - self.yRing.hide() - self.yDisc.hide() - self.zPost.hide() - self.zRing.hide() - self.zDisc.hide() - - def showHandle(self, handle): - if handle == 'x-post': - self.xPost.show() - elif handle == 'x-ring': - self.xRing.show() - elif handle == 'x-disc': - self.xDisc.show() - elif handle == 'y-post': - self.yPost.show() - elif handle == 'y-ring': - self.yRing.show() - elif handle == 'y-disc': - self.yDisc.show() - elif handle == 'z-post': - self.zPost.show() - elif handle == 'z-ring': - self.zRing.show() - elif handle == 'z-disc': - self.zDisc.show() - - def showGuides(self): - self.guideLines.show() - - def hideGuides(self): - self.guideLines.hide() - - def setScalingFactor(self, scaleFactor): - self.ohScalingFactor = scaleFactor - self.scalingNode.setScale(self.ohScalingFactor) - - def getScalingFactor(self): - return self.scalingNode.getScale() - - def transferObjectHandlesScale(self): - # see how much object handles have been scaled - ohs = self.getScale() - sns = self.scalingNode.getScale() - # Transfer this to the scaling node - self.scalingNode.setScale( - ohs[0] * sns[0], - ohs[1] * sns[1], - ohs[2] * sns[2]) - self.setScale(1) - - def multiplyScalingFactorBy(self, factor): - taskMgr.removeTasksNamed('resizeObjectHandles') - sf = self.ohScalingFactor = self.ohScalingFactor * factor - self.scalingNode.lerpScale(sf,sf,sf, 0.5, - blendType = 'easeInOut', - task = 'resizeObjectHandles') - - def growToFit(self): - taskMgr.removeTasksNamed('resizeObjectHandles') - # Increase handles scale until they cover 30% of the min dimension - pos = self.direct.widget.getPos(self.direct.camera) - minDim = min(self.direct.chan.nearWidth, self.direct.chan.nearHeight) - sf = 0.15 * minDim * (pos[1]/self.direct.chan.near) - self.ohScalingFactor = sf - self.scalingNode.lerpScale(sf,sf,sf, 0.5, - blendType = 'easeInOut', - task = 'resizeObjectHandles') - - def createObjectHandleLines(self): - # X post - self.xPost = self.xPostGroup.attachNewNode(NamedNode('x-post-visible')) - lines = LineNodePath(self.xPost) - lines.setColor(VBase4(1,0,0,1)) - lines.setThickness(5) - lines.moveTo(0,0,0) - lines.drawTo(1.5,0,0) - lines.create() - lines = LineNodePath(self.xPost) - lines.setColor(VBase4(1,0,0,1)) - lines.setThickness(1.5) - lines.moveTo(0,0,0) - lines.drawTo(-1.5,0,0) - lines.create() - - # X ring - self.xRing = self.xRingGroup.attachNewNode(NamedNode('x-ring-visible')) - lines = LineNodePath(self.xRing) - lines.setColor(VBase4(1,0,0,1)) - lines.setThickness(3) - lines.moveTo(0,1,0) - for ang in range(15, 370, 15): - lines.drawTo(0, - math.cos(deg2Rad(ang)), - math.sin(deg2Rad(ang))) - lines.create() - - # Y post - self.yPost = self.yPostGroup.attachNewNode(NamedNode('y-post-visible')) - lines = LineNodePath(self.yPost) - lines.setColor(VBase4(0,1,0,1)) - lines.setThickness(5) - lines.moveTo(0,0,0) - lines.drawTo(0,1.5,0) - lines.create() - lines = LineNodePath(self.yPost) - lines.setColor(VBase4(0,1,0,1)) - lines.setThickness(1.5) - lines.moveTo(0,0,0) - lines.drawTo(0,-1.5,0) - lines.create() - - # Y ring - self.yRing = self.yRingGroup.attachNewNode(NamedNode('y-ring-visible')) - lines = LineNodePath(self.yRing) - lines.setColor(VBase4(0,1,0,1)) - lines.setThickness(3) - lines.moveTo(1,0,0) - for ang in range(15, 370, 15): - lines.drawTo(math.cos(deg2Rad(ang)), - 0, - math.sin(deg2Rad(ang))) - lines.create() - - # Z post - self.zPost = self.zPostGroup.attachNewNode(NamedNode('z-post-visible')) - lines = LineNodePath(self.zPost) - lines.setColor(VBase4(0,0,1,1)) - lines.setThickness(5) - lines.moveTo(0,0,0) - lines.drawTo(0,0,1.5) - lines.create() - lines = LineNodePath(self.zPost) - lines.setColor(VBase4(0,0,1,1)) - lines.setThickness(1.5) - lines.moveTo(0,0,0) - lines.drawTo(0,0,-1.5) - lines.create() - - # Z ring - self.zRing = self.zRingGroup.attachNewNode(NamedNode('z-ring-visible')) - lines = LineNodePath(self.zRing) - lines.setColor(VBase4(0,0,1,1)) - lines.setThickness(3) - lines.moveTo(1,0,0) - for ang in range(15, 370, 15): - lines.drawTo(math.cos(deg2Rad(ang)), - math.sin(deg2Rad(ang)), - 0) - lines.create() - - def createGuideLines(self): - self.guideLines = self.attachNewNode(NamedNode('guideLines')) - # X guide lines - lines = LineNodePath(self.guideLines) - lines.setColor(VBase4(1,0,0,1)) - lines.setThickness(0.5) - lines.moveTo(-500,0,0) - lines.drawTo(500,0,0) - lines.create() - lines.node().setName('x-guide') - - # Y guide lines - lines = LineNodePath(self.guideLines) - lines.setColor(VBase4(0,1,0,1)) - lines.setThickness(0.5) - lines.moveTo(0,-500,0) - lines.drawTo(0,500,0) - lines.create() - lines.node().setName('y-guide') - - # Z guide lines - lines = LineNodePath(self.guideLines) - lines.setColor(VBase4(0,0,1,1)) - lines.setThickness(0.5) - lines.moveTo(0,0,-500) - lines.drawTo(0,0,500) - lines.create() - lines.node().setName('z-guide') - - def getAxisIntersectPt(self, axis): - # Calc the xfrom from camera to widget - mCam2Widget = self.direct.camera.getMat(self.direct.widget) - lineDir = Vec3(mCam2Widget.xformVec(self.direct.chan.nearVec)) - lineDir.normalize() - # And determine where the viewpoint is relative to widget - lineOrigin = VBase3(0) - decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin, - CSDefault) - # Now see where this hits the plane containing the 1D motion axis. - # Pick the intersection plane most normal to the intersection ray - # by comparing lineDir with plane normals. The plane with the - # largest dotProduct is most "normal" - if axis == 'x': - if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))): - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS)) - else: - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS)) - # We really only care about the nearest point on the axis - self.hitPt.setY(0) - self.hitPt.setZ(0) - elif axis == 'y': - if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))): - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS)) - else: - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS)) - # We really only care about the nearest point on the axis - self.hitPt.setX(0) - self.hitPt.setZ(0) - elif axis == 'z': - if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))): - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS)) - else: - self.hitPt.assign( - planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS)) - # We really only care about the nearest point on the axis - self.hitPt.setX(0) - self.hitPt.setY(0) - return self.hitPt - - def getWidgetIntersectPt(self, nodePath, plane): - # Find out the point of interection of the ray passing though the mouse - # with the plane containing the 2D xlation or 1D rotation widgets - - # Calc the xfrom from camera to the nodePath - mCam2NodePath = self.direct.camera.getMat(nodePath) - - # And determine where the viewpoint is relative to widget - lineOrigin = VBase3(0) - decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin, - CSDefault) - - # Next we find the vector from viewpoint to the widget through - # the mouse's position on near plane. - # This defines the intersection ray - lineDir = Vec3(mCam2NodePath.xformVec(self.direct.chan.nearVec)) - lineDir.normalize() - # Find the hit point - if plane == 'x': - self.hitPt.assign(planeIntersect( - lineOrigin, lineDir, ORIGIN, X_AXIS)) - elif plane == 'y': - self.hitPt.assign(planeIntersect( - lineOrigin, lineDir, ORIGIN, Y_AXIS)) - elif plane == 'z': - self.hitPt.assign(planeIntersect( - lineOrigin, lineDir, ORIGIN, Z_AXIS)) - return self.hitPt - - - +from PandaObject import * +from DirectGeometry import * + +MANIPULATION_MOVE_DELAY = 0.65 +VISIBLE_DISCS = ['x-disc-visible', 'y-disc-visible', 'z-disc-visible'] + +class DirectManipulationControl(PandaObject): + def __init__(self): + # Create the grid + self.chan = direct.chan + self.camera = self.chan.camera + self.objectHandles = ObjectHandles() + self.hitPt = Point3(0) + self.prevHit = Vec3(0) + self.rotationCenter = Point3(0) + self.initScaleMag = 1 + self.refNodePath = render.attachNewNode(NamedNode('refNodePath')) + self.hitPtDist = 0 + self.constraint = None + self.rotateAxis = 'x' + self.lastCrankAngle = 0 + self.fSetCoa = 0 + self.fHitInit = 1 + self.fWidgetTop = 0 + self.fFreeManip = 1 + self.fScaling = 1 + self.mode = None + + def manipulationStart(self, chan): + # Start out in select mode + self.mode = 'select' + # Check for a widget hit point + numEntries = direct.iRay.pickWidget( + render,chan.mouseX,chan.mouseY) + # Did we hit a widget? + if(numEntries): + # Yes! + # Entry 0 is the closest hit point if multiple hits + minPt = 0 + # Find hit point in camera's space + self.hitPt = direct.iRay.camToHitPt(minPt) + self.hitPtDist = Vec3(self.hitPt - ZERO_POINT).length() + # Get the associated collision queue object + entry = direct.iRay.cq.getEntry(minPt) + # Extract the node + node = entry.getIntoNode() + # Constraint determined by nodes name + self.constraint = node.getName() + else: + # Nope, off the widget, no constraint + self.constraint = None + # Check to see if we are moving the object + # We are moving the object if we either wait long enough + """ + taskMgr.spawnTaskNamed( + Task.doLater(MANIPULATION_MOVE_DELAY, + Task.Task(self.switchToMoveMode), + 'manip-switch-to-move'), + 'manip-move-wait') + """ + # Or if we move far enough + self.moveDir = None + watchMouseTask = Task.Task(self.watchMouseTask) + watchMouseTask.initX = self.chan.mouseX + watchMouseTask.initY = self.chan.mouseY + taskMgr.spawnTaskNamed(watchMouseTask, 'manip-watch-mouse') + + def switchToMoveMode(self, state): + taskMgr.removeTasksNamed('manip-watch-mouse') + self.mode = 'move' + self.manipulateObject() + return Task.done + + def watchMouseTask(self, state): + if (((abs (state.initX - self.chan.mouseX)) > 0.01) | + ((abs (state.initY - self.chan.mouseY)) > 0.01)): + taskMgr.removeTasksNamed('manip-move-wait') + taskMgr.removeTasksNamed('manip-switch-to-move') + self.mode = 'move' + self.manipulateObject() + return Task.done + else: + return Task.cont + + def manipulationStop(self): + taskMgr.removeTasksNamed('manipulateObject') + taskMgr.removeTasksNamed('manip-move-wait') + taskMgr.removeTasksNamed('manip-switch-to-move') + taskMgr.removeTasksNamed('manip-watch-mouse') + # depending on flag..... + if self.mode == 'select': + # Check for object under mouse + numEntries = direct.iRay.pickGeom( + render,self.chan.mouseX,self.chan.mouseY) + # Pick out the closest object that isn't a widget + index = -1 + for i in range(0,numEntries): + entry = direct.iRay.cq.getEntry(i) + node = entry.getIntoNode() + if node.isHidden(): + pass + # Is it a named node?, If so, see if it has a name + elif issubclass(node.__class__, NamedNode): + name = node.getName() + if name in VISIBLE_DISCS: + pass + else: + index = i + break + else: + # Not hidden and not one of the widgets, use it + index = i + # Did we hit an object? + if(index >= 0): + # Yes! + # Find hit point in camera's space + self.hitPt = direct.iRay.camToHitPt(index) + self.hitPtDist = Vec3(self.hitPt - ZERO_POINT).length() + # Find the node path from the node found above + nodePath = render.findPathDownTo(node) + # Select it + direct.select(nodePath) + else: + direct.deselectAll() + else: + self.manipulateObjectCleanup() + + def manipulateObjectCleanup(self): + if self.fScaling: + # We had been scaling, need to reset object handles + self.objectHandles.transferObjectHandlesScale() + self.fScaling = 0 + if self.fSetCoa: + self.objectHandles.manipModeColor() + direct.selected.highlightAll() + self.objectHandles.showAllHandles() + self.objectHandles.hideGuides() + # Restart followSelectedNodePath task + if direct.selected.last: + self.spawnFollowSelectedNodePathTask() + messenger.send('manipulateObjectCleanup') + + def spawnFollowSelectedNodePathTask(self): + # Where are the object handles relative to the selected object + pos = VBase3(0) + hpr = VBase3(0) + decomposeMatrix(direct.selected.last.mCoa2Dnp, + VBase3(0), hpr, pos, CSDefault) + # Create the task + t = Task.Task(self.followSelectedNodePathTask) + # Update state variables + t.pos = pos + t.hpr = hpr + t.base = direct.selected.last + # Spawn the task + taskMgr.spawnTaskNamed(t, 'followSelectedNodePath') + + def followSelectedNodePathTask(self, state): + direct.widget.setPosHpr(state.base, state.pos, state.hpr) + return Task.cont + + def enableManipulation(self): + # Accept mouse events + self.accept('handleMouse1', self.manipulationStart, [self.chan]) + self.accept('handleMouse1Up', self.manipulationStop) + self.enableHotKeys() + + def enableHotKeys(self): + self.accept( + '.', self.objectHandles.multiplyScalingFactorBy, [2.0]) + self.accept( + ',', self.objectHandles.multiplyScalingFactorBy, [0.5]) + self.accept('F', self.objectHandles.growToFit) + + def disableManipulation(self): + # Ignore middle mouse events + self.ignore('handleMouse1') + self.ignore('handleMouse1Up') + self.disableHotKeys() + + def disableHotKeys(self): + self.ignore('.') + self.ignore(',') + self.ignore('F') + + def removeManipulateObjectTask(self): + taskMgr.removeTasksNamed('manipulateObject') + + def manipulateObject(self): + # Only do this if something is selected + if direct.selected: + # Remove the task to keep the widget attached to the object + taskMgr.removeTasksNamed('followSelectedNodePath') + # and the task to highlight the widget + taskMgr.removeTasksNamed('highlightWidgetTask') + # Set manipulation flag + self.fManip = 1 + # Update object handles visibility + self.objectHandles.showGuides() + self.objectHandles.hideAllHandles() + self.objectHandles.showHandle(self.constraint) + if self.fSetCoa: + self.objectHandles.coaModeColor() + + # Record relationship between selected nodes and widget + direct.selected.getWrtAll() + + # hide the bbox of the selected objects during interaction + direct.selected.dehighlightAll() + + """ + # Push the undo dcs for the selected objects + direct.undo.push( + (direct.selected, 'dcs')) + """ + # Manipulate the real object with the constraint + # The constraint is passed as the name of the node + self.spawnManipulateObjectTask() + + def spawnManipulateObjectTask(self): + # reset hit-pt flag + self.fHitInit = 1 + # record initial offset between widget and camera + t = Task.Task(self.manipulateObjectTask) + taskMgr.spawnTaskNamed(t, 'manipulateObject') + + def manipulateObjectTask(self, state): + + if self.constraint: + type = self.constraint[2:] + if type == 'post': + self.xlate1D() + elif type == 'disc': + self.xlate2D() + elif type == 'ring': + self.rotate1D() + elif self.fFreeManip: + if self.fScaling & (not direct.fAlt): + # We had been scaling and changed modes, + # reset object handles + self.objectHandles.transferObjectHandlesScale() + self.fScaling = 0 + if direct.fControl: + self.rotate2D() + elif direct.fAlt: + self.fScaling = 1 + self.scale3D() + elif direct.fShift: + self.xlateCamXY() + else: + self.xlateCamXZ() + else: + # MRM: Needed, more elegant fallback + return Task.cont + + if self.fSetCoa: + # Update coa based on current widget position + direct.selected.last.mCoa2Dnp.assign( + direct.widget.getMat(direct.selected.last) + ) + else: + # Move the objects with the widget + direct.selected.moveWrtWidgetAll() + + # Continue + return Task.cont + + def xlate1D(self): + # Constrained 1D Translation along widget axis + # Compute nearest hit point along axis and try to keep + # that point as close to the current mouse position as possible + # what point on the axis is the mouse pointing at? + self.hitPt.assign(self.objectHandles.getAxisIntersectPt( + self.constraint[:1])) + # use it to see how far to move the widget + if self.fHitInit: + # First time through, just record that point + self.fHitInit = 0 + self.prevHit.assign(self.hitPt) + else: + # Move widget to keep hit point as close to mouse as possible + offset = self.hitPt - self.prevHit + direct.widget.setPos(direct.widget, offset) + + def xlate2D(self): + # Constrained 2D (planar) translation + # Compute point of intersection of ray from eyepoint through cursor + # to one of the three orthogonal planes on the widget. + # This point tracks all subsequent mouse movements + self.hitPt.assign(self.objectHandles.getWidgetIntersectPt( + direct.widget, self.constraint[:1])) + # use it to see how far to move the widget + if self.fHitInit: + # First time through just record hit point + self.fHitInit = 0 + self.prevHit.assign(self.hitPt) + else: + offset = self.hitPt - self.prevHit + direct.widget.setPos(direct.widget, offset) + + + def xlateCamXZ(self): + """Constrained 2D motion parallel to the camera's image plane + This moves the object in the camera's XZ plane""" + # reset fHitInit + # (in case we later switch to another manipulation mode) + #self.fHitInit = 1 + # Where is the widget relative to current camera view + vWidget2Camera = direct.widget.getPos(self.camera) + x = vWidget2Camera[0] + y = vWidget2Camera[1] + z = vWidget2Camera[2] + # Move widget (and objects) based upon mouse motion + # Scaled up accordingly based upon widget distance + chan = self.chan + direct.widget.setX( + self.camera, + x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near)) + direct.widget.setZ( + self.camera, + z + 0.5 * chan.mouseDeltaY * chan.nearHeight * (y/chan.near)) + + def xlateCamXY(self): + """Constrained 2D motion perpendicular to camera's image plane + This moves the object in the camera's XY plane""" + # Now, where is the widget relative to current camera view + vWidget2Camera = direct.widget.getPos(self.camera) + # If this is first time around, record initial y distance + if self.fHitInit: + self.fHitInit = 0 + # Record widget offset along y + self.initY = vWidget2Camera[1] + # Extract current values + x = vWidget2Camera[0] + y = vWidget2Camera[1] + z = vWidget2Camera[2] + # Move widget (and objects) based upon mouse motion + # Scaled up accordingly based upon widget distance + chan = self.chan + direct.widget.setPos( + self.camera, + x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near), + y + self.initY * chan.mouseDeltaY, + z) + + def getCrankAngle(self): + # Used to compute current angle of mouse (relative to the widget's + # origin) in screen space + x = self.chan.mouseX - self.rotationCenter[0] + y = self.chan.mouseY - self.rotationCenter[2] + return (180 + rad2Deg(math.atan2(y,x))) + + def widgetCheck(self,type): + # Utility to see if we are looking at the top or bottom of + # a 2D planar widget or if we are looking at a 2D planar widget + # edge on + # Based upon angle between view vector from eye through the + # widget's origin and one of the three principle axes + axis = self.constraint[:1] + # First compute vector from eye through widget origin + mWidget2Cam = direct.widget.getMat(self.camera) + # And determine where the viewpoint is relative to widget + pos = VBase3(0) + decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos, + CSDefault) + widgetDir = Vec3(pos) + widgetDir.normalize() + # Convert specified widget axis to view space + if axis == 'x': + widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS)) + elif axis == 'y': + widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS)) + elif axis == 'z': + widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS)) + widgetAxis.normalize() + if type == 'top?': + # Check sign of angle between two vectors + return (widgetDir.dot(widgetAxis) < 0.) + elif type == 'edge?': + # Checking to see if we are viewing edge-on + # Check angle between two vectors + return(abs(widgetDir.dot(widgetAxis)) < .2) + + def getWidgetsNearProjectionPoint(self): + # Find the position of the projection of the specified node path + # on the near plane + widgetOrigin = direct.widget.getPos(self.camera) + # project this onto near plane + return widgetOrigin * (self.chan.near / widgetOrigin[1]) + + def getScreenXY(self): + # Where does the widget's projection fall on the near plane + nearVec = self.getWidgetsNearProjectionPoint() + # Clamp these coordinates to visible screen + nearX = self.clamp(nearVec[0], self.chan.left, self.chan.right) + nearY = self.clamp(nearVec[2], self.chan.bottom, self.chan.top) + # What percentage of the distance across the screen is this? + percentX = (nearX - self.chan.left)/self.chan.nearWidth + percentY = (nearY - self.chan.bottom)/self.chan.nearHeight + # Map this percentage to the same -1 to 1 space as the mouse + screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0) + # Return the resulting value + return screenXY + + def rotate1D(self): + # Constrained 1D rotation about the widget's main axis (X,Y, or Z) + # Rotation depends upon circular motion of the mouse about the + # projection of the widget's origin on the image plane + # A complete circle about the widget results in a change in + # orientation of 360 degrees. + + # First initialize hit point/rotation angle + if self.fHitInit: + self.fHitInit = 0 + self.rotateAxis = self.constraint[:1] + self.fWidgetTop = self.widgetCheck('top?') + self.rotationCenter = self.getScreenXY() + self.lastCrankAngle = self.getCrankAngle() + + # Rotate widget based on how far cursor has swung around origin + newAngle = self.getCrankAngle() + deltaAngle = self.lastCrankAngle - newAngle + if self.fWidgetTop: + deltaAngle = -1 * deltaAngle + if self.rotateAxis == 'x': + direct.widget.setP(direct.widget, deltaAngle) + elif self.rotateAxis == 'y': + direct.widget.setR(direct.widget, -deltaAngle) + elif self.rotateAxis == 'z': + direct.widget.setH(direct.widget, deltaAngle) + # Record crank angle for next time around + self.lastCrankAngle = newAngle + + def relHpr(self, base, h, p, r): + # Compute widget2newWidget relative to base coordinate system + mWidget2Base = direct.widget.getMat(base) + mBase2NewBase = Mat4() + mBase2NewBase.composeMatrix( + UNIT_VEC, VBase3(h,p,r), ZERO_VEC, + CSDefault) + mBase2Widget = base.getMat(direct.widget) + mWidget2Parent = direct.widget.getMat() + # Compose the result + resultMat = mWidget2Base * mBase2NewBase + resultMat = resultMat * mBase2Widget + resultMat = resultMat * mWidget2Parent + # Extract and apply the hpr + hpr = Vec3(0) + decomposeMatrix(resultMat, VBase3(), hpr, VBase3(), + CSDefault) + direct.widget.setHpr(hpr) + + def rotate2D(self): + # Virtual trackball or arcball rotation of widget + # Rotation method depends upon variable dd-want-arcball + # Default is virtual trackball (handles 1D rotations better) + self.fHitInit = 1 + tumbleRate = 360 + # Mouse motion edge to edge of channel results in one full turn + self.relHpr(self.camera, + self.chan.mouseDeltaX * tumbleRate, + -self.chan.mouseDeltaY * tumbleRate, + 0) + + def scale3D(self): + # Scale the selected node based upon up down mouse motion + # Mouse motion from edge to edge results in a factor of 4 scaling + # From midpoint to edge doubles or halves objects scale + if self.fHitInit: + self.fHitInit = 0 + self.refNodePath.setPos(direct.widget, 0, 0, 0) + self.refNodePath.setHpr(self.camera, 0, 0, 0) + self.initScaleMag = Vec3( + self.objectHandles.getWidgetIntersectPt( + self.refNodePath, 'y')).length() + # record initial scale + self.initScale = direct.widget.getScale() + # Begin + # Scale factor is ratio current mag with init mag + currScale = ( + self.initScale * + (self.objectHandles.getWidgetIntersectPt( + self.refNodePath, 'y').length() / + self.initScaleMag) + ) + direct.widget.setScale(currScale) + + def clamp(self, val, min, max): + if val < min: + return min + elif val > max: + return max + else: + return val + + +class ObjectHandles(NodePath,PandaObject): + def __init__(self): + # Initialize the superclass + NodePath.__init__(self) + + # Load up object handles model and assign it to self + self.assign(loader.loadModel('models/misc/objectHandles')) + self.node().setName('objectHandles') + self.scalingNode = self.getChild(0) + self.scalingNode.node().setName('ohScalingNode') + self.ohScalingFactor = 1.0 + # To avoid recreating a vec every frame + self.hitPt = Vec3(0) + # Get a handle on the components + self.xHandles = self.find('**/X') + self.xPostGroup = self.xHandles.find('**/x-post-group') + self.xPostCollision = self.xHandles.find('**/x-post') + self.xRingGroup = self.xHandles.find('**/x-ring-group') + self.xRingCollision = self.xHandles.find('**/x-ring') + self.xDiscGroup = self.xHandles.find('**/x-disc-group') + self.xDisc = self.xHandles.find('**/x-disc-visible') + self.xDiscCollision = self.xHandles.find('**/x-disc') + + self.yHandles = self.find('**/Y') + self.yPostGroup = self.yHandles.find('**/y-post-group') + self.yPostCollision = self.yHandles.find('**/y-post') + self.yRingGroup = self.yHandles.find('**/y-ring-group') + self.yRingCollision = self.yHandles.find('**/y-ring') + self.yDiscGroup = self.yHandles.find('**/y-disc-group') + self.yDisc = self.yHandles.find('**/y-disc-visible') + self.yDiscCollision = self.yHandles.find('**/y-disc') + + self.zHandles = self.find('**/Z') + self.zPostGroup = self.zHandles.find('**/z-post-group') + self.zPostCollision = self.zHandles.find('**/z-post') + self.zRingGroup = self.zHandles.find('**/z-ring-group') + self.zRingCollision = self.zHandles.find('**/z-ring') + self.zDiscGroup = self.zHandles.find('**/z-disc-group') + self.zDisc = self.zHandles.find('**/z-disc-visible') + self.zDiscCollision = self.zHandles.find('**/z-disc') + + # Adjust visiblity, colors, and transparency + self.xPostCollision.hide() + self.xRingCollision.hide() + self.xDisc.setColor(1,0,0,.2) + self.yPostCollision.hide() + self.yRingCollision.hide() + self.yDisc.setColor(0,1,0,.2) + self.zPostCollision.hide() + self.zRingCollision.hide() + self.zDisc.setColor(0,0,1,.2) + # Augment geometry with lines + self.createObjectHandleLines() + # Create long markers to help line up in world + self.createGuideLines() + self.hideGuides() + + def coaModeColor(self): + self.setColor(.5,.5,.5,1) + + def manipModeColor(self): + self.clearColor() + + def enableHandles(self, handles): + if type(handles) == types.ListType: + for handle in handles: + self.enableHandle(handle) + elif handles == 'x': + self.enableHandles(['x-post','x-ring','x-disc']) + elif handles == 'y': + self.enableHandles(['y-post','y-ring','y-disc']) + elif handles == 'z': + self.enableHandles(['z-post','z-ring','z-disc']) + elif handles == 'post': + self.enableHandles(['x-post','y-post','z-post']) + elif handles == 'ring': + self.enableHandles(['x-ring','y-ring','z-ring']) + elif handles == 'disc': + self.enableHandles(['x-disc','y-disc','z-disc']) + elif handles == 'all': + self.enableHandles(['x-post','x-ring','x-disc', + 'y-post','y-ring','y-disc', + 'z-post','z-ring','z-disc']) + + def enableHandle(self, handle): + if handle == 'x-post': + self.xPostGroup.reparentTo(self.xHandles) + elif handle == 'x-ring': + self.xRingGroup.reparentTo(self.xHandles) + elif handle == 'x-disc': + self.xDiscGroup.reparentTo(self.xHandles) + if handle == 'y-post': + self.yPostGroup.reparentTo(self.yHandles) + elif handle == 'y-ring': + self.yRingGroup.reparentTo(self.yHandles) + elif handle == 'y-disc': + self.yDiscGroup.reparentTo(self.yHandles) + if handle == 'z-post': + self.zPostGroup.reparentTo(self.zHandles) + elif handle == 'z-ring': + self.zRingGroup.reparentTo(self.zHandles) + elif handle == 'z-disc': + self.zDiscGroup.reparentTo(self.zHandles) + + def disableHandles(self, handles): + if type(handles) == types.ListType: + for handle in handles: + self.disableHandle(handle) + elif handles == 'x': + self.disableHandles(['x-post','x-ring','x-disc']) + elif handles == 'y': + self.disableHandles(['y-post','y-ring','y-disc']) + elif handles == 'z': + self.disableHandles(['z-post','z-ring','z-disc']) + elif handles == 'post': + self.disableHandles(['x-post','y-post','z-post']) + elif handles == 'ring': + self.disableHandles(['x-ring','y-ring','z-ring']) + elif handles == 'disc': + self.disableHandles(['x-disc','y-disc','z-disc']) + elif handles == 'all': + self.disableHandles(['x-post','x-ring','x-disc', + 'y-post','y-ring','y-disc', + 'z-post','z-ring','z-disc']) + + def disableHandle(self, handle): + if handle == 'x-post': + self.xPostGroup.reparentTo(hidden) + elif handle == 'x-ring': + self.xRingGroup.reparentTo(hidden) + elif handle == 'x-disc': + self.xDiscGroup.reparentTo(hidden) + if handle == 'y-post': + self.yPostGroup.reparentTo(hidden) + elif handle == 'y-ring': + self.yRingGroup.reparentTo(hidden) + elif handle == 'y-disc': + self.yDiscGroup.reparentTo(hidden) + if handle == 'z-post': + self.zPostGroup.reparentTo(hidden) + elif handle == 'z-ring': + self.zRingGroup.reparentTo(hidden) + elif handle == 'z-disc': + self.zDiscGroup.reparentTo(hidden) + + def showAllHandles(self): + self.xPost.show() + self.xRing.show() + self.xDisc.show() + self.yPost.show() + self.yRing.show() + self.yDisc.show() + self.zPost.show() + self.zRing.show() + self.zDisc.show() + + def hideAllHandles(self): + self.xPost.hide() + self.xRing.hide() + self.xDisc.hide() + self.yPost.hide() + self.yRing.hide() + self.yDisc.hide() + self.zPost.hide() + self.zRing.hide() + self.zDisc.hide() + + def showHandle(self, handle): + if handle == 'x-post': + self.xPost.show() + elif handle == 'x-ring': + self.xRing.show() + elif handle == 'x-disc': + self.xDisc.show() + elif handle == 'y-post': + self.yPost.show() + elif handle == 'y-ring': + self.yRing.show() + elif handle == 'y-disc': + self.yDisc.show() + elif handle == 'z-post': + self.zPost.show() + elif handle == 'z-ring': + self.zRing.show() + elif handle == 'z-disc': + self.zDisc.show() + + def showGuides(self): + self.guideLines.show() + + def hideGuides(self): + self.guideLines.hide() + + def setScalingFactor(self, scaleFactor): + self.ohScalingFactor = scaleFactor + self.scalingNode.setScale(self.ohScalingFactor) + + def getScalingFactor(self): + return self.scalingNode.getScale() + + def transferObjectHandlesScale(self): + # see how much object handles have been scaled + ohs = self.getScale() + sns = self.scalingNode.getScale() + # Transfer this to the scaling node + self.scalingNode.setScale( + ohs[0] * sns[0], + ohs[1] * sns[1], + ohs[2] * sns[2]) + self.setScale(1) + + def multiplyScalingFactorBy(self, factor): + taskMgr.removeTasksNamed('resizeObjectHandles') + sf = self.ohScalingFactor = self.ohScalingFactor * factor + self.scalingNode.lerpScale(sf,sf,sf, 0.5, + blendType = 'easeInOut', + task = 'resizeObjectHandles') + + def growToFit(self): + taskMgr.removeTasksNamed('resizeObjectHandles') + # Increase handles scale until they cover 30% of the min dimension + pos = direct.widget.getPos(direct.camera) + minDim = min(direct.chan.nearWidth, direct.chan.nearHeight) + sf = 0.15 * minDim * (pos[1]/direct.chan.near) + self.ohScalingFactor = sf + self.scalingNode.lerpScale(sf,sf,sf, 0.5, + blendType = 'easeInOut', + task = 'resizeObjectHandles') + + def createObjectHandleLines(self): + # X post + self.xPost = self.xPostGroup.attachNewNode(NamedNode('x-post-visible')) + lines = LineNodePath(self.xPost) + lines.setColor(VBase4(1,0,0,1)) + lines.setThickness(5) + lines.moveTo(0,0,0) + lines.drawTo(1.5,0,0) + lines.create() + lines = LineNodePath(self.xPost) + lines.setColor(VBase4(1,0,0,1)) + lines.setThickness(1.5) + lines.moveTo(0,0,0) + lines.drawTo(-1.5,0,0) + lines.create() + + # X ring + self.xRing = self.xRingGroup.attachNewNode(NamedNode('x-ring-visible')) + lines = LineNodePath(self.xRing) + lines.setColor(VBase4(1,0,0,1)) + lines.setThickness(3) + lines.moveTo(0,1,0) + for ang in range(15, 370, 15): + lines.drawTo(0, + math.cos(deg2Rad(ang)), + math.sin(deg2Rad(ang))) + lines.create() + + # Y post + self.yPost = self.yPostGroup.attachNewNode(NamedNode('y-post-visible')) + lines = LineNodePath(self.yPost) + lines.setColor(VBase4(0,1,0,1)) + lines.setThickness(5) + lines.moveTo(0,0,0) + lines.drawTo(0,1.5,0) + lines.create() + lines = LineNodePath(self.yPost) + lines.setColor(VBase4(0,1,0,1)) + lines.setThickness(1.5) + lines.moveTo(0,0,0) + lines.drawTo(0,-1.5,0) + lines.create() + + # Y ring + self.yRing = self.yRingGroup.attachNewNode(NamedNode('y-ring-visible')) + lines = LineNodePath(self.yRing) + lines.setColor(VBase4(0,1,0,1)) + lines.setThickness(3) + lines.moveTo(1,0,0) + for ang in range(15, 370, 15): + lines.drawTo(math.cos(deg2Rad(ang)), + 0, + math.sin(deg2Rad(ang))) + lines.create() + + # Z post + self.zPost = self.zPostGroup.attachNewNode(NamedNode('z-post-visible')) + lines = LineNodePath(self.zPost) + lines.setColor(VBase4(0,0,1,1)) + lines.setThickness(5) + lines.moveTo(0,0,0) + lines.drawTo(0,0,1.5) + lines.create() + lines = LineNodePath(self.zPost) + lines.setColor(VBase4(0,0,1,1)) + lines.setThickness(1.5) + lines.moveTo(0,0,0) + lines.drawTo(0,0,-1.5) + lines.create() + + # Z ring + self.zRing = self.zRingGroup.attachNewNode(NamedNode('z-ring-visible')) + lines = LineNodePath(self.zRing) + lines.setColor(VBase4(0,0,1,1)) + lines.setThickness(3) + lines.moveTo(1,0,0) + for ang in range(15, 370, 15): + lines.drawTo(math.cos(deg2Rad(ang)), + math.sin(deg2Rad(ang)), + 0) + lines.create() + + def createGuideLines(self): + self.guideLines = self.attachNewNode(NamedNode('guideLines')) + # X guide lines + lines = LineNodePath(self.guideLines) + lines.setColor(VBase4(1,0,0,1)) + lines.setThickness(0.5) + lines.moveTo(-500,0,0) + lines.drawTo(500,0,0) + lines.create() + lines.node().setName('x-guide') + + # Y guide lines + lines = LineNodePath(self.guideLines) + lines.setColor(VBase4(0,1,0,1)) + lines.setThickness(0.5) + lines.moveTo(0,-500,0) + lines.drawTo(0,500,0) + lines.create() + lines.node().setName('y-guide') + + # Z guide lines + lines = LineNodePath(self.guideLines) + lines.setColor(VBase4(0,0,1,1)) + lines.setThickness(0.5) + lines.moveTo(0,0,-500) + lines.drawTo(0,0,500) + lines.create() + lines.node().setName('z-guide') + + def getAxisIntersectPt(self, axis): + # Calc the xfrom from camera to widget + mCam2Widget = direct.camera.getMat(direct.widget) + lineDir = Vec3(mCam2Widget.xformVec(direct.chan.nearVec)) + lineDir.normalize() + # And determine where the viewpoint is relative to widget + lineOrigin = VBase3(0) + decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin, + CSDefault) + # Now see where this hits the plane containing the 1D motion axis. + # Pick the intersection plane most normal to the intersection ray + # by comparing lineDir with plane normals. The plane with the + # largest dotProduct is most "normal" + if axis == 'x': + if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))): + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS)) + else: + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS)) + # We really only care about the nearest point on the axis + self.hitPt.setY(0) + self.hitPt.setZ(0) + elif axis == 'y': + if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))): + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS)) + else: + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS)) + # We really only care about the nearest point on the axis + self.hitPt.setX(0) + self.hitPt.setZ(0) + elif axis == 'z': + if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))): + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS)) + else: + self.hitPt.assign( + planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS)) + # We really only care about the nearest point on the axis + self.hitPt.setX(0) + self.hitPt.setY(0) + return self.hitPt + + def getWidgetIntersectPt(self, nodePath, plane): + # Find out the point of interection of the ray passing though the mouse + # with the plane containing the 2D xlation or 1D rotation widgets + + # Calc the xfrom from camera to the nodePath + mCam2NodePath = direct.camera.getMat(nodePath) + + # And determine where the viewpoint is relative to widget + lineOrigin = VBase3(0) + decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin, + CSDefault) + + # Next we find the vector from viewpoint to the widget through + # the mouse's position on near plane. + # This defines the intersection ray + lineDir = Vec3(mCam2NodePath.xformVec(direct.chan.nearVec)) + lineDir.normalize() + # Find the hit point + if plane == 'x': + self.hitPt.assign(planeIntersect( + lineOrigin, lineDir, ORIGIN, X_AXIS)) + elif plane == 'y': + self.hitPt.assign(planeIntersect( + lineOrigin, lineDir, ORIGIN, Y_AXIS)) + elif plane == 'z': + self.hitPt.assign(planeIntersect( + lineOrigin, lineDir, ORIGIN, Z_AXIS)) + return self.hitPt + + + diff --git a/direct/src/directutil/DirectSelection.py b/direct/src/directutil/DirectSelection.py index 0a73e86122..30cb5e3ba2 100644 --- a/direct/src/directutil/DirectSelection.py +++ b/direct/src/directutil/DirectSelection.py @@ -1,380 +1,379 @@ -from PandaObject import * -from DirectGeometry import * -from DirectSelection import * - - -class DirectNodePath(NodePath): - # A node path augmented with info, bounding box, and utility methods - def __init__(self, nodePath): - # Initialize the superclass - NodePath.__init__(self) - self.assign(nodePath) - # Get a reasonable name - self.name = self.getName() - # Create a bounding box - self.bbox = DirectBoundingBox(self) - center = self.bbox.getCenter() - # Create matrix to hold the offset between the nodepath - # and its center of action (COA) - self.mCoa2Dnp = Mat4() - self.mCoa2Dnp.assign(Mat4.identMat()) - # self.mCoa2Dnp.setRow(3, Vec4(center[0], center[1], center[2], 1)) - # Transform from nodePath to widget - self.mDnp2Widget = Mat4() - self.mDnp2Widget.assign(Mat4.identMat()) - - def highlight(self): - self.bbox.show() - - def dehighlight(self): - self.bbox.hide() - - def getCenter(self): - return self.bbox.getCenter() - - def getRadius(self): - return self.bbox.getRadius() - - def getMin(self): - return self.bbox.getMin() - - def getMax(self): - return self.bbox.getMax() - - def __repr__(self): - return ('NodePath:\t%s\n' % self.name) - - -class SelectedNodePaths(PandaObject): - def __init__(self,direct): - self.direct = direct - self.selectedDict = {} - self.deselectedDict = {} - self.last = None - - def select(self, nodePath, fMultiSelect = 0): - # Do nothing if nothing selected - if not nodePath: - print 'Nothing selected!!' - return None - - # Reset selected objects and highlight if multiSelect is false - if not fMultiSelect: - self.deselectAll() - - # Get this pointer - id = nodePath.id() - # First see if its already in the selected dictionary - dnp = self.selectedDict.get(id, None) - # If so, we're done - if not dnp: - # See if it is in the deselected dictionary - dnp = self.deselectedDict.get(id, None) - if dnp: - # It has been previously selected: - # Show its bounding box - dnp.highlight() - # Remove it from the deselected dictionary - del(self.deselectedDict[id]) - else: - # Didn't find it, create a new selectedNodePath instance - dnp = DirectNodePath(nodePath) - # Show its bounding box - dnp.highlight() - # Add it to the selected dictionary - self.selectedDict[dnp.id()] = dnp - # And update last - self.last = dnp - return dnp - - def deselect(self, nodePath): - # Get this pointer - id = nodePath.id() - # See if it is in the selected dictionary - dnp = self.selectedDict.get(id, None) - if dnp: - # It was selected: - # Hide its bounding box - dnp.dehighlight() - # Remove it from the selected dictionary - del(self.selectedDict[id]) - # And keep track of it in the deselected dictionary - self.deselectedDict[id] = dnp - return dnp - - def selectedAsList(self): - list = [] - for key in self.selectedDict.keys(): - list.append(self.selectedDict[key]) - return list - - def __getitem__(self,index): - return self.selectedAsList()[index] - - def deselectedAsList(self): - list = [] - for key in self.deselectedDict.keys(): - list.append(self.deselectedDict[key]) - return list - - def forEachSelectedNodePathDo(self, func): - duplicateKeys = self.selectedDict.keys()[:] - for key in duplicateKeys: - func(self.selectedDict[key]) - - def forEachDeselectedNodePathDo(self, func): - duplicateKeys = self.deselectedDict.keys()[:] - for key in duplicateKeys: - func(self.deselectedDict[key]) - - def getWrtAll(self): - self.forEachSelectedNodePathDo(self.getWrt) - - def getWrt(self, nodePath): - nodePath.mDnp2Widget.assign(nodePath.getMat(self.direct.widget)) - - def moveWrtWidgetAll(self): - self.forEachSelectedNodePathDo(self.moveWrtWidget) - - def moveWrtWidget(self, nodePath): - nodePath.setMat(self.direct.widget, nodePath.mDnp2Widget) - - def deselectAll(self): - self.forEachSelectedNodePathDo(self.deselect) - - def highlightAll(self): - self.forEachSelectedNodePathDo(DirectNodePath.highlight) - - def dehighlightAll(self): - self.forEachSelectedNodePathDo(DirectNodePath.dehighlight) - - def removeSelected(self): - selected = self.dnp.last - if selected: - selected.remove() - - def removeAll(self): - # Remove all selected nodePaths from the Scene Graph - self.forEachSelectedNodePathDo(NodePath.remove) - - def toggleVizSelected(self): - selected = self.dnp.last - # Toggle visibility of selected node paths - if selected: - selected.toggleViz() - - def toggleVizAll(self): - # Toggle viz for all selected node paths - self.forEachSelectedNodePathDo(NodePath.toggleViz) - - def isolateSelected(self): - selected = self.dnp.last - if selected: - selected.isolate() - - def getDirectNodePath(self, nodePath): - # Get this pointer - id = nodePath.id() - # First check selected dict - dnp = self.selectedDict.get(id, None) - if dnp: - return dnp - # Otherwise return result of deselected search - return self.selectedDict.get(id, None) - - def getNumSelected(self): - return len(self.selectedDict.keys()) - - -class DirectBoundingBox: - def __init__(self, nodePath): - # Record the node path - self.nodePath = nodePath - # Compute bounds, min, max, etc. - self.computeBounds() - # Generate the bounding box - self.lines = self.createBBoxLines() - - def computeBounds(self): - self.bounds = self.nodePath.getBounds() - if self.bounds.isEmpty(): - self.center = Point3(0) - self.radius = 1.0 - else: - self.center = self.bounds.getCenter() - self.radius = self.bounds.getRadius() - self.min = Point3(self.center - Point3(self.radius)) - self.max = Point3(self.center + Point3(self.radius)) - - def createBBoxLines(self): - # Create a line segments object for the bbox - lines = LineNodePath(hidden) - lines.node().setName('bboxLines') - lines.setColor( VBase4( 1., 0., 0., 1. ) ) - lines.setThickness( 0.5 ) - - minX = self.min[0] - minY = self.min[1] - minZ = self.min[2] - maxX = self.max[0] - maxY = self.max[1] - maxZ = self.max[2] - - # Bottom face - lines.moveTo( minX, minY, minZ ) - lines.drawTo( maxX, minY, minZ ) - lines.drawTo( maxX, maxY, minZ ) - lines.drawTo( minX, maxY, minZ ) - lines.drawTo( minX, minY, minZ ) - - # Front Edge/Top face - lines.drawTo( minX, minY, maxZ ) - lines.drawTo( maxX, minY, maxZ ) - lines.drawTo( maxX, maxY, maxZ ) - lines.drawTo( minX, maxY, maxZ ) - lines.drawTo( minX, minY, maxZ ) - - # Three remaining edges - lines.moveTo( maxX, minY, minZ ) - lines.drawTo( maxX, minY, maxZ ) - lines.moveTo( maxX, maxY, minZ ) - lines.drawTo( maxX, maxY, maxZ ) - lines.moveTo( minX, maxY, minZ ) - lines.drawTo( minX, maxY, maxZ ) - - # Create and return bbox lines - lines.create() - return lines - - def updateBBoxLines(self): - ls = self.lines.lineSegs - - minX = self.min[0] - minY = self.min[1] - minZ = self.min[2] - maxX = self.max[0] - maxY = self.max[1] - maxZ = self.max[2] - - # Bottom face - ls.setVertex( 0, minX, minY, minZ ) - ls.setVertex( 1, maxX, minY, minZ ) - ls.setVertex( 2, maxX, maxY, minZ ) - ls.setVertex( 3, minX, maxY, minZ ) - ls.setVertex( 4, minX, minY, minZ ) - - # Front Edge/Top face - ls.setVertex( 5, minX, minY, maxZ ) - ls.setVertex( 6, maxX, minY, maxZ ) - ls.setVertex( 7, maxX, maxY, maxZ ) - ls.setVertex( 8, minX, maxY, maxZ ) - ls.setVertex( 9, minX, minY, maxZ ) - - # Three remaining edges - ls.setVertex( 10, maxX, minY, minZ ) - ls.setVertex( 11, maxX, minY, maxZ ) - ls.setVertex( 12, maxX, maxY, minZ ) - ls.setVertex( 13, maxX, maxY, maxZ ) - ls.setVertex( 14, minX, maxY, minZ ) - ls.setVertex( 15, minX, maxY, maxZ ) - - def getBounds(self): - # Get a node path's bounds - nodeBounds = self.nodePath.node().getBound() - for child in self.nodePath.getChildrenAsList(): - nodeBounds.extendBy(child.getBottomArc().getBound()) - return nodeBounds.makeCopy() - - def show(self): - self.lines.reparentTo(self.nodePath) - - def hide(self): - self.lines.reparentTo(hidden) - - def getCenter(self): - return self.center - - def getRadius(self): - return self.radius - - def getMin(self): - return self.min - - def getMax(self): - return self.max - - def vecAsString(self, vec): - return '%.2f %.2f %.2f' % (vec[0], vec[1], vec[2]) - - def __repr__(self): - return (`self.__class__` + - '\nNodePath:\t%s\n' % self.nodePath.getName() + - 'Min:\t\t%s\n' % self.vecAsString(self.min) + - 'Max:\t\t%s\n' % self.vecAsString(self.max) + - 'Center:\t\t%s\n' % self.vecAsString(self.center) + - 'Radius:\t\t%.2f' % self.radius - ) - - -class SelectionRay: - def __init__(self, camera): - # Record the camera associated with this selection ray - self.camera = camera - # Create a collision node - self.rayCollisionNodePath = camera.attachNewNode( CollisionNode() ) - # Don't pay the penalty of drawing this collision ray - self.rayCollisionNodePath.hide() - self.rayCollisionNode = self.rayCollisionNodePath.node() - # Intersect with geometry to begin with - self.collideWithGeom() - # Create a collision ray - self.ray = CollisionRay() - # Add the ray to the collision Node - self.rayCollisionNode.addSolid( self.ray ) - # Create a queue to hold the collision results - self.cq = CollisionHandlerQueue() - self.numEntries = 0 - # And a traverser to do the actual collision tests - self.ct = CollisionTraverser( RenderRelation.getClassType() ) - # Let the traverser know about the queue and the collision node - self.ct.addCollider(self.rayCollisionNode, self.cq ) - - def pickGeom(self, targetNodePath, mouseX, mouseY): - self.collideWithGeom() - return self.pick(targetNodePath, mouseX, mouseY) - - def pickWidget(self, targetNodePath, mouseX, mouseY): - self.collideWithWidget() - return self.pick(targetNodePath, mouseX, mouseY) - - def pick(self, targetNodePath, mouseX, mouseY): - # Determine ray direction based upon the mouse coordinates - # Note! This has to be a cam object (of type ProjectionNode) - self.ray.setProjection( base.cam.node(), mouseX, mouseY ) - self.ct.traverse( targetNodePath.node() ) - self.numEntries = self.cq.getNumEntries() - self.cq.sortEntries() - return self.numEntries - - def collideWithGeom(self): - self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff()) - self.rayCollisionNode.setFromCollideMask(BitMask32().allOff()) - self.rayCollisionNode.setCollideGeom(1) - - def collideWithWidget(self): - self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff()) - mask = BitMask32() - mask.setWord(0x80000000) - self.rayCollisionNode.setFromCollideMask(mask) - self.rayCollisionNode.setCollideGeom(0) - - def objectToHitPt(self, index): - return self.cq.getEntry(index).getIntoIntersectionPoint() - - def camToHitPt(self, index): - # Get the specified entry - entry = self.cq.getEntry(index) - hitPt = entry.getIntoIntersectionPoint() - # Convert point from object local space to camera space - return entry.getInvWrtSpace().xformPoint(hitPt) - +from PandaObject import * +from DirectGeometry import * +from DirectSelection import * + + +class DirectNodePath(NodePath): + # A node path augmented with info, bounding box, and utility methods + def __init__(self, nodePath): + # Initialize the superclass + NodePath.__init__(self) + self.assign(nodePath) + # Get a reasonable name + self.name = self.getName() + # Create a bounding box + self.bbox = DirectBoundingBox(self) + center = self.bbox.getCenter() + # Create matrix to hold the offset between the nodepath + # and its center of action (COA) + self.mCoa2Dnp = Mat4() + self.mCoa2Dnp.assign(Mat4.identMat()) + # self.mCoa2Dnp.setRow(3, Vec4(center[0], center[1], center[2], 1)) + # Transform from nodePath to widget + self.mDnp2Widget = Mat4() + self.mDnp2Widget.assign(Mat4.identMat()) + + def highlight(self): + self.bbox.show() + + def dehighlight(self): + self.bbox.hide() + + def getCenter(self): + return self.bbox.getCenter() + + def getRadius(self): + return self.bbox.getRadius() + + def getMin(self): + return self.bbox.getMin() + + def getMax(self): + return self.bbox.getMax() + + def __repr__(self): + return ('NodePath:\t%s\n' % self.name) + + +class SelectedNodePaths(PandaObject): + def __init__(self): + self.selectedDict = {} + self.deselectedDict = {} + self.last = None + + def select(self, nodePath, fMultiSelect = 0): + # Do nothing if nothing selected + if not nodePath: + print 'Nothing selected!!' + return None + + # Reset selected objects and highlight if multiSelect is false + if not fMultiSelect: + self.deselectAll() + + # Get this pointer + id = nodePath.id() + # First see if its already in the selected dictionary + dnp = self.selectedDict.get(id, None) + # If so, we're done + if not dnp: + # See if it is in the deselected dictionary + dnp = self.deselectedDict.get(id, None) + if dnp: + # It has been previously selected: + # Show its bounding box + dnp.highlight() + # Remove it from the deselected dictionary + del(self.deselectedDict[id]) + else: + # Didn't find it, create a new selectedNodePath instance + dnp = DirectNodePath(nodePath) + # Show its bounding box + dnp.highlight() + # Add it to the selected dictionary + self.selectedDict[dnp.id()] = dnp + # And update last + self.last = dnp + return dnp + + def deselect(self, nodePath): + # Get this pointer + id = nodePath.id() + # See if it is in the selected dictionary + dnp = self.selectedDict.get(id, None) + if dnp: + # It was selected: + # Hide its bounding box + dnp.dehighlight() + # Remove it from the selected dictionary + del(self.selectedDict[id]) + # And keep track of it in the deselected dictionary + self.deselectedDict[id] = dnp + return dnp + + def selectedAsList(self): + list = [] + for key in self.selectedDict.keys(): + list.append(self.selectedDict[key]) + return list + + def __getitem__(self,index): + return self.selectedAsList()[index] + + def deselectedAsList(self): + list = [] + for key in self.deselectedDict.keys(): + list.append(self.deselectedDict[key]) + return list + + def forEachSelectedNodePathDo(self, func): + duplicateKeys = self.selectedDict.keys()[:] + for key in duplicateKeys: + func(self.selectedDict[key]) + + def forEachDeselectedNodePathDo(self, func): + duplicateKeys = self.deselectedDict.keys()[:] + for key in duplicateKeys: + func(self.deselectedDict[key]) + + def getWrtAll(self): + self.forEachSelectedNodePathDo(self.getWrt) + + def getWrt(self, nodePath): + nodePath.mDnp2Widget.assign(nodePath.getMat(direct.widget)) + + def moveWrtWidgetAll(self): + self.forEachSelectedNodePathDo(self.moveWrtWidget) + + def moveWrtWidget(self, nodePath): + nodePath.setMat(direct.widget, nodePath.mDnp2Widget) + + def deselectAll(self): + self.forEachSelectedNodePathDo(self.deselect) + + def highlightAll(self): + self.forEachSelectedNodePathDo(DirectNodePath.highlight) + + def dehighlightAll(self): + self.forEachSelectedNodePathDo(DirectNodePath.dehighlight) + + def removeSelected(self): + selected = self.dnp.last + if selected: + selected.remove() + + def removeAll(self): + # Remove all selected nodePaths from the Scene Graph + self.forEachSelectedNodePathDo(NodePath.remove) + + def toggleVizSelected(self): + selected = self.dnp.last + # Toggle visibility of selected node paths + if selected: + selected.toggleViz() + + def toggleVizAll(self): + # Toggle viz for all selected node paths + self.forEachSelectedNodePathDo(NodePath.toggleViz) + + def isolateSelected(self): + selected = self.dnp.last + if selected: + selected.isolate() + + def getDirectNodePath(self, nodePath): + # Get this pointer + id = nodePath.id() + # First check selected dict + dnp = self.selectedDict.get(id, None) + if dnp: + return dnp + # Otherwise return result of deselected search + return self.selectedDict.get(id, None) + + def getNumSelected(self): + return len(self.selectedDict.keys()) + + +class DirectBoundingBox: + def __init__(self, nodePath): + # Record the node path + self.nodePath = nodePath + # Compute bounds, min, max, etc. + self.computeBounds() + # Generate the bounding box + self.lines = self.createBBoxLines() + + def computeBounds(self): + self.bounds = self.nodePath.getBounds() + if self.bounds.isEmpty(): + self.center = Point3(0) + self.radius = 1.0 + else: + self.center = self.bounds.getCenter() + self.radius = self.bounds.getRadius() + self.min = Point3(self.center - Point3(self.radius)) + self.max = Point3(self.center + Point3(self.radius)) + + def createBBoxLines(self): + # Create a line segments object for the bbox + lines = LineNodePath(hidden) + lines.node().setName('bboxLines') + lines.setColor( VBase4( 1., 0., 0., 1. ) ) + lines.setThickness( 0.5 ) + + minX = self.min[0] + minY = self.min[1] + minZ = self.min[2] + maxX = self.max[0] + maxY = self.max[1] + maxZ = self.max[2] + + # Bottom face + lines.moveTo( minX, minY, minZ ) + lines.drawTo( maxX, minY, minZ ) + lines.drawTo( maxX, maxY, minZ ) + lines.drawTo( minX, maxY, minZ ) + lines.drawTo( minX, minY, minZ ) + + # Front Edge/Top face + lines.drawTo( minX, minY, maxZ ) + lines.drawTo( maxX, minY, maxZ ) + lines.drawTo( maxX, maxY, maxZ ) + lines.drawTo( minX, maxY, maxZ ) + lines.drawTo( minX, minY, maxZ ) + + # Three remaining edges + lines.moveTo( maxX, minY, minZ ) + lines.drawTo( maxX, minY, maxZ ) + lines.moveTo( maxX, maxY, minZ ) + lines.drawTo( maxX, maxY, maxZ ) + lines.moveTo( minX, maxY, minZ ) + lines.drawTo( minX, maxY, maxZ ) + + # Create and return bbox lines + lines.create() + return lines + + def updateBBoxLines(self): + ls = self.lines.lineSegs + + minX = self.min[0] + minY = self.min[1] + minZ = self.min[2] + maxX = self.max[0] + maxY = self.max[1] + maxZ = self.max[2] + + # Bottom face + ls.setVertex( 0, minX, minY, minZ ) + ls.setVertex( 1, maxX, minY, minZ ) + ls.setVertex( 2, maxX, maxY, minZ ) + ls.setVertex( 3, minX, maxY, minZ ) + ls.setVertex( 4, minX, minY, minZ ) + + # Front Edge/Top face + ls.setVertex( 5, minX, minY, maxZ ) + ls.setVertex( 6, maxX, minY, maxZ ) + ls.setVertex( 7, maxX, maxY, maxZ ) + ls.setVertex( 8, minX, maxY, maxZ ) + ls.setVertex( 9, minX, minY, maxZ ) + + # Three remaining edges + ls.setVertex( 10, maxX, minY, minZ ) + ls.setVertex( 11, maxX, minY, maxZ ) + ls.setVertex( 12, maxX, maxY, minZ ) + ls.setVertex( 13, maxX, maxY, maxZ ) + ls.setVertex( 14, minX, maxY, minZ ) + ls.setVertex( 15, minX, maxY, maxZ ) + + def getBounds(self): + # Get a node path's bounds + nodeBounds = self.nodePath.node().getBound() + for child in self.nodePath.getChildrenAsList(): + nodeBounds.extendBy(child.getBottomArc().getBound()) + return nodeBounds.makeCopy() + + def show(self): + self.lines.reparentTo(self.nodePath) + + def hide(self): + self.lines.reparentTo(hidden) + + def getCenter(self): + return self.center + + def getRadius(self): + return self.radius + + def getMin(self): + return self.min + + def getMax(self): + return self.max + + def vecAsString(self, vec): + return '%.2f %.2f %.2f' % (vec[0], vec[1], vec[2]) + + def __repr__(self): + return (`self.__class__` + + '\nNodePath:\t%s\n' % self.nodePath.getName() + + 'Min:\t\t%s\n' % self.vecAsString(self.min) + + 'Max:\t\t%s\n' % self.vecAsString(self.max) + + 'Center:\t\t%s\n' % self.vecAsString(self.center) + + 'Radius:\t\t%.2f' % self.radius + ) + + +class SelectionRay: + def __init__(self, camera): + # Record the camera associated with this selection ray + self.camera = camera + # Create a collision node + self.rayCollisionNodePath = camera.attachNewNode( CollisionNode() ) + # Don't pay the penalty of drawing this collision ray + self.rayCollisionNodePath.hide() + self.rayCollisionNode = self.rayCollisionNodePath.node() + # Intersect with geometry to begin with + self.collideWithGeom() + # Create a collision ray + self.ray = CollisionRay() + # Add the ray to the collision Node + self.rayCollisionNode.addSolid( self.ray ) + # Create a queue to hold the collision results + self.cq = CollisionHandlerQueue() + self.numEntries = 0 + # And a traverser to do the actual collision tests + self.ct = CollisionTraverser( RenderRelation.getClassType() ) + # Let the traverser know about the queue and the collision node + self.ct.addCollider(self.rayCollisionNode, self.cq ) + + def pickGeom(self, targetNodePath, mouseX, mouseY): + self.collideWithGeom() + return self.pick(targetNodePath, mouseX, mouseY) + + def pickWidget(self, targetNodePath, mouseX, mouseY): + self.collideWithWidget() + return self.pick(targetNodePath, mouseX, mouseY) + + def pick(self, targetNodePath, mouseX, mouseY): + # Determine ray direction based upon the mouse coordinates + # Note! This has to be a cam object (of type ProjectionNode) + self.ray.setProjection( base.cam.node(), mouseX, mouseY ) + self.ct.traverse( targetNodePath.node() ) + self.numEntries = self.cq.getNumEntries() + self.cq.sortEntries() + return self.numEntries + + def collideWithGeom(self): + self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff()) + self.rayCollisionNode.setFromCollideMask(BitMask32().allOff()) + self.rayCollisionNode.setCollideGeom(1) + + def collideWithWidget(self): + self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff()) + mask = BitMask32() + mask.setWord(0x80000000) + self.rayCollisionNode.setFromCollideMask(mask) + self.rayCollisionNode.setCollideGeom(0) + + def objectToHitPt(self, index): + return self.cq.getEntry(index).getIntoIntersectionPoint() + + def camToHitPt(self, index): + # Get the specified entry + entry = self.cq.getEntry(index) + hitPt = entry.getIntoIntersectionPoint() + # Convert point from object local space to camera space + return entry.getInvWrtSpace().xformPoint(hitPt) + diff --git a/direct/src/directutil/DirectSession.py b/direct/src/directutil/DirectSession.py index 64abf43f17..bd0a10dd2a 100644 --- a/direct/src/directutil/DirectSession.py +++ b/direct/src/directutil/DirectSession.py @@ -5,10 +5,14 @@ from DirectSelection import * from DirectGrid import * from DirectGeometry import * import OnscreenText +import __builtin__ class DirectSession(PandaObject): def __init__(self): + # Establish a global pointer to the direct object early on + # so dependant classes can access it in their code + __builtin__.direct = self self.contextList = [] self.iRayList = [] for camera in base.cameraList: @@ -17,14 +21,14 @@ class DirectSession(PandaObject): self.chan = self.getChanData(0) self.camera = base.cameraList[0] - self.cameraControl = DirectCameraControl(self) - self.manipulationControl = DirectManipulationControl(self) + self.cameraControl = DirectCameraControl() + self.manipulationControl = DirectManipulationControl() self.useObjectHandles() - self.grid = DirectGrid(self) + self.grid = DirectGrid() self.grid.disable() # Initialize the collection of selected nodePaths - self.selected = SelectedNodePaths(self) + self.selected = SelectedNodePaths() self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 ) # self.readout.textNode.setCardColor(0.5, 0.5, 0.5, 0.5) diff --git a/direct/src/directutil/DirectSessionGlobal.py b/direct/src/directutil/DirectSessionGlobal.py index 4ac2144008..86e789e83f 100644 --- a/direct/src/directutil/DirectSessionGlobal.py +++ b/direct/src/directutil/DirectSessionGlobal.py @@ -1,10 +1,11 @@ -from ShowBaseGlobal import * - -# If specified in the user's Configrc, create the direct session -if base.wantDIRECT: - from DirectSession import * - direct = base.direct = DirectSession() -else: - # Otherwise set the values to None - direct = base.direct = None - +from ShowBaseGlobal import * +import __builtin__ + +# If specified in the user's Configrc, create the direct session +if base.wantDIRECT: + from DirectSession import * + __builtin__.direct = base.direct = DirectSession() +else: + # Otherwise set the values to None + __builtin__.direct = base.direct = None + diff --git a/direct/src/showbase/ShowBaseGlobal.py b/direct/src/showbase/ShowBaseGlobal.py index 00fe2051e3..0dd3e5a8bd 100644 --- a/direct/src/showbase/ShowBaseGlobal.py +++ b/direct/src/showbase/ShowBaseGlobal.py @@ -1,15 +1,16 @@ """instantiate global ShowBase object""" from ShowBase import * +import __builtin__ -base = ShowBase() +__builtin__.base = ShowBase() # Make some global aliases for convenience -render2d = base.render2d -render = base.render -hidden = base.hidden -camera = base.camera -loader = base.loader -ostream = Notify.out() -run = base.run -tkroot = base.tkroot +__builtin__.render2d = base.render2d +__builtin__.render = base.render +__builtin__.hidden = base.hidden +__builtin__.camera = base.camera +__builtin__.loader = base.loader +__builtin__.ostream = Notify.out() +__builtin__.run = base.run +__builtin__.tkroot = base.tkroot