class NodeConnector: def __init__(self, socketA, socketB): self.connectorID = uuid4() self.socketA = socketA self.socketB = socketB self.line = LineNodePath(ShowBaseGlobal.aspect2d, thickness=2, colorVec=(0.8, 0.8, 0.8, 1)) self.draw() def update(self): self.line.reset() self.draw() def draw(self): self.line.moveTo(self.socketA.plug.getPos(ShowBaseGlobal.aspect2d)) self.line.drawTo(self.socketB.plug.getPos(ShowBaseGlobal.aspect2d)) self.line.create() def has(self, socket): """Returns True if one of the sockets this connector connects is the given socket""" return socket == self.socketA or socket == self.socketB def connects(self, a, b): """Returns True if this connector connects socket a and b""" return (a == self.socketA or a == self.socketB) and (b == self.socketA or b == self.socketB) def disconnect(self): self.line.reset() self.socketA.setConnected(False) self.socketB.setConnected(False)
def draw_lines(lines: LineNodePath, *paths: dict, origin=None, relative=True): if origin is None: origin = lines.getCurrentPosition() lines.reset() for path in paths: if 'color' in path: lines.setColor(*path['color']) points = path['points'] lines.moveTo(*origin) for point in points: if relative: point += origin lines.drawTo(*point) lines.create()
class Fisherman(Toon.Toon): numFishTypes = 3 def __init__(self, id, toNpcId=20001, fAutonomous=1): # Default NPC ID is Flippy Toon.Toon.__init__(self) self.id = id self.fAutonomous = fAutonomous npcInfo = NPCToons.NPCToonDict[toNpcId] dnaList = npcInfo[2] gender = npcInfo[3] dna = ToonDNA.ToonDNA() dna.newToonFromProperties(*dnaList) self.setDNA(dna) self.reparentTo(render) self.angleNP = self.find('**/actorGeom') # Create pole self.pole = Actor.Actor() self.pole.loadModel('phase_4/models/props/fishing-pole-mod') self.pole.loadAnims({'cast': 'phase_4/models/props/fishing-pole-chan'}) # Get the top of the pole. self.ptop = self.pole.find('**/joint_attachBill') self.pole.pose('cast', 0) # Prepare Pole self.poleNode = [] self.holdPole() self.createCastTrack() self.castIval = None # Prepare actor self.setupNeutralBlend() self.targetInterval = None # Start automatic casting or create cast button if self.fAutonomous: self.castButton = None self.targetButton = None self.startCasting() else: # Starts casting mode when mouse enters button region self.castButton = DirectButton(text='CAST', relief=None, scale=0.1, pos=(0, 0, -0.2)) self.castButton.bind(DGG.ENTER, self.showCancelFrame) # A big screen encompassing frame to catch the button releases self.cancelFrame = DirectFrame(parent=self.castButton, frameSize=(-1, 1, -1, 1), relief=None, state='normal') # Make sure this is on top of all the other widgets self.cancelFrame.setBin('gui-popup', 0) self.cancelFrame.bind(DGG.B1PRESS, self.startAdjustingCastTask) self.cancelFrame.bind(DGG.B1RELEASE, self.finishCast) self.cancelFrame.hide() # Create bob self.bob = loader.loadModel('phase_4/models/props/fishing_bob') self.bobSpot = Point3(0) # Parameters to control bob motion self.vZeroMax = 30.0 self.angleMax = 30.0 # Ripple effect self.ripples = Ripples.Ripples(self.angleNP) self.ripples.hide() # Target self.buttonFrame = DirectFrame() self.target = base.distributedFishingTarget.fishingTargetNode self.fishPivot = self.attachNewNode('fishPivot') self.fish = loader.loadModel('models/misc/smiley') self.fish.reparentTo(self.fishPivot) self.fish.setScale(0.3, 1, 0.3) self.wiggleIval = None self.circleIval = None self.initFish() self.targetButton = DirectButton(parent=self.buttonFrame, text='MOVE', relief=DGG.RAISED, scale=0.1, pos=(0, 0, -0.9), command=self.moveTarget) self.targetTypeButton = DirectCheckButton(parent=self.buttonFrame, text='MOVING', relief=DGG.RAISED, scale=0.085, pos=(0.4, 0, -0.895), command=self.setfMove) self.fMovingTarget = 0 self.targetModeButton = DirectCheckButton( parent=self.buttonFrame, text='dTASK', relief=DGG.RAISED, scale=0.085, pos=(0.8, 0, -0.895), command=self.setfTargetMode) self.fTargetMode = 0 # Vector line self.line = LineNodePath(render2d) self.line.setColor(VBase4(1, 0, 0, 1)) self.line.moveTo(0, 0, 0) self.line.drawTo(1, 0, 0) self.line.create() self.moveTarget() def showCancelFrame(self, event): # Also display cancel frame to catch clicks outside of the popup self.cancelFrame.show() def startAdjustingCastTask(self, event): # Start task to adjust power of cast self.getMouse() self.initMouseX = self.mouseX self.initMouseY = self.mouseY self.initMouseX = 0 self.initMouseY = -0.2 self.line.lineSegs.setVertex(0, self.initMouseX, 0, self.initMouseY) self.angleNP.setH(0) self.__hideBob() taskMgr.remove('distCheck') # Position and scale cancel frame to fill entire window self.cancelFrame.setPos(render2d, 0, 0, 0) self.cancelFrame.setScale(render2d, 1, 1, 1) self.castTrack.finish() self.castTrack.setT(0) self.castTrack.start(startT=0, endT=0.4) self.castIval = Sequence( Wait(0.4), Func(taskMgr.add, self.adjustingCastTask, 'adjustCastTask')) self.castIval.start() def adjustingCastTask(self, state): self.getMouse() deltaX = self.mouseX - self.initMouseX deltaY = self.mouseY - self.initMouseY self.line.lineSegs.setVertex(1, self.mouseX, 0, self.mouseY) dist = math.sqrt(deltaX * deltaX + deltaY * deltaY) delta = (dist / 0.5) if deltaY > 0: delta *= -1 p = max(min(delta, 1.0), 0.0) self.power = p self.castTrack.setT(0.4 + p * 0.5) self.bobSpot = Point3(0, 6.5 + p * 25.0, -1.9) # Calc angle if deltaY == 0: angle = 0 else: angle = rad2Deg(math.atan(deltaX / deltaY)) self.angleNP.setH(-angle) return Task.cont def stopAdjustingCastTask(self): taskMgr.remove('adjustingTask') def setupNeutralBlend(self): self.stop() self.loop('neutral') self.enableBlend() self.pose('cast', 0) self.setControlEffect('neutral', 0.2) self.setControlEffect('cast', 0.8) def startCasting(self): if self.fAutonomous: self.castIval = Sequence( ActorInterval(self, 'cast'), Func(self.catchFish), Parallel( ActorInterval(self, 'neutral', loop=1, duration=100), Sequence( Wait(random.random() * 20.0), Func(self.startCasting), ))) else: self.castIval = Sequence( ActorInterval(self, 'cast'), Func(self.catchFish), ActorInterval(self, 'neutral', loop=1, duration=100)) self.castIval.play() def stopCasting(self): if self.castIval: self.castIval.pause() if self.targetInterval: self.stopTargetInterval() taskMgr.remove('distCheck') def catchFish(self): fishNum = int(round(random.random() * self.numFishTypes)) messenger.send('caughtFish', sentArgs=[self.id, fishNum]) def setupNeutralBlend(self): self.stop() self.loop('neutral') self.enableBlend() self.pose('cast', 0) self.setControlEffect('neutral', 0.2) self.setControlEffect('cast', 0.8) def getPole(self): toonTrack = Sequence( # Blend in neutral anim Func(self.setupNeutralBlend), # Pull out pole Func(self.holdPole), Parallel( ActorInterval(self, 'cast', playRate=0.5, duration=27. / 12.), ActorInterval(self.pole, 'cast', playRate=0.5, duration=27. / 12.), LerpScaleInterval(self.pole, duration=2.0, scale=1.0, startScale=0.01)), ) toonTrack.play() def putAwayPole(self): Sequence( Parallel( ActorInterval(self, 'cast', duration=1.0, startTime=1.0, endTime=0.0), ActorInterval(self.pole, 'cast', duration=1.0, startTime=1.0, endTime=0.0), LerpScaleInterval(self.pole, duration=0.5, scale=0.01, startScale=1.0)), Func(self.dropPole)).start() def holdPole(self): if self.poleNode != []: self.dropPole() # One node, instanced to each of the toon's three right hands, # will hold the pole. np = NodePath('pole-holder') hands = self.getRightHands() for h in hands: self.poleNode.append(np.instanceTo(h)) self.pole.reparentTo(self.poleNode[0]) def dropPole(self): self.__hideBob() #self.__hideLine() if self.pole != None: self.pole.clearMat() self.pole.detachNode() for pn in self.poleNode: pn.removeNode() self.poleNode = [] def createCastTrack(self): self.castTrack = Sequence( Parallel( ActorInterval(self, 'cast', duration=2.0, startTime=1.0), ActorInterval(self.pole, 'cast', duration=2.0, startTime=1.0), )) def cast(self): self.castTrack.start() def finishCast(self, event=None): #self.line.lineSegs.setVertex(1,self.initMouseX,0,self.initMouseY) self.cancelFrame.hide() if not self.castTrack: self.createCastTrack() self.castIval.finish() taskMgr.remove('adjustCastTask') taskMgr.remove('moveBobTask') self.castTrack.pause() #self.castTrack.start(self.castTrack.getT()) p = self.power startT = 0.9 + (1 - p) * 0.3 self.castTrack.start(startT) self.bobStartPos = Point3(0.017568, 7.90371, 6.489) Sequence(Wait(1.2 - startT), Func(self.startMoveBobTask)).start() def startMoveBobTask(self): self.__showBob() self.bobStartT = globalClock.getFrameTime() taskMgr.add(self.moveBobTask, 'moveBobTask') def moveBobTask(self, state): # Accel due to gravity g = 32.2 # Elapsed time of cast t = globalClock.getFrameTime() - self.bobStartT # Scale bob velocity and angle based on power of cast vZero = self.power * self.vZeroMax angle = deg2Rad(self.power * self.angleMax) # How far has bob moved from start point? deltaY = vZero * math.cos(angle) * t deltaZ = vZero * math.sin(angle) * t - (g * t * t) / 2.0 deltaPos = Point3(0, deltaY, deltaZ) # Current bob position pos = self.bobStartPos + deltaPos # Have we reached end condition? if pos[2] < -1.9: pos.setZ(-1.9) self.ripples.reparentTo(self.angleNP) self.ripples.setPos(pos) self.ripples.play() returnVal = Task.done if self.fTargetMode: taskMgr.add(self.distCheck, 'distCheck') else: self.distCheck() else: returnVal = Task.cont self.bob.setPos(pos) return returnVal def distCheck(self, state=None): # Check to see if we hit the target bPos = self.bob.getPos() # Check target returnVal = self.__distCheck(bPos, self.target) if returnVal == Task.done: return returnVal # Check fish return self.__distCheck(bPos, self.fish) def __distCheck(self, bPos, target): def flashTarget(): self.stopTargetInterval() self.target.getChild(0).setColor(0, 0, 1) def flashFish(): taskMgr.remove('turnTask') self.fish.lerpScale(Point3(0.01), 0.5, task='flashFish') tPos = target.getPos(self.angleNP) tDist = Vec3(tPos - bPos) tDist.setZ(0) dist = tDist.length() if target == self.target: flashFunc = flashTarget moveFunc = self.moveTarget else: flashFunc = flashFish moveFunc = self.turnFish if dist < 2.5: fBite = (random.random() < 0.4) or (not self.fTargetMode) delay = self.fTargetMode * 0.25 if fBite: print('BITE') Sequence( Wait(random.random() * delay), Func(flashFunc), Func(self.catchFish), Wait(2.0), Func(moveFunc), ).play() else: print('MISS') def moveIt(): moveFunc(targetPos=target.getPos()) Sequence(Wait(random.random() * delay), Func(moveIt)).play() return Task.done return Task.cont def stopTargetInterval(self): if self.targetInterval: self.targetInterval.pause() self.targetInterval = None def __showBob(self): # Put the bob in the water and make it gently float. self.__hideBob() self.bob.reparentTo(self.angleNP) self.bob.setPos(self.ptop, 0, 0, 0) def __hideBob(self): if self.bob != None: self.bob.detachNode() def getMouse(self): if (base.mouseWatcherNode.hasMouse()): self.mouseX = base.mouseWatcherNode.getMouseX() self.mouseY = base.mouseWatcherNode.getMouseY() else: self.mouseX = 0 self.mouseY = 0 def setfMove(self, fMoving): self.fMovingTarget = fMoving def setfTargetMode(self, fTargetMode): self.fTargetMode = fTargetMode def moveTarget(self, targetPos=None): base.distributedFishingTarget.sendUpdate('bobEnter', []) # self.stopTargetInterval() # self.target.clearColor() # if not targetPos: # x = -87.0 + random.random() * 15.0 # y = 25.0 + random.random() * 20.0 # z = -4.8 # self.targetPos = Point3(x,y,z) # else: # self.targetPos.assign(targetPos) # if self.fMovingTarget: # self.makeTargetInterval() # else: # #self.target.setPos(self.targetPos) def initFish(self): x = -10.0 + random.random() * 20.0 y = 00.0 + random.random() * 30.0 z = -1.6 self.fishPivot.setPos(x, y, z) self.turningRadius = 5.0 + random.random() * 5.0 self.fish.setPos(self.turningRadius, 0, -0.4) if self.wiggleIval: self.wiggleIval.pause() self.wiggleIval = Sequence( self.fish.hprInterval(0.5, hpr=Point3(10, 0, 0), startHpr=Point3(-10, 0, 0), blendType='easeInOut'), self.fish.hprInterval(0.5, hpr=Point3(-10, 0, 0), startHpr=Point3(10, 0, 0), blendType='easeInOut')) self.wiggleIval.loop() if self.circleIval: self.circleIval.pause() self.circleIval = self.fishPivot.hprInterval(20, Point3(360, 0, 0), startHpr=Point3(0)) self.circleIval.loop() taskMgr.remove('turnTask') taskMgr.doMethodLater(3.0 + random.random() * 3.0, self.turnFish, 'turnTask') taskMgr.remove('fishBoundsCheck') taskMgr.add(self.fishBoundsCheck, 'fishBoundsCheck') def fishBoundsCheck(self, state): pos = self.fish.getPos(self) if pos[0] < -20: self.fishPivot.setX(self.fishPivot.getX() + 40.0) elif pos[0] > 20: self.fishPivot.setX(self.fishPivot.getX() - 40.0) if pos[1] < -10: self.fishPivot.setY(self.fishPivot.getY() + 50.0) elif pos[1] > 40: self.fishPivot.setY(self.fishPivot.getY() - 50.0) return Task.cont def turnFish(self, state=None, targetPos=None): self.fish.setScale(0.3, 1, 0.3) if self.circleIval: self.circleIval.pause() newTurningRadius = 5.0 + random.random() * 5.0 fRightTurn = random.random() < 0.5 if fRightTurn: newTurningRadius *= -1.0 offset = self.turningRadius - newTurningRadius self.fishPivot.setX(self.fishPivot, offset) self.turningRadius = newTurningRadius self.fish.setX(self.turningRadius) currH = self.fishPivot.getH() % 360.0 if fRightTurn: self.circleIval = self.fishPivot.hprInterval( 20, Point3(currH - 360, 0, 0), startHpr=Point3(currH, 0, 0)) else: self.circleIval = self.fishPivot.hprInterval( 20, Point3(currH + 360, 0, 0), startHpr=Point3(currH, 0, 0)) self.circleIval.loop() taskMgr.doMethodLater(3.0 + random.random() * 3.0, self.turnFish, 'turnTask') return Task.done def makeTargetInterval(self): x = -10.0 + random.random() * 20.0 y = 0.0 + random.random() * 30.0 z = -1.6 self.targetEndPos = Point3(x, y, z) dist = Vec3(self.targetEndPos - self.targetPos).length() dur = dist / 1.5 dur = dur * (0.75 + random.random() * 0.5) self.targetInterval = Sequence( self.target.posInterval(dur, self.targetEndPos, startPos=self.targetPos, blendType='easeInOut'), self.target.posInterval(dur, self.targetPos, startPos=self.targetEndPos, blendType='easeInOut'), name='moveInterval') offsetDur = dur / random.randint(1, 4) amplitude = 0.1 + random.random() * 1.0 self.targetInterval.loop() def destroy(self): if not self.fAutonomous: self.castButton.destroy() self.buttonFrame.destroy() self.line.removeNode() taskMgr.remove('turnTask') taskMgr.remove('fishBoundsCheck') self.stopCasting() self.removeNode()
class main(ShowBase): def __init__(self): ShowBase.__init__(self) # Disable the default camera movements self.disableMouse() # # VIEW SETTINGS # self.win.setClearColor((0.16, 0.16, 0.16, 1)) render.setAntialias(AntialiasAttrib.MAuto) render2d.setAntialias(AntialiasAttrib.MAuto) # # NODE VIEW # self.viewNP = aspect2d.attachNewNode("viewNP") self.viewNP.setScale(0.5) # # NODE MANAGER # self.nodeMgr = NodeManager(self.viewNP) # # NODE RELATED EVENTS # # Add nodes self.accept("addNode", self.nodeMgr.addNode) # Remove nodes self.accept("removeNode", self.nodeMgr.removeNode) self.accept("x", self.nodeMgr.removeNode) self.accept("delete", self.nodeMgr.removeNode) # Selecting self.accept("selectNode", self.nodeMgr.selectNode) # Deselecting self.accept("mouse3", self.nodeMgr.deselectAll) # Node Drag and Drop self.accept("dragNodeStart", self.setDraggedNode) self.accept("dragNodeMove", self.updateNodeMove) self.accept("dragNodeStop", self.updateNodeStop) # Duplicate/Copy nodes self.accept("shift-d", self.nodeMgr.copyNodes) self.accept("copyNodes", self.nodeMgr.copyNodes) # Refresh node logics self.accept("ctlr-r", self.nodeMgr.updateAllLeaveNodes) self.accept("refreshNodes", self.nodeMgr.updateAllLeaveNodes) # # SOCKET RELATED EVENTS # self.accept("updateConnectedNodes", self.nodeMgr.updateConnectedNodes) # Socket connection with drag and drop self.accept("startPlug", self.nodeMgr.setStartPlug) self.accept("endPlug", self.nodeMgr.setEndPlug) self.accept("connectPlugs", self.nodeMgr.connectPlugs) self.accept("cancelPlug", self.nodeMgr.cancelPlug) # Draw line while connecting sockets self.accept("startLineDrawing", self.startLineDrawing) self.accept("stopLineDrawing", self.stopLineDrawing) # # PROJECT MANAGEMENT # self.accept("new", self.newProject) self.accept("save", self.saveProject) self.accept("load", self.loadProject) self.accept("quit", exit) # # EDITOR VIEW # # Zooming self.accept("zoom", self.zoom) self.accept("zoom_reset", self.zoomReset) self.accept("wheel_up", self.zoom, [True]) self.accept("wheel_down", self.zoom, [False]) # Drag view self.mouseSpeed = 1 self.mousePos = None self.startCameraMovement = False self.accept("mouse2", self.setMoveCamera, [True]) self.accept("mouse2-up", self.setMoveCamera, [False]) # Box select # accept the 1st mouse button events to start and stop the draw self.accept("mouse1", self.startBoxDraw) self.accept("mouse1-up", self.stopBoxDraw) # variables to store the start and current pos of the mousepointer self.startPos = LPoint2f(0, 0) self.lastPos = LPoint2f(0, 0) # variables for the to be drawn box self.boxCardMaker = CardMaker("SelectionBox") self.boxCardMaker.setColor(1, 1, 1, 0.25) self.box = None # # WINDOW RELATED EVENTS # self.screenSize = base.getSize() self.accept("window-event", self.windowEventHandler) # # MENU BAR # self.menuBar = MenuBar() # # TASKS # # Task for handling dragging of the camera/view self.taskMgr.add(self.updateCam, "task_camActualisation", priority=-4) # ------------------------------------------------------------------ # PROJECT FUNCTIONS # ------------------------------------------------------------------ def newProject(self): self.nodeMgr.cleanup() def saveProject(self): Save(self.nodeList, self.connections) def loadProject(self): self.nodeMgr.cleanup() Load(self.nodeMgr) # ------------------------------------------------------------------ # CAMERA SPECIFIC FUNCTIONS # ------------------------------------------------------------------ def setMoveCamera(self, moveCamera): """Start dragging around the editor area/camera""" # store the mouse position if weh have a mouse if base.mouseWatcherNode.hasMouse(): x = base.mouseWatcherNode.getMouseX() y = base.mouseWatcherNode.getMouseY() self.mousePos = Point2(x, y) # set the variable according to if we want to move the camera or not self.startCameraMovement = moveCamera def updateCam(self, task): """Task that will move the editor area/camera around according to mouse movements""" # variables to store the mouses current x and y position x = 0.0 y = 0.0 if base.mouseWatcherNode.hasMouse(): # get the mouse position x = base.mouseWatcherNode.getMouseX() y = base.mouseWatcherNode.getMouseY() if base.mouseWatcherNode.hasMouse() \ and self.mousePos is not None \ and self.startCameraMovement: # Move the viewer node aspect independent wp = self.win.getProperties() aspX = 1.0 aspY = 1.0 wpXSize = wp.getXSize() wpYSize = wp.getYSize() if wpXSize > wpYSize: aspX = wpXSize / float(wpYSize) else: aspY = wpYSize / float(wpXSize) mouseMoveX = (self.mousePos.getX() - x) / self.viewNP.getScale( ).getX() * self.mouseSpeed * aspX mouseMoveY = (self.mousePos.getY() - y) / self.viewNP.getScale( ).getZ() * self.mouseSpeed * aspY self.mousePos = Point2(x, y) self.viewNP.setX(self.viewNP, -mouseMoveX) self.viewNP.setZ(self.viewNP, -mouseMoveY) self.nodeMgr.updateConnections() # continue the task until it got manually stopped return task.cont def zoom(self, zoomIn): """Zoom the editor in or out dependent on the value in zoomIn""" zoomFactor = 0.05 maxZoomIn = 2 maxZoomOut = 0.1 if zoomIn: s = self.viewNP.getScale() if s.getX() - zoomFactor < maxZoomIn and s.getY( ) - zoomFactor < maxZoomIn and s.getZ() - zoomFactor < maxZoomIn: self.viewNP.setScale(s.getX() + zoomFactor, s.getY() + zoomFactor, s.getZ() + zoomFactor) else: s = self.viewNP.getScale() if s.getX() - zoomFactor > maxZoomOut and s.getY( ) - zoomFactor > maxZoomOut and s.getZ() - zoomFactor > maxZoomOut: self.viewNP.setScale(s.getX() - zoomFactor, s.getY() - zoomFactor, s.getZ() - zoomFactor) self.nodeMgr.updateConnections() def zoomReset(self): """Set the zoom level back to the default""" self.viewNP.setScale(0.5) self.nodeMgr.updateConnections() # ------------------------------------------------------------------ # DRAG LINE # ------------------------------------------------------------------ def startLineDrawing(self, startPos): """Start a task that will draw a line from the given start position to the cursor""" self.line = LineNodePath(render2d, thickness=2, colorVec=(0.8, 0.8, 0.8, 1)) self.line.moveTo(startPos) t = self.taskMgr.add(self.drawLineTask, "drawLineTask") t.startPos = startPos def drawLineTask(self, task): """Draws a line from a given start position to the cursor""" mwn = base.mouseWatcherNode if mwn.hasMouse(): pos = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) self.line.reset() self.line.moveTo(task.startPos) self.line.drawTo(pos) self.line.create() return task.cont def stopLineDrawing(self): """Stop the task that draws a line to the cursor""" taskMgr.remove("drawLineTask") if self.line is not None: self.line.reset() self.line = None # ------------------------------------------------------------------ # EDITOR NODE DRAGGING UPDATE # ------------------------------------------------------------------ def setDraggedNode(self, node): """This will set the node that is currently dragged around as well as update other selected nodes which will be moved in addition to the main dragged node""" self.draggedNode = node self.tempNodePositions = {} for node in self.nodeMgr.selectedNodes: self.tempNodePositions[node] = node.frame.getPos(render2d) def updateNodeMove(self, mouseA, mouseB): """Will be called as long as a node is beeing dragged around""" for node in self.nodeMgr.selectedNodes: if node is not self.draggedNode and node in self.tempNodePositions.keys( ): editVec = Vec3(self.tempNodePositions[node] - mouseA) newPos = mouseB + editVec node.frame.setPos(render2d, newPos) self.nodeMgr.updateConnections() def updateNodeStop(self, node=None): """Will be called when a node dragging stopped""" self.draggedNode = None self.tempNodePositions = {} self.nodeMgr.updateConnections() # ------------------------------------------------------------------ # BASIC WINDOW HANDLING # ------------------------------------------------------------------ def windowEventHandler(self, window=None): """Custom handler for window events. We mostly use this for resizing.""" # call showBase windowEvent which would otherwise get overridden and breaking the app self.windowEvent(window) if window != self.win: # This event isn't about our window. return if window is not None: # window is none if panda3d is not started if self.screenSize == base.getSize(): return self.screenSize = base.getSize() # Resize all editor frames self.menuBar.resizeFrame() # ------------------------------------------------------------------ # SELECTION BOX # ------------------------------------------------------------------ def startBoxDraw(self): """Start drawing the box""" if self.mouseWatcherNode.hasMouse(): # get the mouse position self.startPos = LPoint2f(self.mouseWatcherNode.getMouse()) taskMgr.add(self.dragBoxDrawTask, "dragBoxDrawTask") def stopBoxDraw(self): """Stop the draw box task and remove the box""" if not taskMgr.hasTaskNamed("dragBoxDrawTask"): return taskMgr.remove("dragBoxDrawTask") if self.startPos is None or self.lastPos is None: return self.nodeMgr.deselectAll() if self.box is not None: for node in self.nodeMgr.getAllNodes(): # store some view scales for calculations viewXScale = self.viewNP.getScale().getX() viewZScale = self.viewNP.getScale().getZ() # calculate the node edges nodeLeft = node.getLeft() * viewXScale nodeRight = node.getRight() * viewXScale nodeBottom = node.getBottom() * viewZScale nodeTop = node.getTop() * viewZScale # calculate bounding box edges left = min(self.lastPos.getX(), self.startPos.getX()) right = max(self.lastPos.getX(), self.startPos.getX()) top = max(self.lastPos.getY(), self.startPos.getY()) bottom = min(self.lastPos.getY(), self.startPos.getY()) # check for hits xGood = yGood = False if left < nodeLeft and right > nodeLeft: xGood = True elif left < nodeRight and right > nodeRight: xGood = True if top > nodeTop and bottom < nodeTop: yGood = True elif top > nodeBottom and bottom < nodeBottom: yGood = True # check if we have any hits if xGood and yGood: self.nodeMgr.selectNode(node, True, True) # Cleanup the selection box self.box.removeNode() self.startPos = None self.lastPos = None def dragBoxDrawTask(self, task): """This task will track the mouse position and actualize the box's size according to the first click position of the mouse""" if self.mouseWatcherNode.hasMouse(): if self.startPos is None: self.startPos = LPoint2f(self.mouseWatcherNode.getMouse()) # get the current mouse position self.lastPos = LPoint2f(self.mouseWatcherNode.getMouse()) else: return task.cont # check if we already have a box if self.box != None: # if so, remove that old box self.box.removeNode() # set the box's size self.boxCardMaker.setFrame(self.lastPos.getX(), self.startPos.getX(), self.startPos.getY(), self.lastPos.getY()) # generate, setup and draw the box node = self.boxCardMaker.generate() self.box = render2d.attachNewNode(node) self.box.setBin("gui-popup", 25) self.box.setTransparency(TransparencyAttrib.M_alpha) # run until the task is manually stopped return task.cont
class NodeEditor(DirectObject): def __init__(self, parent, customNodeMap={}, customExporterMap={}): DirectObject.__init__(self) fn = Filename.fromOsSpecific(os.path.dirname(__file__)) fn.makeTrueCase() self.icon_dir = str(fn) + "/" loadPrcFileData("", f"model-path {self.icon_dir}") # # NODE VIEW # self.viewNP = aspect2d.attachNewNode("viewNP") self.viewNP.setScale(0.5) # # NODE MANAGER # self.nodeMgr = NodeManager(self.viewNP, customNodeMap) # Drag view self.mouseSpeed = 1 self.mousePos = None self.startCameraMovement = False # Box select # variables to store the start and current pos of the mousepointer self.startPos = LPoint2f(0, 0) self.lastPos = LPoint2f(0, 0) # variables for the to be drawn box self.boxCardMaker = CardMaker("SelectionBox") self.boxCardMaker.setColor(1, 1, 1, 0.25) self.box = None # # MENU BAR # self.mainView = MainView(parent, customNodeMap, customExporterMap) self.enable_editor() # ------------------------------------------------------------------ # FRAME COMPATIBILITY FUNCTIONS # ------------------------------------------------------------------ def is_dirty(self): """ This method returns True if an unsaved state of the editor is given """ return len(self.nodeMgr.getAllNodes()) > 0 def enable_editor(self): """ Enable the editor. """ self.enable_events() # Task for handling dragging of the camera/view taskMgr.add(self.updateCam, "NodeEditor_task_camActualisation", priority=-4) self.viewNP.show() self.nodeMgr.showConnections() def disable_editor(self): """ Disable the editor. """ self.ignore_all() taskMgr.remove("NodeEditor_task_camActualisation") self.viewNP.hide() self.nodeMgr.hideConnections() def do_exception_save(self): """ Save content of editor if the application crashes """ Save(self.nodeMgr.nodeList, self.nodeMgr.connections, True) # ------------------------------------------------------------------ # NODE EDITOR RELATED EVENTS # ------------------------------------------------------------------ def enable_events(self): # Add nodes self.accept("addNode", self.nodeMgr.addNode) # Remove nodes self.accept("NodeEditor_removeNode", self.nodeMgr.removeNode) self.accept("x", self.nodeMgr.removeNode) self.accept("delete", self.nodeMgr.removeNode) # Selecting self.accept("selectNode", self.nodeMgr.selectNode) # Deselecting self.accept("mouse3", self.nodeMgr.deselectAll) # Node Drag and Drop self.accept("dragNodeStart", self.setDraggedNode) self.accept("dragNodeMove", self.updateNodeMove) self.accept("dragNodeStop", self.updateNodeStop) # Duplicate/Copy nodes self.accept("shift-d", self.nodeMgr.copyNodes) self.accept("NodeEditor_copyNodes", self.nodeMgr.copyNodes) # Refresh node logics self.accept("ctlr-r", self.nodeMgr.updateAllLeaveNodes) self.accept("NodeEditor_refreshNodes", self.nodeMgr.updateAllLeaveNodes) # # SOCKET RELATED EVENTS # self.accept("updateConnectedNodes", self.nodeMgr.updateConnectedNodes) # Socket connection with drag and drop self.accept("startPlug", self.nodeMgr.setStartPlug) self.accept("endPlug", self.nodeMgr.setEndPlug) self.accept("connectPlugs", self.nodeMgr.connectPlugs) self.accept("cancelPlug", self.nodeMgr.cancelPlug) # Draw line while connecting sockets self.accept("startLineDrawing", self.startLineDrawing) self.accept("stopLineDrawing", self.stopLineDrawing) # # CONNECTION RELATED EVENTS # self.accept("NodeEditor_updateConnections", self.nodeMgr.updateConnections) # # PROJECT MANAGEMENT # self.accept("NodeEditor_new", self.newProject) self.accept("NodeEditor_save", self.saveProject) self.accept("NodeEditor_load", self.loadProject) self.accept("quit_app", exit) # # EXPORTERS # self.accept("NodeEditor_customSave", self.customExport) # # EDITOR VIEW # # Zooming self.accept("NodeEditor_zoom", self.zoom) self.accept("NodeEditor_zoom_reset", self.zoomReset) self.accept("wheel_up", self.zoom, [True]) self.accept("wheel_down", self.zoom, [False]) # Drag view self.accept("mouse2", self.setMoveCamera, [True]) self.accept("mouse2-up", self.setMoveCamera, [False]) # Box select # accept the 1st mouse button events to start and stop the draw self.accept("mouse1", self.startBoxDraw) self.accept("mouse1-up", self.stopBoxDraw) # ------------------------------------------------------------------ # PROJECT FUNCTIONS # ------------------------------------------------------------------ def newProject(self): self.nodeMgr.cleanup() def saveProject(self): Save(self.nodeMgr.nodeList, self.nodeMgr.connections) def loadProject(self): self.nodeMgr.cleanup() Load(self.nodeMgr) def customExport(self, exporter): exporter(self.nodeMgr.nodeList, self.nodeMgr.connections) # ------------------------------------------------------------------ # CAMERA SPECIFIC FUNCTIONS # ------------------------------------------------------------------ def setMoveCamera(self, moveCamera): """Start dragging around the editor area/camera""" # store the mouse position if weh have a mouse if base.mouseWatcherNode.hasMouse(): x = base.mouseWatcherNode.getMouseX() y = base.mouseWatcherNode.getMouseY() self.mousePos = Point2(x, y) # set the variable according to if we want to move the camera or not self.startCameraMovement = moveCamera def updateCam(self, task): """Task that will move the editor area/camera around according to mouse movements""" # variables to store the mouses current x and y position x = 0.0 y = 0.0 if base.mouseWatcherNode.hasMouse(): # get the mouse position x = base.mouseWatcherNode.getMouseX() y = base.mouseWatcherNode.getMouseY() if base.mouseWatcherNode.hasMouse() \ and self.mousePos is not None \ and self.startCameraMovement: # Move the viewer node aspect independent wp = base.win.getProperties() aspX = 1.0 aspY = 1.0 wpXSize = wp.getXSize() wpYSize = wp.getYSize() if wpXSize > wpYSize: aspX = wpXSize / float(wpYSize) else: aspY = wpYSize / float(wpXSize) mouseMoveX = (self.mousePos.getX() - x) / self.viewNP.getScale( ).getX() * self.mouseSpeed * aspX mouseMoveY = (self.mousePos.getY() - y) / self.viewNP.getScale( ).getZ() * self.mouseSpeed * aspY self.mousePos = Point2(x, y) self.viewNP.setX(self.viewNP, -mouseMoveX) self.viewNP.setZ(self.viewNP, -mouseMoveY) self.nodeMgr.updateConnections() # continue the task until it got manually stopped return task.cont def zoom(self, zoomIn): """Zoom the editor in or out dependent on the value in zoomIn""" zoomFactor = 0.05 maxZoomIn = 2 maxZoomOut = 0.1 if zoomIn: s = self.viewNP.getScale() if s.getX() - zoomFactor < maxZoomIn and s.getY( ) - zoomFactor < maxZoomIn and s.getZ() - zoomFactor < maxZoomIn: self.viewNP.setScale(s.getX() + zoomFactor, s.getY() + zoomFactor, s.getZ() + zoomFactor) else: s = self.viewNP.getScale() if s.getX() - zoomFactor > maxZoomOut and s.getY( ) - zoomFactor > maxZoomOut and s.getZ() - zoomFactor > maxZoomOut: self.viewNP.setScale(s.getX() - zoomFactor, s.getY() - zoomFactor, s.getZ() - zoomFactor) self.nodeMgr.updateConnections() def zoomReset(self): """Set the zoom level back to the default""" self.viewNP.setScale(0.5) self.nodeMgr.updateConnections() # ------------------------------------------------------------------ # DRAG LINE # ------------------------------------------------------------------ def startLineDrawing(self, startPos): """Start a task that will draw a line from the given start position to the cursor""" self.line = LineNodePath(render2d, thickness=2, colorVec=(0.8, 0.8, 0.8, 1)) self.line.moveTo(startPos) t = taskMgr.add(self.drawLineTask, "drawLineTask") t.startPos = startPos def drawLineTask(self, task): """Draws a line from a given start position to the cursor""" mwn = base.mouseWatcherNode if mwn.hasMouse(): pos = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1]) self.line.reset() self.line.moveTo(task.startPos) self.line.drawTo(pos) self.line.create() return task.cont def stopLineDrawing(self): """Stop the task that draws a line to the cursor""" taskMgr.remove("drawLineTask") if self.line is not None: self.line.reset() self.line = None # ------------------------------------------------------------------ # EDITOR NODE DRAGGING UPDATE # ------------------------------------------------------------------ def setDraggedNode(self, node): """This will set the node that is currently dragged around as well as update other selected nodes which will be moved in addition to the main dragged node""" self.draggedNode = node self.draggedNode.disable() self.tempNodePositions = {} for node in self.nodeMgr.selectedNodes: self.tempNodePositions[node] = node.frame.getPos(render2d) def updateNodeMove(self, mouseA, mouseB): """Will be called as long as a node is beeing dragged around""" for node in self.nodeMgr.selectedNodes: if node is not self.draggedNode and node in self.tempNodePositions.keys( ): editVec = Vec3(self.tempNodePositions[node] - mouseA) newPos = mouseB + editVec node.frame.setPos(render2d, newPos) self.nodeMgr.updateConnections() def updateNodeStop(self, node=None): """Will be called when a node dragging stopped""" self.draggedNode.enable() self.draggedNode = None self.tempNodePositions = {} self.nodeMgr.updateConnections() # ------------------------------------------------------------------ # SELECTION BOX # ------------------------------------------------------------------ def startBoxDraw(self): """Start drawing the box""" if base.mouseWatcherNode.hasMouse(): # get the mouse position self.startPos = LPoint2f(base.mouseWatcherNode.getMouse()) taskMgr.add(self.dragBoxDrawTask, "dragBoxDrawTask") def stopBoxDraw(self): """Stop the draw box task and remove the box""" if not taskMgr.hasTaskNamed("dragBoxDrawTask"): return taskMgr.remove("dragBoxDrawTask") if self.startPos is None or self.lastPos is None: return self.nodeMgr.deselectAll() if self.box is not None: for node in self.nodeMgr.getAllNodes(): # store some view scales for calculations viewXScale = self.viewNP.getScale().getX() viewZScale = self.viewNP.getScale().getZ() # calculate the node edges nodeLeft = node.getLeft() * viewXScale nodeRight = node.getRight() * viewXScale nodeBottom = node.getBottom() * viewZScale nodeTop = node.getTop() * viewZScale # calculate bounding box edges left = min(self.lastPos.getX(), self.startPos.getX()) right = max(self.lastPos.getX(), self.startPos.getX()) top = max(self.lastPos.getY(), self.startPos.getY()) bottom = min(self.lastPos.getY(), self.startPos.getY()) # check for hits xGood = yGood = False if left < nodeLeft and right > nodeLeft: xGood = True elif left < nodeRight and right > nodeRight: xGood = True if top > nodeTop and bottom < nodeTop: yGood = True elif top > nodeBottom and bottom < nodeBottom: yGood = True # check if we have any hits if xGood and yGood: self.nodeMgr.selectNode(node, True, True) # Cleanup the selection box self.box.removeNode() self.startPos = None self.lastPos = None def dragBoxDrawTask(self, task): """This task will track the mouse position and actualize the box's size according to the first click position of the mouse""" if base.mouseWatcherNode.hasMouse(): if self.startPos is None: self.startPos = LPoint2f(base.mouseWatcherNode.getMouse()) # get the current mouse position self.lastPos = LPoint2f(base.mouseWatcherNode.getMouse()) else: return task.cont # check if we already have a box if self.box != None: # if so, remove that old box self.box.removeNode() # set the box's size self.boxCardMaker.setFrame(self.lastPos.getX(), self.startPos.getX(), self.startPos.getY(), self.lastPos.getY()) # generate, setup and draw the box node = self.boxCardMaker.generate() self.box = render2d.attachNewNode(node) self.box.setBin("gui-popup", 25) self.box.setTransparency(TransparencyAttrib.M_alpha) # run until the task is manually stopped return task.cont