Select-Drag-Drop

Hi. I am new to Panda. I wish to achieve the select-drag-drop action for my project involving Chemical Reactions. The User should be able to drag an atom around the screen and drop it where he/she wishes to.

Referring to the Chessboard tutorial that comes along with Panda, Ive written a piece of code (thats a basic modification of the tutorial), but it has obvious logical gaps that I cant find my way around. I would be grateful if someone can help me with this.

import direct.directbase.DirectStart
from pandac.PandaModules import CollisionTraverser,CollisionNode
from pandac.PandaModules import CollisionHandlerQueue,CollisionRay
from pandac.PandaModules import AmbientLight,DirectionalLight,LightAttrib
from pandac.PandaModules import TextNode
from pandac.PandaModules import Point3,Vec3,Vec4,BitMask32
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
import sys

#First we define some contants for the colors
BLACK = Vec4(0,0,0,1)
WHITE = Vec4(1,1,1,1)
HIGHLIGHT = Vec4(0,1,1,1)
PIECEBLACK = Vec4(.15, .15, .15, 1)

#This function, given a line (vector plus origin point) and a desired z value,
#will give us the point on the line where the desired z value is what we want.
#This is how we know where to position an object in 3D space based on a 2D mouse
#position. It also assumes that we are dragging in the XY plane.
#
#This is derived from the mathmatical of a plane, solved for a given point
def PointAtZ(z, point, vec):
  return point + vec * ((z-point.getZ()) / vec.getZ())

#A handy little function for getting the proper position for a point in space to place the atom at
def SquarePos(i):
  return Point3((i%8) - 3.5, int(i/8) - 3.5, 0)

font = loader.loadFont("cmss12")


class World(DirectObject):
  def __init__(self):
    #This code puts the standard title and instruction text on screen
    self.title = OnscreenText(text="Atom Drag Drop Test",
                              style=1, fg=(1,1,1,1), font = font,
                              pos=(0.8,-0.95), scale = .07)
    self.escapeEvent = OnscreenText( 
      text="ESC: Quit", font = font,
      style=1, fg=(1,1,1,1), pos=(-1.3, 0.95),
      align=TextNode.ALeft, scale = .05)
    self.mouse1Event = OnscreenText(
      text="Left-click and drag: Pick up and drag piece",
      style=1, fg=(1,1,1,1), pos=(-1.3, 0.90), font = font,
      align=TextNode.ALeft, scale = .05)

    self.accept('escape', sys.exit)              #Escape quits
    base.disableMouse()                          #Disble mouse camera control
    camera.setPosHpr(0, -13.75, 6, 0, -25, 0)    #Set the camera
    self.setupLights()                           #Setup default lighting

    
    #Since we are using collision detection to do picking, we set it up like
    #any other collision detection system with a traverser and a handler
    self.picker = CollisionTraverser()            #Make a traverser
    self.pq     = CollisionHandlerQueue()         #Make a handler
    
    #Make a collision node for our picker ray
    self.pickerNode = CollisionNode('mouseRay')
    
    #Attach that node to the camera since the ray will need to be positioned
    #relative to it
    self.pickerNP = camera.attachNewNode(self.pickerNode)
    
    #Everything to be picked will use bit 1. This way if we were doing other
    #collision we could seperate it
    self.pickerNode.setFromCollideMask(BitMask32.bit(1))
    self.pickerRay = CollisionRay()               #Make our ray
    self.pickerNode.addSolid(self.pickerRay)      #Add it to the collision node
    
    #Register the ray as something that can cause collisions
    self.picker.addCollider(self.pickerNP, self.pq)
    #self.picker.showCollisions(render)

    #Start the task that handles the picking
    self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
    self.accept("mouse1", self.grabPiece)       #left-click grabs a piece
    self.accept("mouse1-up", self.releasePiece) #releasing places it



 def mouseTask(self, task):
    #Check to see if we can access the mouse. We need it to do anything else
    if base.mouseWatcherNode.hasMouse():
      #get the mouse position
      mpos = base.mouseWatcherNode.getMouse()
      
      #Set the position of the ray based on the mouse position
      self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

      #If we are dragging something, set the position of the object
      #to be at the appropriate point over the plane of the board
      if self.dragging is not False:
        #Gets the point described by pickerRay.getOrigin(), which is relative to
        #camera, relative instead to render
        nearPoint = render.getRelativePoint(camera, self.pickerRay.getOrigin())
        #Same thing with the direction of the ray
        nearVec = render.getRelativeVector(camera, self.pickerRay.getDirection())
        self.pieces[self.dragging].obj.setPos(
          PointAtZ(.5, nearPoint, nearVec))

      #Do the actual collision pass 
      self.picker.traverse(self.object)
      if self.pq.getNumEntries() > 0:
        #if we have hit something, sort the hits so that the closest is first
        self.pq.sortEntries()
        i = int(self.pq.getEntry(0).getIntoNode())
        flag=TRUE 

    return Task.cont

  def grabPiece(self):
    #If the flag set is true,set it to dragging mode
    if (flag is not False):
      self.dragging = TRUE

  def releasePiece(self):
    #Letting go of a piece. 
    if self.dragging is not False:          #Make sure we really are dragging something
          self.pieces[self.dragging].obj.setPos(Pos(self.dragging))      

    #We are no longer dragging anything
    self.dragging = False

  def setupLights(self):    #This function sets up some default lighting
    lAttrib = LightAttrib.makeAllOff()
    ambientLight = AmbientLight( "ambientLight" )
    ambientLight.setColor( Vec4(.8, .8, .8, 1) )
    lAttrib = lAttrib.addLight( ambientLight )
    directionalLight = DirectionalLight( "directionalLight" )
    directionalLight.setDirection( Vec3( 0, 45, -45 ) )
    directionalLight.setColor( Vec4( 0.2, 0.2, 0.2, 1 ) )
    lAttrib = lAttrib.addLight( directionalLight )
    render.attachNewNode( directionalLight.upcastToPandaNode() ) 
    render.attachNewNode( ambientLight.upcastToPandaNode() ) 
    render.node().setAttrib( lAttrib )


