mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 01:07:51 -04:00
*** empty log message ***
This commit is contained in:
parent
1a1b9ae9c2
commit
8ecb2f612c
@ -1,5 +1,5 @@
|
||||
from PandaObject import *
|
||||
from DirectGeometry import useDirectRenderStyle
|
||||
from DirectGeometry import *
|
||||
|
||||
CAM_MOVE_DURATION = 1.0
|
||||
COA_MARKER_SF = 0.0075
|
||||
@ -19,26 +19,24 @@ class DirectCameraControl(PandaObject):
|
||||
useDirectRenderStyle(self.coaMarker)
|
||||
self.coaMarkerPos = Point3(0)
|
||||
self.camManipRef = direct.group.attachNewNode('camManipRef')
|
||||
self.zeroBaseVec = VBase3(0)
|
||||
self.zeroVector = Vec3(0)
|
||||
self.centerVec = Vec3(0, 1, 0)
|
||||
self.zeroPoint = Point3(0)
|
||||
t = CAM_MOVE_DURATION
|
||||
self.actionEvents = [
|
||||
['handleMouse2', self.mouseFlyStart],
|
||||
['handleMouse2Up', self.mouseFlyStop],
|
||||
['u', self.uprightCam],
|
||||
['c', self.centerCamIn, 0.5],
|
||||
['h', self.homeCam],
|
||||
['f', self.fitOnWidget],
|
||||
[`1`, self.SpawnMoveToView, 1],
|
||||
[`2`, self.SpawnMoveToView, 2],
|
||||
[`3`, self.SpawnMoveToView, 3],
|
||||
[`4`, self.SpawnMoveToView, 4],
|
||||
[`5`, self.SpawnMoveToView, 5],
|
||||
[`6`, self.SpawnMoveToView, 6],
|
||||
[`7`, self.SpawnMoveToView, 7],
|
||||
[`8`, self.SpawnMoveToView, 8],
|
||||
['h', self.homeCam],
|
||||
['m', self.moveToFit],
|
||||
['u', self.orbitUprightCam],
|
||||
['U', self.uprightCam],
|
||||
[`1`, self.spawnMoveToView, 1],
|
||||
[`2`, self.spawnMoveToView, 2],
|
||||
[`3`, self.spawnMoveToView, 3],
|
||||
[`4`, self.spawnMoveToView, 4],
|
||||
[`5`, self.spawnMoveToView, 5],
|
||||
[`6`, self.spawnMoveToView, 6],
|
||||
[`7`, self.spawnMoveToView, 7],
|
||||
[`8`, self.spawnMoveToView, 8],
|
||||
['9', self.swingCamAboutWidget, -90.0, t],
|
||||
['0', self.swingCamAboutWidget, 90.0, t],
|
||||
['`', self.removeManipulateCameraTask],
|
||||
@ -59,102 +57,241 @@ class DirectCameraControl(PandaObject):
|
||||
# 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,direct.dr.mouseX,direct.dr.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 * direct.dr.near)) |
|
||||
(coaDist > direct.dr.far)):
|
||||
# Just use existing point
|
||||
coa.assign(self.coaMarker.getPos(direct.camera))
|
||||
coaDist = Vec3(coa - self.zeroPoint).length()
|
||||
if coaDist < (1.1 * direct.dr.near):
|
||||
coa.set(0,100,0)
|
||||
coaDist = 100
|
||||
# Check for a hit point based on
|
||||
# current mouse position
|
||||
# And then spawn task to determine mouse mode
|
||||
numEntries = direct.iRay.pickGeom(
|
||||
render,direct.dr.mouseX,direct.dr.mouseY)
|
||||
# Sort intersection points
|
||||
direct.iRay.cq.sortEntries()
|
||||
# Then 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:
|
||||
# If no intersection point:
|
||||
# Use existing point
|
||||
# Not one of the widgets, use it
|
||||
indexList.append(i)
|
||||
coa = Point3(0)
|
||||
if(indexList):
|
||||
# Grab first point (it should be the closest)
|
||||
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 - ZERO_POINT).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 - ZERO_POINT).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 * direct.dr.near)) |
|
||||
(coaDist > direct.dr.far)):
|
||||
# Just use existing point
|
||||
coa.assign(self.coaMarker.getPos(direct.camera))
|
||||
coaDist = Vec3(coa - self.zeroPoint).length()
|
||||
coaDist = Vec3(coa - ZERO_POINT).length()
|
||||
if coaDist < (1.1 * direct.dr.near):
|
||||
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()
|
||||
else:
|
||||
# If no intersection point:
|
||||
# Use existing point
|
||||
coa.assign(self.coaMarker.getPos(direct.camera))
|
||||
coaDist = Vec3(coa - ZERO_POINT).length()
|
||||
# Check again its not to close
|
||||
if coaDist < (1.1 * direct.dr.near):
|
||||
coa.set(0,100,0)
|
||||
coaDist = 100
|
||||
# Update coa and marker
|
||||
self.updateCoa(coa, coaDist)
|
||||
# Start manipulation
|
||||
self.spawnXZTranslateOrHPanYZoom()
|
||||
# END MOUSE IN CENTRAL REGION
|
||||
else:
|
||||
# Mouse is in outer frame, spawn mouseRotateTask
|
||||
self.spawnMouseRotateTask()
|
||||
if ((abs(self.initMouseX) > 0.9) & (abs(self.initMouseY) > 0.9)):
|
||||
# Mouse is in corners, spawn roll task
|
||||
self.spawnMouseRollTask()
|
||||
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 spawnXZTranslateOrHPanYZoom(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Spawn the new task
|
||||
t = Task.Task(self.XZTranslateOrHPanYZoomTask)
|
||||
# For HPanYZoom
|
||||
t.zoomSF = Vec3(self.coa).length()
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def determineMouseFlyModeTask(self, state):
|
||||
deltaX = direct.dr.mouseX - self.initMouseX
|
||||
deltaY = direct.dr.mouseY - self.initMouseY
|
||||
if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)):
|
||||
return Task.cont
|
||||
def spawnXZTranslateOrHPPan(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Spawn new task
|
||||
taskMgr.spawnMethodNamed(self.XZTranslateOrHPPanTask,
|
||||
'manipulateCamera')
|
||||
|
||||
def spawnXZTranslate(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Spawn new task
|
||||
taskMgr.spawnMethodNamed(self.XZTranslateTask, 'manipulateCamera')
|
||||
|
||||
def spawnHPanYZoom(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Spawn new task
|
||||
t = Task.Task(self.HPanYZoomTask)
|
||||
t.zoomSF = Vec3(self.coa).length()
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def spawnHPPan(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Spawn new task
|
||||
taskMgr.spawnMethodNamed(self.HPPanTask, 'manipulateCamera')
|
||||
|
||||
def XZTranslateOrHPanYZoomTask(self, state):
|
||||
if direct.fShift:
|
||||
return self.XZTranslateTask(state)
|
||||
else:
|
||||
if (abs(deltaY) > 0.1):
|
||||
self.spawnHPanYZoom()
|
||||
else:
|
||||
self.spawnXZTranslate()
|
||||
return Task.done
|
||||
return self.HPanYZoomTask(state)
|
||||
|
||||
def XZTranslateOrHPPanTask(self, state):
|
||||
if direct.fShift:
|
||||
# Panning action
|
||||
return self.HPPanTask(state)
|
||||
else:
|
||||
# Translation action
|
||||
return self.XZTranslateTask(state)
|
||||
|
||||
def XZTranslateTask(self,state):
|
||||
coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
|
||||
xlateSF = (coaDist / direct.dr.near)
|
||||
direct.camera.setPos(direct.camera,
|
||||
(-0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.nearWidth *
|
||||
xlateSF),
|
||||
0.0,
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.nearHeight *
|
||||
xlateSF))
|
||||
return Task.cont
|
||||
|
||||
def HPanYZoomTask(self,state):
|
||||
if direct.fControl:
|
||||
moveDir = Vec3(Y_AXIS)
|
||||
else:
|
||||
moveDir = Vec3(self.coaMarker.getPos(direct.camera))
|
||||
# If marker is behind camera invert vector
|
||||
if moveDir[1] < 0.0:
|
||||
moveDir.assign(moveDir * -1)
|
||||
moveDir.normalize()
|
||||
moveDir.assign(moveDir * (-2.0 * direct.dr.mouseDeltaY *
|
||||
state.zoomSF))
|
||||
direct.camera.setPosHpr(direct.camera,
|
||||
moveDir[0],
|
||||
moveDir[1],
|
||||
moveDir[2],
|
||||
(0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.fovH),
|
||||
0.0, 0.0)
|
||||
return Task.cont
|
||||
|
||||
def HPPanTask(self, state):
|
||||
direct.camera.setHpr(direct.camera,
|
||||
(0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.fovH),
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.fovV),
|
||||
0.0)
|
||||
return Task.cont
|
||||
|
||||
def spawnMouseRotateTask(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Set at markers position in render coordinates
|
||||
self.camManipRef.setPos(self.coaMarkerPos)
|
||||
self.camManipRef.setHpr(direct.camera, ZERO_POINT)
|
||||
t = Task.Task(self.mouseRotateTask)
|
||||
if abs(direct.dr.mouseX) > 0.9:
|
||||
t.constrainedDir = 'y'
|
||||
else:
|
||||
t.constrainedDir = 'x'
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def mouseRotateTask(self, state):
|
||||
# If moving within frame, ignore motion perpendicular to edge
|
||||
if ((state.constrainedDir == 'y') & (abs(direct.dr.mouseX) > 0.9)):
|
||||
deltaX = 0
|
||||
deltaY = direct.dr.mouseDeltaY
|
||||
elif ((state.constrainedDir == 'x') & (abs(direct.dr.mouseY) > 0.9)):
|
||||
deltaX = direct.dr.mouseDeltaX
|
||||
deltaY = 0
|
||||
else:
|
||||
deltaX = direct.dr.mouseDeltaX
|
||||
deltaY = direct.dr.mouseDeltaY
|
||||
if direct.fShift:
|
||||
direct.camera.setHpr(direct.camera,
|
||||
(deltaX * direct.dr.fovH),
|
||||
(-deltaY * direct.dr.fovV),
|
||||
0.0)
|
||||
self.camManipRef.setPos(self.coaMarkerPos)
|
||||
self.camManipRef.setHpr(direct.camera, ZERO_POINT)
|
||||
else:
|
||||
wrtMat = direct.camera.getMat( self.camManipRef )
|
||||
self.camManipRef.setHpr(self.camManipRef,
|
||||
(-1 * deltaX * 180.0),
|
||||
(deltaY * 180.0),
|
||||
0.0)
|
||||
direct.camera.setMat(self.camManipRef, wrtMat)
|
||||
return Task.cont
|
||||
|
||||
def spawnMouseRollTask(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Set at markers position in render coordinates
|
||||
self.camManipRef.setPos(self.coaMarkerPos)
|
||||
self.camManipRef.setHpr(direct.camera, ZERO_POINT)
|
||||
t = Task.Task(self.mouseRollTask)
|
||||
t.coaCenter = getScreenXY(self.coaMarker)
|
||||
t.lastAngle = getCrankAngle(t.coaCenter)
|
||||
t.wrtMat = direct.camera.getMat( self.camManipRef )
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def mouseRollTask(self, state):
|
||||
wrtMat = state.wrtMat
|
||||
angle = getCrankAngle(state.coaCenter)
|
||||
deltaAngle = angle - state.lastAngle
|
||||
state.lastAngle = angle
|
||||
self.camManipRef.setHpr(self.camManipRef, 0, 0, -deltaAngle)
|
||||
direct.camera.setMat(self.camManipRef, wrtMat)
|
||||
return Task.cont
|
||||
|
||||
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()
|
||||
self.coaDist = Vec3(self.coa - ZERO_POINT).length()
|
||||
# Place the marker in render space
|
||||
self.coaMarker.setPos(direct.camera,self.coa)
|
||||
# Resize it
|
||||
@ -162,6 +299,10 @@ class DirectCameraControl(PandaObject):
|
||||
# Record marker pos in render space
|
||||
self.coaMarkerPos.assign(self.coaMarker.getPos())
|
||||
|
||||
def updateCoaMarkerSizeOnDeath(self, state):
|
||||
# Needed because tasks pass in state as first arg
|
||||
self.updateCoaMarkerSize()
|
||||
|
||||
def updateCoaMarkerSize(self, coaDist = None):
|
||||
if not coaDist:
|
||||
coaDist = Vec3(self.coaMarker.getPos( direct.camera )).length()
|
||||
@ -172,18 +313,59 @@ class DirectCameraControl(PandaObject):
|
||||
# Record undo point
|
||||
direct.pushUndo([direct.camera])
|
||||
direct.camera.setMat(Mat4.identMat())
|
||||
# Resize coa marker
|
||||
self.updateCoaMarkerSize()
|
||||
|
||||
def uprightCam(self):
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
currH = direct.camera.getH()
|
||||
# Record undo point
|
||||
direct.pushUndo([direct.camera])
|
||||
# Pitch camera till upright
|
||||
currH = direct.camera.getH()
|
||||
direct.camera.lerpHpr(currH, 0, 0,
|
||||
CAM_MOVE_DURATION,
|
||||
other = render,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
|
||||
def orbitUprightCam(self):
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Record undo point
|
||||
direct.pushUndo([direct.camera])
|
||||
# Transform camera z axis to render space
|
||||
mCam2Render = camera.getMat(render)
|
||||
zAxis = Vec3(mCam2Render.xformVec(Z_AXIS))
|
||||
zAxis.normalize()
|
||||
# Compute rotation angle needed to upright cam
|
||||
orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(Z_AXIS),-1,1)))
|
||||
# Check angle
|
||||
if orbitAngle < 0.1:
|
||||
# Already upright
|
||||
return
|
||||
# Compute orthogonal axis of rotation
|
||||
rotAxis = Vec3(zAxis.cross(Z_AXIS))
|
||||
rotAxis.normalize()
|
||||
# Find angle between rot Axis and render X_AXIS
|
||||
rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(X_AXIS),-1,1)))
|
||||
# Determine sign or rotation angle
|
||||
if rotAxis[1] < 0:
|
||||
rotAngle *= -1
|
||||
# Position ref CS at coa marker with xaxis aligned with rot axis
|
||||
self.camManipRef.setPos(self.coaMarker, Vec3(0))
|
||||
self.camManipRef.setHpr(render, rotAngle, 0, 0)
|
||||
# Reparent Cam to ref Coordinate system
|
||||
parent = direct.camera.getParent()
|
||||
direct.camera.wrtReparentTo(self.camManipRef)
|
||||
# Rotate ref CS to final orientation
|
||||
t = self.camManipRef.lerpHpr(rotAngle, orbitAngle, 0,
|
||||
CAM_MOVE_DURATION,
|
||||
other = render,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
# Upon death, reparent Cam to parent
|
||||
t.parent = parent
|
||||
t.uponDeath = self.reparentCam
|
||||
|
||||
def centerCam(self):
|
||||
self.centerCamIn(1.0)
|
||||
|
||||
@ -196,15 +378,16 @@ class DirectCameraControl(PandaObject):
|
||||
direct.pushUndo([direct.camera])
|
||||
# Determine marker location
|
||||
markerToCam = self.coaMarker.getPos( direct.camera )
|
||||
dist = Vec3(markerToCam - self.zeroPoint).length()
|
||||
scaledCenterVec = self.centerVec * dist
|
||||
dist = Vec3(markerToCam - ZERO_POINT).length()
|
||||
scaledCenterVec = Y_AXIS * dist
|
||||
delta = markerToCam - scaledCenterVec
|
||||
self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
|
||||
direct.camera.lerpPos(Point3(delta),
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
t = direct.camera.lerpPos(Point3(delta),
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
t.uponDeath = self.updateCoaMarkerSizeOnDeath
|
||||
|
||||
def zoomCam(self, zoomFactor, t):
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
@ -216,13 +399,14 @@ class DirectCameraControl(PandaObject):
|
||||
# Put a target nodePath there
|
||||
self.camManipRef.setPos(direct.camera, zoomPtToCam)
|
||||
# Move to that point
|
||||
direct.camera.lerpPos(self.zeroPoint,
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
t = direct.camera.lerpPos(ZERO_POINT,
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
t.uponDeath = self.updateCoaMarkerSizeOnDeath
|
||||
|
||||
def SpawnMoveToView(self, view):
|
||||
def spawnMoveToView(self, view):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Record undo point
|
||||
@ -252,25 +436,26 @@ class DirectCameraControl(PandaObject):
|
||||
elif view == 7:
|
||||
hprOffset.set(135., -35.264, 0.)
|
||||
# Position target
|
||||
self.camManipRef.setPosHpr(self.coaMarker, self.zeroBaseVec,
|
||||
self.camManipRef.setPosHpr(self.coaMarker, ZERO_VEC,
|
||||
hprOffset)
|
||||
# Scale center vec by current distance to target
|
||||
offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) -
|
||||
self.zeroPoint).length()
|
||||
scaledCenterVec = self.centerVec * (-1.0 * offsetDistance)
|
||||
ZERO_POINT).length()
|
||||
scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
|
||||
# Now put the camManipRef at that point
|
||||
self.camManipRef.setPosHpr(self.camManipRef,
|
||||
scaledCenterVec,
|
||||
self.zeroBaseVec)
|
||||
ZERO_VEC)
|
||||
# Record view for next time around
|
||||
self.lastView = view
|
||||
direct.camera.lerpPosHpr(self.zeroPoint,
|
||||
VBase3(0,0,self.orthoViewRoll),
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
|
||||
t = direct.camera.lerpPosHpr(ZERO_POINT,
|
||||
VBase3(0,0,self.orthoViewRoll),
|
||||
CAM_MOVE_DURATION,
|
||||
other = self.camManipRef,
|
||||
blendType = 'easeInOut',
|
||||
task = 'manipulateCamera')
|
||||
t.uponDeath = self.updateCoaMarkerSizeOnDeath
|
||||
|
||||
|
||||
def swingCamAboutWidget(self, degrees, t):
|
||||
# Remove existing camera manipulation task
|
||||
@ -280,9 +465,9 @@ class DirectCameraControl(PandaObject):
|
||||
direct.pushUndo([direct.camera])
|
||||
|
||||
# Coincident with widget
|
||||
self.camManipRef.setPos(self.coaMarker, self.zeroPoint)
|
||||
self.camManipRef.setPos(self.coaMarker, ZERO_POINT)
|
||||
# But aligned with render space
|
||||
self.camManipRef.setHpr(self.zeroPoint)
|
||||
self.camManipRef.setHpr(ZERO_POINT)
|
||||
|
||||
parent = direct.camera.getParent()
|
||||
direct.camera.wrtReparentTo(self.camManipRef)
|
||||
@ -297,115 +482,7 @@ class DirectCameraControl(PandaObject):
|
||||
|
||||
def reparentCam(self, state):
|
||||
direct.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
|
||||
# Can bring object to you by dragging across half the screen
|
||||
distToMove = targetVector * (2.0 * direct.dr.mouseDeltaY)
|
||||
direct.camera.setPosHpr(direct.camera,
|
||||
distToMove[0],
|
||||
distToMove[1],
|
||||
distToMove[2],
|
||||
(0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.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 / direct.dr.near)
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def XZTranslateOrHPPanTask(self, state):
|
||||
if direct.fShift:
|
||||
direct.camera.setHpr(direct.camera,
|
||||
(0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.fovH),
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.fovV),
|
||||
0.0)
|
||||
else:
|
||||
direct.camera.setPos(direct.camera,
|
||||
(-0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.nearWidth *
|
||||
state.scaleFactor),
|
||||
0.0,
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.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 / direct.dr.near)
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def XZTranslateTask(self,state):
|
||||
direct.camera.setPos(direct.camera,
|
||||
(-0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.nearWidth *
|
||||
state.scaleFactor),
|
||||
0.0,
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.nearHeight *
|
||||
state.scaleFactor))
|
||||
return Task.cont
|
||||
|
||||
def spawnMouseRotateTask(self):
|
||||
# Kill any existing tasks
|
||||
taskMgr.removeTasksNamed('manipulateCamera')
|
||||
# Set at markers position in render coordinates
|
||||
self.camManipRef.setPos(self.coaMarkerPos)
|
||||
self.camManipRef.setHpr(direct.camera, self.zeroPoint)
|
||||
t = Task.Task(self.mouseRotateTask)
|
||||
t.wrtMat = direct.camera.getMat( self.camManipRef )
|
||||
taskMgr.spawnTaskNamed(t, 'manipulateCamera')
|
||||
|
||||
def mouseRotateTask(self, state):
|
||||
wrtMat = state.wrtMat
|
||||
self.camManipRef.setHpr(self.camManipRef,
|
||||
(-0.5 * direct.dr.mouseDeltaX * 180.0),
|
||||
(0.5 * direct.dr.mouseDeltaY * 180.0),
|
||||
0.0)
|
||||
direct.camera.setMat(self.camManipRef, 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):
|
||||
direct.camera.setHpr(direct.camera,
|
||||
(0.5 * direct.dr.mouseDeltaX *
|
||||
direct.dr.fovH),
|
||||
(-0.5 * direct.dr.mouseDeltaY *
|
||||
direct.dr.fovV),
|
||||
0.0)
|
||||
return Task.cont
|
||||
self.updateCoaMarkerSize()
|
||||
|
||||
def fitOnWidget(self):
|
||||
# Fit the node on the screen
|
||||
@ -444,6 +521,39 @@ class DirectCameraControl(PandaObject):
|
||||
fitTask.parent = parent
|
||||
fitTask.uponDeath = self.reparentCam
|
||||
|
||||
def moveToFit(self):
|
||||
# How bit is the active widget?
|
||||
widgetScale = direct.widget.scalingNode.getScale(render)
|
||||
maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
|
||||
# At what distance does the widget fill 50% of the screen?
|
||||
camY = ((2 * direct.dr.near * (1.5 * maxScale)) /
|
||||
min(direct.dr.nearWidth, direct.dr.nearHeight))
|
||||
# Find a point this distance along the Y axis
|
||||
# MRM: This needs to be generalized to support non uniform frusta
|
||||
centerVec = Y_AXIS * camY
|
||||
# Before moving, record the relationship between the selected nodes
|
||||
# and the widget, so that this can be maintained
|
||||
direct.selected.getWrtAll()
|
||||
# Push state onto undo stack
|
||||
direct.pushUndo(direct.selected)
|
||||
# Remove the task to keep the widget attached to the object
|
||||
taskMgr.removeTasksNamed('followSelectedNodePath')
|
||||
# Spawn a task to keep the selected objects with the widget
|
||||
taskMgr.spawnMethodNamed(self.stickToWidgetTask, 'stickToWidget')
|
||||
# Spawn a task to move the widget
|
||||
t = direct.widget.lerpPos(Point3(centerVec),
|
||||
CAM_MOVE_DURATION,
|
||||
other = direct.camera,
|
||||
blendType = 'easeInOut',
|
||||
task = 'moveToFitTask')
|
||||
t.uponDeath = lambda state: taskMgr.removeTasksNamed('stickToWidget')
|
||||
|
||||
def stickToWidgetTask(self, state):
|
||||
# Move the objects with the widget
|
||||
direct.selected.moveWrtWidgetAll()
|
||||
# Continue
|
||||
return Task.cont
|
||||
|
||||
def enableMouseFly(self):
|
||||
# disable C++ fly interface
|
||||
base.disableMouse()
|
||||
|
@ -86,6 +86,45 @@ def ROUND_TO(value, divisor):
|
||||
return round(value/float(divisor)) * divisor
|
||||
def ROUND_INT(val):
|
||||
return int(round(val))
|
||||
def CLAMP(val, min, max):
|
||||
if val < min:
|
||||
return min
|
||||
elif val > max:
|
||||
return max
|
||||
else:
|
||||
return val
|
||||
|
||||
def getNearProjectionPoint(nodePath):
|
||||
# Find the position of the projection of the specified node path
|
||||
# on the near plane
|
||||
origin = nodePath.getPos(direct.camera)
|
||||
# project this onto near plane
|
||||
if origin[1] != 0.0:
|
||||
return origin * (direct.dr.near / origin[1])
|
||||
else:
|
||||
# Object is coplaner with camera, just return something reasonable
|
||||
return Point3(0, direct.dr.near, 0)
|
||||
|
||||
def getScreenXY(nodePath):
|
||||
# Where does the node path's projection fall on the near plane
|
||||
nearVec = getNearProjectionPoint(nodePath)
|
||||
# Clamp these coordinates to visible screen
|
||||
nearX = CLAMP(nearVec[0], direct.dr.left, direct.dr.right)
|
||||
nearY = CLAMP(nearVec[2], direct.dr.bottom, direct.dr.top)
|
||||
# What percentage of the distance across the screen is this?
|
||||
percentX = (nearX - direct.dr.left)/direct.dr.nearWidth
|
||||
percentY = (nearY - direct.dr.bottom)/direct.dr.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 getCrankAngle(center):
|
||||
# Used to compute current angle of mouse (relative to the coa's
|
||||
# origin) in screen space
|
||||
x = direct.dr.mouseX - center[0]
|
||||
y = direct.dr.mouseY - center[2]
|
||||
return (180 + rad2Deg(math.atan2(y,x)))
|
||||
|
||||
# Set direct drawing style for an object
|
||||
# Never light object or draw in wireframe
|
||||
|
@ -28,6 +28,7 @@ class DirectManipulationControl(PandaObject):
|
||||
self.actionEvents = [
|
||||
['handleMouse1', self.manipulationStart],
|
||||
['handleMouse1Up', self.manipulationStop],
|
||||
['space', self.toggleObjectHandlesMode],
|
||||
['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
|
||||
['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
|
||||
[',', self.objectHandles.multiplyScalingFactorBy, 0.5],
|
||||
@ -137,8 +138,6 @@ class DirectManipulationControl(PandaObject):
|
||||
# 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()
|
||||
@ -188,6 +187,13 @@ class DirectManipulationControl(PandaObject):
|
||||
if item in self.unpickable:
|
||||
self.unpickable.remove(item)
|
||||
|
||||
def toggleObjectHandlesMode(self):
|
||||
self.fSetCoa = 1 - self.fSetCoa
|
||||
if self.fSetCoa:
|
||||
self.objectHandles.coaModeColor()
|
||||
else:
|
||||
self.objectHandles.manipModeColor()
|
||||
|
||||
def removeManipulateObjectTask(self):
|
||||
taskMgr.removeTasksNamed('manipulateObject')
|
||||
|
||||
@ -206,20 +212,10 @@ class DirectManipulationControl(PandaObject):
|
||||
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()
|
||||
@ -350,13 +346,6 @@ class DirectManipulationControl(PandaObject):
|
||||
y + self.initY * dr.mouseDeltaY,
|
||||
z)
|
||||
|
||||
def getCrankAngle(self):
|
||||
# Used to compute current angle of mouse (relative to the widget's
|
||||
# origin) in screen space
|
||||
x = direct.dr.mouseX - self.rotationCenter[0]
|
||||
y = direct.dr.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
|
||||
@ -388,27 +377,6 @@ class DirectManipulationControl(PandaObject):
|
||||
# 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(direct.camera)
|
||||
# project this onto near plane
|
||||
return widgetOrigin * (direct.dr.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], direct.dr.left, direct.dr.right)
|
||||
nearY = self.clamp(nearVec[2], direct.dr.bottom, direct.dr.top)
|
||||
# What percentage of the distance across the screen is this?
|
||||
percentX = (nearX - direct.dr.left)/direct.dr.nearWidth
|
||||
percentY = (nearY - direct.dr.bottom)/direct.dr.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
|
||||
@ -421,11 +389,11 @@ class DirectManipulationControl(PandaObject):
|
||||
self.fHitInit = 0
|
||||
self.rotateAxis = self.constraint[:1]
|
||||
self.fWidgetTop = self.widgetCheck('top?')
|
||||
self.rotationCenter = self.getScreenXY()
|
||||
self.lastCrankAngle = self.getCrankAngle()
|
||||
self.rotationCenter = getScreenXY(direct.widget)
|
||||
self.lastCrankAngle = getCrankAngle(self.rotationCenter)
|
||||
|
||||
# Rotate widget based on how far cursor has swung around origin
|
||||
newAngle = self.getCrankAngle()
|
||||
newAngle = getCrankAngle(self.rotationCenter)
|
||||
deltaAngle = self.lastCrankAngle - newAngle
|
||||
if self.fWidgetTop:
|
||||
deltaAngle = -1 * deltaAngle
|
||||
@ -491,14 +459,6 @@ class DirectManipulationControl(PandaObject):
|
||||
)
|
||||
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):
|
||||
|
@ -82,7 +82,8 @@ class DirectSession(PandaObject):
|
||||
'escape', 'space', 'delete',
|
||||
'shift', 'shift-up', 'alt', 'alt-up',
|
||||
'control', 'control-up',
|
||||
'page_up', 'page_down',
|
||||
'page_up', 'page_down', 'tab',
|
||||
'[', '{', ']', '}',
|
||||
'b', 'c', 'f', 'l', 's', 't', 'v', 'w']
|
||||
self.mouseEvents = ['mouse1', 'mouse1-up',
|
||||
'mouse2', 'mouse2-up',
|
||||
@ -190,21 +191,27 @@ class DirectSession(PandaObject):
|
||||
self.downAncestry()
|
||||
elif input == 'escape':
|
||||
self.deselectAll()
|
||||
elif input == 'delete':
|
||||
self.removeAllSelected()
|
||||
elif input == 'tab':
|
||||
self.toggleWidgetVis()
|
||||
elif input == 'b':
|
||||
base.toggleBackface()
|
||||
elif input == 'l':
|
||||
self.lights.toggle()
|
||||
elif input == 's':
|
||||
if self.selected.last:
|
||||
self.select(self.selected.last)
|
||||
elif input == 'delete':
|
||||
self.removeAllSelected()
|
||||
elif input == 'v':
|
||||
self.selected.toggleVisAll()
|
||||
elif input == 'b':
|
||||
base.toggleBackface()
|
||||
elif input == 't':
|
||||
base.toggleTexture()
|
||||
elif input == 'v':
|
||||
self.selected.toggleVisAll()
|
||||
elif input == 'w':
|
||||
base.toggleWireframe()
|
||||
elif (input == '[') | (input == '{'):
|
||||
self.undo()
|
||||
elif (input == ']') | (input == '}'):
|
||||
self.redo()
|
||||
|
||||
def select(self, nodePath, fMultiselect = 0, fResetAncestry = 1):
|
||||
dnp = self.selected.select(nodePath, fMultiselect)
|
||||
@ -219,7 +226,7 @@ class DirectSession(PandaObject):
|
||||
self.readout.reparentTo(render2d)
|
||||
self.readout.setText(dnp.name)
|
||||
# Show the manipulation widget
|
||||
self.widget.reparentTo(direct.group)
|
||||
self.reparentWidgetTo('direct')
|
||||
# Update camera controls coa to this point
|
||||
# Coa2Camera = Coa2Dnp * Dnp2Camera
|
||||
mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(self.camera)
|
||||
@ -250,7 +257,7 @@ class DirectSession(PandaObject):
|
||||
dnp = self.selected.deselect(nodePath)
|
||||
if dnp:
|
||||
# Hide the manipulation widget
|
||||
self.widget.reparentTo(hidden)
|
||||
self.reparentWidgetTo('hidden')
|
||||
self.readout.reparentTo(hidden)
|
||||
self.readout.setText(' ')
|
||||
taskMgr.removeTasksNamed('followSelectedNodePath')
|
||||
@ -261,7 +268,7 @@ class DirectSession(PandaObject):
|
||||
def deselectAll(self):
|
||||
self.selected.deselectAll()
|
||||
# Hide the manipulation widget
|
||||
self.widget.reparentTo(hidden)
|
||||
self.reparentWidgetTo('hidden')
|
||||
self.readout.reparentTo(hidden)
|
||||
self.readout.setText(' ')
|
||||
taskMgr.removeTasksNamed('followSelectedNodePath')
|
||||
@ -384,7 +391,7 @@ class DirectSession(PandaObject):
|
||||
# Now record group
|
||||
self.undoList.append(undoGroup)
|
||||
# Truncate list
|
||||
self.undoList = self.undoList[-5:]
|
||||
self.undoList = self.undoList[-25:]
|
||||
# Alert anyone who cares
|
||||
messenger.send('pushUndo')
|
||||
if fResetRedo & (nodePathList != []):
|
||||
@ -413,7 +420,7 @@ class DirectSession(PandaObject):
|
||||
# Now record redo group
|
||||
self.redoList.append(redoGroup)
|
||||
# Truncate list
|
||||
self.redoList = self.redoList[-5:]
|
||||
self.redoList = self.redoList[-25:]
|
||||
# Alert anyone who cares
|
||||
messenger.send('pushRedo')
|
||||
|
||||
@ -462,6 +469,20 @@ class DirectSession(PandaObject):
|
||||
def hideReadout(self):
|
||||
self.readout.reparentTo(hidden)
|
||||
|
||||
def reparentWidgetTo(self, parent):
|
||||
if parent == 'direct':
|
||||
self.widget.reparentTo(direct.group)
|
||||
self.widgetParent = 'direct'
|
||||
else:
|
||||
self.widget.reparentTo(hidden)
|
||||
self.widgetParent = 'hidden'
|
||||
|
||||
def toggleWidgetVis(self):
|
||||
if self.widgetParent == 'direct':
|
||||
self.reparentWidgetTo('hidden')
|
||||
else:
|
||||
self.reparentWidgetTo('direct')
|
||||
|
||||
class DisplayRegionList:
|
||||
def __init__(self):
|
||||
self.displayRegionList = []
|
||||
|
@ -113,12 +113,46 @@ class Messenger:
|
||||
"""
|
||||
self.dict.clear()
|
||||
|
||||
def listAllEvents(self):
|
||||
str = 'Messenger\n'
|
||||
str = str + '='*50 + '\n'
|
||||
keys = self.dict.keys()
|
||||
keys.sort()
|
||||
for event in keys:
|
||||
str = str + 'Event: ' + event + '\n'
|
||||
str = str + '='*50 + '\n'
|
||||
print str
|
||||
|
||||
def __repr__(self):
|
||||
"""__repr__(self)
|
||||
Print out the table in a readable format
|
||||
"""
|
||||
str = 'Messenger\n'
|
||||
str = str + '='*50 + '\n'
|
||||
keys = self.dict.keys()
|
||||
keys.sort()
|
||||
for event in keys:
|
||||
acceptorDict = self.dict[event]
|
||||
str = str + 'Event: ' + event + '\n'
|
||||
for object in acceptorDict.keys():
|
||||
method, extraArgs, persistent = acceptorDict[object]
|
||||
className = object.__class__.__name__
|
||||
methodName = method.__name__
|
||||
str = (str + '\t' +
|
||||
'Acceptor: ' + className + ' instance' + '\n\t' +
|
||||
'Method: ' + methodName + '\n\t' +
|
||||
'Extra Args: ' + `extraArgs` + '\n\t' +
|
||||
'Persistent: ' + `persistent` + '\n\n'
|
||||
)
|
||||
str = str + '='*50 + '\n'
|
||||
return str
|
||||
|
||||
def __reprehensible__(self):
|
||||
"""__repr__(self)
|
||||
Old way to print out the table in a readable format
|
||||
"""
|
||||
str = 'Messenger\n'
|
||||
str = str + '='*50 + '\n'
|
||||
for event in self.dict.keys():
|
||||
acceptorDict = self.dict[event]
|
||||
str = str + event + '\n'
|
||||
@ -129,3 +163,7 @@ class Messenger:
|
||||
return str
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user