From 38c15b1bddb9bf81dfee544bb4a0b08c9de8d117 Mon Sep 17 00:00:00 2001 From: Mark Mine Date: Thu, 19 Oct 2000 04:59:41 +0000 Subject: [PATCH] *** empty log message *** --- direct/src/directscripts/python-mode.el | 11 +- .../directutil/DirectCameraManipulation.py | 307 ++++++++++++++++++ direct/src/directutil/DirectGeometry.py | 56 ++++ direct/src/directutil/DirectGrid.py | 7 + direct/src/directutil/DirectSelection.py | 31 ++ direct/src/directutil/DirectSession.py | 98 ++++-- direct/src/showbase/PythonUtil.py | 10 +- 7 files changed, 485 insertions(+), 35 deletions(-) create mode 100644 direct/src/directutil/DirectCameraManipulation.py create mode 100644 direct/src/directutil/DirectGeometry.py create mode 100644 direct/src/directutil/DirectGrid.py create mode 100644 direct/src/directutil/DirectSelection.py diff --git a/direct/src/directscripts/python-mode.el b/direct/src/directscripts/python-mode.el index 01f008372f..4763b59f67 100644 --- a/direct/src/directscripts/python-mode.el +++ b/direct/src/directscripts/python-mode.el @@ -554,7 +554,7 @@ Currently-active file is at the head of the list.") (define-key py-shell-map "\C-c-" 'py-up-exception) (define-key py-shell-map "\C-c=" 'py-down-exception) ;; VR STUDIO ENHANCEMENTS - (define-key py-shell-map "\C-d" 'delete-char) + (define-key py-shell-map "\C-d" 'comint-delchar-or-maybe-python-resume) (define-key py-shell-map "\C-c\C-r" 'python-resume) (define-key py-shell-map "\C-c\C-s" 'pyd-shell) ) @@ -3146,6 +3146,15 @@ These are Python temporary files awaiting execution." (add-hook 'kill-emacs-hook 'py-kill-emacs-hook) ;; VR STUDIO ENHANCEMENT +(defun comint-delchar-or-maybe-python-resume (arg) + "Delete ARG characters forward or send a python-resume to subprocess. + Sends a python-resume only if point is at the end of the buffer and there is no input." + (interactive "p") + (let ((proc (get-buffer-process (current-buffer)))) + (if (and (eobp) proc (= (point) (marker-position (process-mark proc)))) + (python-resume) + (delete-char arg)))) + ;; Function to try to resume panda mainloop (defun python-resume () (interactive) diff --git a/direct/src/directutil/DirectCameraManipulation.py b/direct/src/directutil/DirectCameraManipulation.py new file mode 100644 index 0000000000..79ae804654 --- /dev/null +++ b/direct/src/directutil/DirectCameraManipulation.py @@ -0,0 +1,307 @@ +from PandaObject import * +from DirectGrid import DirectGrid + +class DirectCameraManipulation(PandaObject): + def __init__(): + # Create the grid + self.grid = DirectGrid(render) + self.grid.hide() + self.hitPt = Point3(0) + self.iPoint = Point3(0) + self.centerOfRotation = render.attachNewNode( NamedNode() ) + self.centerOfRotation.node().setName( 'centerOfRotation' ) + self.centerOfRotation.setPosHpr(Vec3(0), Vec3(0)) + self.orthoViewRoll = 0.0 + self.lastView = 0 + self.targetNodePath = render.attachNewNode(NamedNode('targetNode')) + self.zeroBaseVec = VBase3(0) + self.zeroVector = Vec3(0) + self.centerVec = Vec3(0., 1., 0.) + self.zeroPoint = Point3(0) + + def centerCam(chan): + # Chan is a display region context + self.centerCamIn(chan, 1.0) + + def centerCamNow(chan): + self.centerCamIn(chan, 0.) + + def centerCamIn(chan,t): + # Chan is a display region context + taskMgr.removeTasksNamed('manipulateCamera') + widgetToCam = direct.widget.getPos( chan.camera ) + dist = Vec3(widgetToCam - zeroPoint).length() + scaledCenterVec = centerVec * dist. + delta = widgetToCam - scaledCenterVec. + relNodePath = render.attachNewNode(Node()) + relNodePath.setPosHpr(chan.camera, Point3(0), Point3(0)) + ### + [ chan camera setPos: relNodePath pos: delta t: t. + ] spawnTaskNamed: 'manipulateCamera' + uponDeath: [ relNodePath removeNode. ] + ### + + def homeCam(chan): + chan.camera.setMat(Mat4.identMat()) + + + def mouseFlyStart(chan): + # Record starting mouse positions + initMouseX = chan.mouseX + initMouseY = chan.mouseY + + # Where are we in the channel? + if ((initMouseX abs < 0.9) & (initMouseY abs < 0.9)): + # Mouse is in central region + # spawn task to determine mouse fly mode" + self.determineMouseFlyMode() + else: + #Mouse is in outer frame, spawn mouseRotateTask + self.spawnMouseRotateTask() + + def mouseFlyStop(): + taskMgr.removeTasksNamed('determineMouseFlyMode') + taskMgr.removeTasksNamed('manipulateCamera') + + def removeManipulateCameraTask(): + taskMgr.removeTasksNamed('manipulateCamera') + + def enableMouseFly(): + self.enableMouseInteraction() + self.enableHotKeys() + + def enableMouseInteraction(): + # disable C++ fly interface + base.disableMouse() + # Accept middle mouse events + self.accept('mouse2', self.mouseFlyStart, [chanCenter]) + self.accept('mouse2-up' self.mouseFlyStop) + + def disableMouseFly(): + # Accept middle mouse events + self.ignore('mouse2') + self.ignore:'mouse2-up') + self.ignore('u') + self.ignore('c') + self.ignore('h') + for i in range(0,10): + self.ignore(`i`) + self.ignore('=') + self.ignore('+') + self.ignore('-') + self.ignore('=') + + def enableHotKeys(): + self.accept('u', self.uprightCam, [chanCenter]) + self.accept('c', self.centerCamIn, [chanCenter, 0.5]) + self.accept('h', self.homeCam, [chanCenter]) + for i in range(1,8): + self.accept(`i`, self.moveToView, [chanCenter, i]) + self.accept('9', self.swingCamAboutWidget, [chanCenter, -90.0, 1.0]) + self.accept('0', self.swingCamAboutWidget, [chanCenter, 90.0, 1.0]) + self.accept('8', self.removeManipulateCameraTask) + self.accept('=', self.zoomCam, [chanCenter, 0.5, 1.0]) + self.accept('+', self.zoomCam, [chanCenter, 0.5, 1.0]) + self.accept('-', self.zoomCam, [chanCenter, -2.0, 1.0]) + self.accept('=', self.zoomCam, [chanCenter, -2.0, 1.0]) + + def SpawnMoveToView(chan, view): + # Kill any existing tasks + taskMgr.removeTasksNamed('manipulateCamera') + # Calc hprOffset + hprOffset = VBase3() + 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 + targetNodePath.setPosHpr(direct.widget, + self.zeroBaseVec, + hprOffset) + # Scale center vec by current distance to target + offsetDistance = Vec3(chan.camera.getPos(targetNodePath) - \ + zeroPoint).length() + scaledCenterVec = centerVec * (-1.0 * offsetDistance). + + # Now put the targetNodePath at that point + targetNodePath.setPosHpr(targetNodePath, + scaledCenterVec, + zeroBaseVec) + + # Start off with best view if change is to new view + if (view != lastView): + orthoViewRoll = 0.0 + lastView = view. + + [ chan camera setPosHpr: targetNodePath pos: zeroBaseVec + hpr: (VBase3 new: 0.0 y: 0.0 z: orthoViewRoll) + t: 1.0. + "Try another roll next time" + orthoViewRoll = (orthoViewRoll + 90.0) rem: 360.0.] + spawnTaskNamed: #manipulateCamera. + + + def determineMouseFlyMode(): + # Get mouse intersection point + # TBS + + # Find this point to camera space + gridToCamera = grid getMat: chanCenter camera. + hitPt operatorAssign: (gridToCamera xformPoint: iPoint). + "Make sure hitPt is in front of the camera" + hitPt setY: (hitPt at: 1) abs. + + "Handle case of bad hit point (too close or too far)" + hitDistance = (hitPt - zeroPoint) length. + ((hitDistance < (1.1 * chanCenter near)) | (hitDistance > chanCenter far)) ifTrue: [ + "Just use grid origin" + "hitPt operatorAssign: centerVec * (0.5 * (chanCenter far + chanCenter near))" + hitPt operatorAssign: (grid getPos: chanCenter camera). + ]. + + (direct fShift) ifTrue: [ self.spawnHPPan. ] + ifFalse: [ + [[deltaX = chanCenter mouseX - initMouseX. + deltaY = chanCenter mouseY - initMouseY. + ((deltaX abs < 0.1) & (deltaY abs < 0.1))] taskWhileTrue: [ nil ] + ] spawnTaskNamed: #determineMouseFlyMode + uponDeath: [ + (deltaY abs > 0.1) ifTrue: [ + self.spawnHPanYZoom] + ifFalse: [ + self.spawnXZTranslate ]. + ]. + ]. +! ! + + + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +spawnHPPan + [[true] taskWhileTrue: + [ chanCenter camera setHpr: chanCenter camera + h: (0.5 * chanCenter mouseDeltaX * chanCenter fovH) + p: (-0.5 * chanCenter mouseDeltaY * chanCenter fovV) + r: 0.0 ]] + spawnTaskNamed: #manipulateCamera. +! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +spawnHPanYZoom + | targetVector distToMove | + targetVector = Vec3 new: (hitPt at: 0) y: (hitPt at: 1) z: (hitPt at: 2). + [[true] taskWhileTrue: + [ distToMove = targetVector * (-1.0 * chanCenter mouseDeltaY). + chanCenter camera setPosHpr: chanCenter camera + x: (distToMove at: 0) + y: (distToMove at: 1) + z: (distToMove at: 2) + h: (0.5 * chanCenter mouseDeltaX * chanCenter fovH) + p: 0.0 r: 0.0. ]] + spawnTaskNamed: #manipulateCamera. +! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +spawnMouseRotateTask + | wrtMat | + centerOfRotation setPos: grid pos: iPoint. + centerOfRotation setHpr: chanCenter camera h: 0.0 p: 0.0 r: 0.0. + + wrtMat = chanCenter camera getMat: centerOfRotation. + + [[true] taskWhileTrue: + [ centerOfRotation setHpr: centerOfRotation + h: (-0.5 * chanCenter mouseDeltaX * 180.0) + p: (0.5 * chanCenter mouseDeltaY * 180.0) + r: 0.0. + chanCenter camera setMat: centerOfRotation mat: wrtMat. ]] + spawnTaskNamed: #manipulateCamera. +! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +spawnXZTranslate + | scaleFactor | + scaleFactor = ((hitPt at: 1) / chanCenter near). + [[true] taskWhileTrue: + [ chanCenter camera setPos: chanCenter camera + x: (-0.5 * chanCenter mouseDeltaX * chanCenter nearWidth * scaleFactor) + y: 0.0 + z: (-0.5 * chanCenter mouseDeltaY * chanCenter nearHeight * scaleFactor) ] ] + spawnTaskNamed: #manipulateCamera. +! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +spawnXZTranslateOrHPPan + | scaleFactor | + scaleFactor = ((hitPt at: 1) / chanCenter near). + [[true] taskWhileTrue: + [ direct fShift + ifTrue: [ chanCenter camera setHpr: chanCenter camera + h: (0.5 * chanCenter mouseDeltaX * chanCenter fovH) + p: (-0.5 * chanCenter mouseDeltaY * chanCenter fovV) + r: 0.0 ] + ifFalse: [ chanCenter camera setPos: chanCenter camera + x: (-0.5 * chanCenter mouseDeltaX * chanCenter nearWidth * scaleFactor) + y: 0.0 + z: (-0.5 * chanCenter mouseDeltaY * chanCenter nearHeight * scaleFactor)] + ]] + spawnTaskNamed: #manipulateCamera. +! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +swingCamAboutWidget: chan deg: degrees in: t + | relNodePath parent | + + Task removeTasksNamed: #manipulateCamera. + + relNodePath = showBase render attachNewNode: (Node new). + "Coincident with widget" + relNodePath setPos: direct widget pos: (Point3 zero). + "But aligned with render space" + relNodePath setHpr: (Point3 zero). + + parent = chanCenter camera getParent. + chanCenter camera wrtReparentTo: relNodePath. + + [ relNodePath setHpr: (VBase3 new: degrees y: 0.0 z: 0.0) t: t. ] + spawnTaskNamed: #manipulateCamera + uponDeath: [ + chanCenter camera wrtReparentTo: parent. + relNodePath reparentTo: showBase hidden. + relNodePath removeNode. + ].! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +uprightCam: chan + | currH | + Task removeTasksNamed: #manipulateCamera. + currH = chan camera getH. + [ chan camera setHpr: showBase render h: currH p: 0.0 r: 0.0 t: 1.0. + ] spawnTaskNamed: #manipulateCamera.! ! + +!DirectCameraManipulation methodsFor: 'event handling' stamp: 'panda 00/00/0000 00:00'! +zoomCam: chan zoom: zoom in: t + | relNodePath zoomPtToCam | + Task removeTasksNamed: #manipulateCamera. + "Find a point zoom factor times the current separation of the widget and cam" + zoomPtToCam = (direct widget getPos: chan camera) * zoom. + + "Put a target nodePath there" + relNodePath = showBase render attachNewNode: (Node new). + relNodePath setPos: chanCenter camera pos: zoomPtToCam. + + "Move to that point" + [ chan camera setPos: relNodePath pos: (Point3 zero) t: t. ] + spawnTaskNamed: #manipulateCamera + uponDeath: [ relNodePath removeNode. ].! ! + diff --git a/direct/src/directutil/DirectGeometry.py b/direct/src/directutil/DirectGeometry.py new file mode 100644 index 0000000000..5689a2fea4 --- /dev/null +++ b/direct/src/directutil/DirectGeometry.py @@ -0,0 +1,56 @@ +from PandaObject import * +import math + +class LineNodePath(NodePath): + def __init__(self, parent = hidden, **kw): + + # Initialize the superclass + NodePath.__init__(self) + + # Attach a geomNode to the parent and set self to be + # the resulting node path + self.lineNode = GeomNode() + self.assign(parent.attachNewNode( self.lineNode )) + + # Create a lineSegs object to hold the line + ls = self.lineSegs = LineSegs() + # Initialize the lineSegs parameters + ls.setThickness( kw.get('thickness', 1.0) ) + ls.setColor( kw.get('colorVec', VBase4(1.0)) ) + + def moveTo( self, *_args ): + apply( self.lineSegs.moveTo, _args ) + + def drawTo( self, *_args ): + apply( self.lineSegs.drawTo, _args ) + + def create( self, frameAccurate = 0 ): + self.lineSegs.create( self.lineNode, frameAccurate ) + + def reset( self ): + self.lineSegs.reset() + self.lineNode.clear() + + def isEmpty( self ): + return self.lineSegs.isEmpty() + + def setThickness( self, thickness ): + self.lineSegs.setThickness( thickness ) + + def setColor( self, *_args ): + apply( self.lineSegs.setColor, _args ) + + def setVertexColor( self, vertex, *_args ): + apply( self.lineSegs.setVertexColor, (vertex,) + _args ) + + def getCurrentPosition( self ): + return self.lineSegs.getCurrentPosition() + + def getNumVertices( self ): + return self.lineSegs.getNumVertices() + + def getVertex( self ): + return self.lineSegs.getVertex() + + def getVertexColor( self ): + return self.lineSegs.getVertexColor() diff --git a/direct/src/directutil/DirectGrid.py b/direct/src/directutil/DirectGrid.py new file mode 100644 index 0000000000..f32f050748 --- /dev/null +++ b/direct/src/directutil/DirectGrid.py @@ -0,0 +1,7 @@ +from PandaObject import * + +class DirectGrid(NodePath): + def __init__(self, parent = hidden): + NodePath.__init__(self) + self.assign(parent.attachNewNode( NamedNode('grid') )) + diff --git a/direct/src/directutil/DirectSelection.py b/direct/src/directutil/DirectSelection.py new file mode 100644 index 0000000000..4f69bab8aa --- /dev/null +++ b/direct/src/directutil/DirectSelection.py @@ -0,0 +1,31 @@ +dd = loader.loadModel(r"I:\beta\toons\install\neighborhoods\donalds_dock") +dd.reparentTo(render) + +cam = base.cam.node() +rNode = camera.attachNewNode( CollisionNode() ) +rNode.node().setCollideGeom(1) +ray = CollisionRay() +rNode.node().addSolid( ray ) +cq = CollisionHandlerQueue() +ct = CollisionTraverser( ) +# Optionally +# ct = CollisionTraverser( RenderRelation.getClassType() ) +ct.addCollider(rNode.node(), cq ) +# These are the mouse coordinates +ray.setProjection( cam, 0, 0 ) +ct.traverse( render.node() ) + +# Operations on cq +cq.getNumEntries() +cq.getEntry(0).getIntoNode() +cq.getEntry(0).getIntoNode().getName() +print cq.getEntry(i).getIntoIntersectionPoint() + +for i in range(0,cq.getNumEntries()): + name = cq.getEntry(i).getIntoNode().getName() + if not name: + name = "" + print name + print cq.getEntry(i).getIntoIntersectionPoint()[0], + print cq.getEntry(i).getIntoIntersectionPoint()[1], + print cq.getEntry(i).getIntoIntersectionPoint()[2] diff --git a/direct/src/directutil/DirectSession.py b/direct/src/directutil/DirectSession.py index 0e3130dcef..bf74c1f63b 100644 --- a/direct/src/directutil/DirectSession.py +++ b/direct/src/directutil/DirectSession.py @@ -1,4 +1,6 @@ from PandaObject import * +from DirectGeometry import * +import OnscreenText import math class DisplayRegionContext(PandaObject): @@ -51,7 +53,7 @@ class DisplayRegionContext(PandaObject): self.mouseDeltaY = self.mouseY - self.mouseLastY # Continue the task return Task.cont - + class DirectSession(PandaObject): def __init__(self): self.contextList = [] @@ -62,49 +64,79 @@ class DirectSession(PandaObject): self.selectedNodePaths = {} self.lastSelected = None + self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 ) + # self.readout.textNode.setCardColor(0.5, 0.5, 0.5, 0.5) + self.readout.reparentTo( hidden ) + + self.createBBox() + + # self.createObjectHandles() + # self.useObjectHandles() + self.bboxList = [] self.fControl = 0 self.fAlt = 0 self.fShift = 0 - """" - def createBBox(self): - bbox = hidden.attachNewNode(NamedNode()) - bbox.setName('bbox') - bboxLines = GridLine new: bbox. - bboxLines color: (VBase4 new: 1.0 y: 0.0 z: 0.0 w: 1.0). - bboxLines thickness: 0.5. + def createBBox(self, parent = hidden): + # Create a line segments object for the bbox + bbox = self.bbox = LineNodePath(parent) + #bbox.setName('bbox') + bbox.setColor( VBase4( 1., 0., 0., 1. ) ) + bbox.setThickness( 0.5 ) - "Bottom face" - bboxLines moveTo: 0.0 y: 0.0 z: 0.0. - bboxLines drawTo: 1.0 y: 0.0 z: 0.0. - bboxLines drawTo: 1.0 y: 1.0 z: 0.0. - bboxLines drawTo: 0.0 y: 1.0 z: 0.0. - bboxLines drawTo: 0.0 y: 0.0 z: 0.0. + # Bottom face + bbox.drawTo( 0.0, 0.0, 0.0 ) + bbox.drawTo( 1.0, 0.0, 0.0 ) + bbox.drawTo( 1.0, 1.0, 0.0 ) + bbox.drawTo( 0.0, 1.0, 0.0 ) + bbox.drawTo( 0.0, 0.0, 0.0 ) - "Front Edge/Top face" - bboxLines drawTo: 0.0 y: 0.0 z: 1.0. - bboxLines drawTo: 1.0 y: 0.0 z: 1.0. - bboxLines drawTo: 1.0 y: 1.0 z: 1.0. - bboxLines drawTo: 0.0 y: 1.0 z: 1.0. - bboxLines drawTo: 0.0 y: 0.0 z: 1.0. + # Front Edge/Top face + bbox.drawTo( 0.0, 0.0, 1.0 ) + bbox.drawTo( 1.0, 0.0, 1.0 ) + bbox.drawTo( 1.0, 1.0, 1.0 ) + bbox.drawTo( 0.0, 1.0, 1.0 ) + bbox.drawTo( 0.0, 0.0, 1.0 ) - "Three remaining edges" - bboxLines moveTo: 1.0 y: 0.0 z: 0.0. - bboxLines drawTo: 1.0 y: 0.0 z: 1.0. - bboxLines moveTo: 1.0 y: 1.0 z: 0.0. - bboxLines drawTo: 1.0 y: 1.0 z: 1.0. - bboxLines moveTo: 0.0 y: 1.0 z: 0.0. - bboxLines drawTo: 0.0 y: 1.0 z: 1.0. + # Three remaining edges + bbox.moveTo( Point3( 1.0, 0.0, 0.0 ) ) + bbox.drawTo( 1.0, 0.0, 1.0 ) + bbox.moveTo( Point3( 1.0, 1.0, 0.0 ) ) + bbox.drawTo( 1.0, 1.0, 1.0 ) + bbox.moveTo( Point3( 0.0, 1.0, 0.0 ) ) + bbox.drawTo( 0.0, 1.0, 1.0 ) - bboxLines create: bboxLines lineNode.! ! - """ - -class Line(NodePath, LineSegs): - def __init__(self): - LineSegs.__init__(self) + bbox.create() + def createObjectHandles(self): + oh = self.objectHandles = hidden.attachNewNode( NamedNode('objectHandles') ) + ohLines = LineNodePath( oh ) + ohLines.setColor( VBase4( 1.0, 0.0, 1.0, 1.0) ) + ohLines.setThickness( 3.0 ) + + "InnerRing" + ohLines.moveTo( 0.8, 0.0, 0.0 ) + for ang in range(10, 360, 10): + ohLines.drawTo( (0.8 * math.cos(deg2Rad(ang))), + (0.8 * math.sin(deg2Rad(ang))), + 0.0 ) + + "Outer Ring" + ohLines.moveTo( 1.2, 0.0, 0.0 ) + for ang in range(0, 360, 10): + ohLines.drawTo( (1.2 * math.cos(deg2Rad(ang))), + (1.2 * math.sin(deg2Rad(ang))), + 0.0 ) + + ohLines.moveTo( 0.0, 0.0, 0.0 ) + ohLines.drawTo( 0.0, 0.0, 1.5 ) + + ohLines.create() + + + diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 4b8db606d1..65e4870d64 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -25,7 +25,10 @@ def apropos(obj, str = None, fOverloaded = 0, width = None, fTruncate = 1, lineWidth = 75): if type(obj) == types.InstanceType: print "***************************INSTANCE INFO*****************************" - dict = obj.__dict__ + if type(obj) == types.DictionaryType: + dict = obj + else: + dict = obj.__dict__ if width: maxWidth = width else: @@ -81,3 +84,8 @@ def aproposAll(obj): apropos(obj, fOverloaded = 1, fTruncate = 0) +def doc(obj): + if (isinstance(obj, types.MethodType)) or \ + (isinstance(obj, types.FunctionType)): + print obj.__doc__ +