#Class to load the meshes
class Object:
  def __init__(self, position):
    self.obj = loader.loadModel(self.model)
    self.obj.reparentTo(render)
    self.obj.setColor(color)
    
#Do the main initialization and start 3D rendering
w = World()
run()
   

Thanks

here there is a working simple sample that shows you how to perform easy drag and dropping:

#snatched time ago from chombee's code
#
import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.task import Task
import sys,random

# Collision mask worn by all draggable objects.
dragMask = BitMask32.bit(1)
# Collision mask worn by all droppable objects.
dropMask = BitMask32.bit(1)

highlight = VBase4(.3,.3,.3,1)

class objectMangerClass:
  def __init__( self ):
    self.objectIdCounter = 0
    self.objectDict = dict()

  def tag( self, objectNp, objectClass ):
    self.objectIdCounter += 1
    objectTag = str(self.objectIdCounter)
    objectNp.setTag( 'objectId', objectTag )
    self.objectDict[objectTag] = objectClass

  def get( self, objectTag ):
    if objectTag in self.objectDict:
      return self.objectDict[objectTag]
    return None
objectManger = objectMangerClass()

class dragDropObjectClass:
  def __init__( self, np ):
    self.model = np
    self.previousParent = None
    self.model.setCollideMask(dragMask)
    objectManger.tag( self.model, self )

  def onPress( self, mouseNp ):
    self.previousParent = self.model.getParent()
    self.model.wrtReparentTo( mouseNp )
    self.model.setCollideMask(BitMask32.allOff())

  def onRelease( self ):
    self.model.wrtReparentTo( self.previousParent )
    self.model.setCollideMask(dragMask)

  def onCombine( self, otherObj ):
    self.model.setPos( otherObj.model.getPos() )

class mouseCollisionClass:
  def __init__(self):
    base.accept("escape",sys.exit)
    base.accept('mouse1',self.onPress)
    base.accept('mouse1-up',self.onRelease)
    self.draggedObj = None
    self.setupCollision()

    taskMgr.add( self.mouseMoverTask, 'mouseMoverTask' )

  def setupCollision( self ):
    # Initialise the collision ray that is used to detect which draggable
    # object the mouse pointer is over.
    cn = CollisionNode('')
    cn.addSolid( CollisionRay(0,-100,0, 0,1,0) )
    cn.setFromCollideMask(dragMask)
    cn.setIntoCollideMask(BitMask32.allOff())
    self.cnp = aspect2d.attachNewNode(cn)
    self.ctrav = CollisionTraverser()
    self.queue = CollisionHandlerQueue()
    self.ctrav.addCollider(self.cnp,self.queue)
    self.cnp.show()

  def mouseMoverTask( self, task ):
    if base.mouseWatcherNode.hasMouse():
      mpos = base.mouseWatcherNode.getMouse()
      self.cnp.setPos(render2d,mpos[0],0,mpos[1])
    return task.cont

  def collisionCheck( self ):
    self.ctrav.traverse(aspect2d)
    self.queue.sortEntries()
    if self.queue.getNumEntries():
      np = self.queue.getEntry( self.queue.getNumEntries()-1 ).getIntoNodePath()
      objectId = np.getTag( 'objectId' )
      if objectId is None:
        objectId = np.findNetTag( 'objectId' )
      if objectId is not None:
        object = objectManger.get( objectId )
        return object
    return None

  def onPress( self ):
    obj = self.collisionCheck()
    if obj is not None:
      self.draggedObj = obj
      obj.onPress( self.cnp )

  def onRelease( self ):
    obj = self.collisionCheck()
    self.draggedObj.onRelease() # self.cnp )
    if obj is not None:
      self.draggedObj.onCombine( obj )

if __name__ == '__main__':
  cm = CardMaker('cm')
  left,right,bottom,top = 0,2,0,-2
  width = right - left
  height = top - bottom
  cm.setFrame(left,right,bottom,top)
  node = aspect2d.attachNewNode('')
  node.setPos(-1.2,0,0.9)
  cards = []

  for i in range(3):
    for j in range(3):
      card = node.attachNewNode(cm.generate())
      card.setScale(.2)
      card.setPos(i/2.0,0,-j/2.0)
      card.setColor(random.random(),random.random(),random.random())
      draggable = dragDropObjectClass(card)
      cards.append(card)

  mouseCollision = mouseCollisionClass()
  run()

hope it helps

Thanks a lot astelix…I ran the code in the Python Shell…Gave me the following error:

Traceback (most recent call last):
  File "<pyshell#0>", line 3, in <module>
    import direct.directbase.DirectStart
ImportError: No module named direct.directbase.DirectStart

Probably a very trivial error but since Im an absolute beginner please bear.

Try changing to the directory that contains the file, then use:

ppython filename.py

the ppython command invokes panda’s python interpreter, which I find works better with Panda code, probably just because it knows where to look for the library files.

edit:
WOO! 100 posts!

[quote=“The Ideator”]
Thanks a lot astelix…I ran the code in the Python Shell…Gave me the following error:
[cut]

wow never faced that error so I guess you got something wrong in your panda engine install
FWIK ppython is not actually used anymore so if you can’t run it with normal python call I doubt ppython will work but try and see however
did you managed to run successfully any official panda3d sample provided with the install package?

@astelix:
Works fine with the ppython command that Tutunkommon suggested. I can run all the tutorials with the install package, but when I run it in the Python 2.6.2 IDLE GUI, it gives me the error. Where do you run your codes?

well I’m on kubuntu linux and use a little editor that have an embedded console - the IDLE I honestly ain’t never used nor had the need to use it. If you’re on windows you may find useful komodo edit but anyhow you need to open a console to test your code out.
I recall time ago a discussion here above this topic - try to search the forum and find it cos I guess could be interesting for you.

Ppython works fine. The code executes, but I need help understanding it. Any suggestions where should I start? I then need to replace the cards with meshes modelled in Blender which need to be dragged and dropped. The meshes have been exported using Chicken. Need help with that too.

No replies. So here are specific queries regarding the code. Referring to the manual, I gleaned that a ray traverser is setup from the mouse pointer to the objects and collisions are checked for. Looking at the code I didnt get the following:

self.objectIdCounter = 0
    self.objectDict = dict()

What purpose do these serve?

def tag( self, objectNp, objectClass ):
    self.objectIdCounter += 1
    objectTag = str(self.objectIdCounter)
    objectNp.setTag( 'objectId', objectTag )
    self.objectDict[objectTag] = objectClass

Didnt get what this function does. What is NP? Its repeated later for MouseNP etc.

 def onPress( self, mouseNp ):
    self.previousParent = self.model.getParent()
    self.model.wrtReparentTo( mouseNp )
    self.model.setCollideMask(BitMask32.allOff())

??

And whats .cnp?

I don’t recognise the code astelix posted and it doesn’t look like something I would write. Maybe I’m forgetful. I you sure that code was from me astelix? It looks a bit like some code of mine that has been modified by someone else maybe.

The Ideator, perhaps checkout my PandaZUI code which had implemented a simple drag and drop protocol. Note that it works on the 2D overlay layer though, not in 3D. Some of the relevent files are:

github.com/seanh/PandaZUI/blob/m … i/znode.py
github.com/seanh/PandaZUI/blob/m … ox_demo.py
…and others, github is playing up so I can’t find them. Explore the PandaZUI project and if you have specific questions about the code I should be able to help you with them.