""" Mopath Recorder Panel Module """ __all__ = ['MopathRecorder'] # Import Tkinter, Pmw, and the dial code from this directory tree. from panda3d.core import * from direct.showbase.DirectObject import DirectObject from direct.showbase.TkGlobal import * from direct.tkwidgets.AppShell import * from direct.directtools.DirectGlobals import * from direct.directtools.DirectUtil import * from direct.directtools.DirectGeometry import * from direct.directtools.DirectSelection import * import Pmw, os, sys from direct.tkwidgets import Dial from direct.tkwidgets import Floater from direct.tkwidgets import Slider from direct.tkwidgets import EntryScale from direct.tkwidgets import VectorWidgets if sys.version_info >= (3, 0): from tkinter.filedialog import * else: from tkFileDialog import * PRF_UTILITIES = [ 'lambda: base.direct.camera.lookAt(render)', 'lambda: base.direct.camera.setZ(render, 0.0)', 'lambda s = self: s.playbackMarker.lookAt(render)', 'lambda s = self: s.playbackMarker.setZ(render, 0.0)', 'lambda s = self: s.followTerrain(10.0)'] class MopathRecorder(AppShell, DirectObject): # Override class variables here appname = 'Mopath Recorder Panel' frameWidth = 450 frameHeight = 550 usecommandarea = 0 usestatusarea = 0 count = 0 def __init__(self, parent = None, **kw): INITOPT = Pmw.INITOPT name = 'recorder-%d' % MopathRecorder.count MopathRecorder.count += 1 optiondefs = ( ('title', self.appname, None), ('nodePath', None, None), ('name', name, None) ) self.defineoptions(kw, optiondefs) # Call superclass initialization function AppShell.__init__(self) self.initialiseoptions(MopathRecorder) self.selectNodePathNamed('camera') def appInit(self): self.name = self['name'] # Dictionary of widgets self.widgetDict = {} self.variableDict = {} # Initialize state # The active node path self.nodePath = self['nodePath'] self.playbackNodePath = self.nodePath # The active node path's parent self.nodePathParent = render # Top level node path self.recorderNodePath = base.direct.group.attachNewNode(self.name) # Temp CS for use in refinement/path extension self.tempCS = self.recorderNodePath.attachNewNode( 'mopathRecorderTempCS') # Marker for use in playback self.playbackMarker = loader.loadModel('models/misc/smiley') self.playbackMarker.setName('Playback Marker') self.playbackMarker.reparentTo(self.recorderNodePath) self.playbackMarkerIds = self.getChildIds( self.playbackMarker.getChild(0)) self.playbackMarker.hide() # Tangent marker self.tangentGroup = self.playbackMarker.attachNewNode('Tangent Group') self.tangentGroup.hide() self.tangentMarker = loader.loadModel('models/misc/sphere') self.tangentMarker.reparentTo(self.tangentGroup) self.tangentMarker.setScale(0.5) self.tangentMarker.setColor(1, 0, 1, 1) self.tangentMarker.setName('Tangent Marker') self.tangentMarkerIds = self.getChildIds( self.tangentMarker.getChild(0)) self.tangentLines = LineNodePath(self.tangentGroup) self.tangentLines.setColor(VBase4(1, 0, 1, 1)) self.tangentLines.setThickness(1) self.tangentLines.moveTo(0, 0, 0) self.tangentLines.drawTo(0, 0, 0) self.tangentLines.create() # Active node path dictionary self.nodePathDict = {} self.nodePathDict['marker'] = self.playbackMarker self.nodePathDict['camera'] = base.direct.camera self.nodePathDict['widget'] = base.direct.widget self.nodePathDict['mopathRecorderTempCS'] = self.tempCS self.nodePathNames = ['marker', 'camera', 'selected'] # ID of selected object self.manipulandumId = None self.trace = LineNodePath(self.recorderNodePath) self.oldPlaybackNodePath = None # Count of point sets recorded self.pointSet = [] self.prePoints = [] self.postPoints = [] self.pointSetDict = {} self.pointSetCount = 0 self.pointSetName = self.name + '-ps-' + repr(self.pointSetCount) # User callback to call before recording point self.samplingMode = 'Continuous' self.preRecordFunc = None # Hook to start/stop recording self.startStopHook = 'f6' self.keyframeHook = 'f10' # Curve fitter object self.lastPos = Point3(0) self.curveFitter = CurveFitter() # Curve variables # Number of ticks per parametric unit self.numTicks = 1 # Number of segments to represent each parametric unit # This just affects the visual appearance of the curve self.numSegs = 40 # The nurbs curves self.curveCollection = None # Curve drawers self.nurbsCurveDrawer = NurbsCurveDrawer() self.nurbsCurveDrawer.setCurves(ParametricCurveCollection()) self.nurbsCurveDrawer.setNumSegs(self.numSegs) self.nurbsCurveDrawer.setShowHull(0) self.nurbsCurveDrawer.setShowCvs(0) self.nurbsCurveDrawer.setNumTicks(0) self.nurbsCurveDrawer.setTickScale(5.0) self.curveNodePath = self.recorderNodePath.attachNewNode( self.nurbsCurveDrawer.getGeomNode()) useDirectRenderStyle(self.curveNodePath) # Playback variables self.maxT = 0.0 self.playbackTime = 0.0 self.loopPlayback = 1 self.playbackSF = 1.0 # Sample variables self.desampleFrequency = 1 self.numSamples = 100 self.recordStart = 0.0 self.deltaTime = 0.0 self.controlStart = 0.0 self.controlStop = 0.0 self.recordStop = 0.0 self.cropFrom = 0.0 self.cropTo = 0.0 self.fAdjustingValues = 0 # For terrain following self.iRayCS = self.recorderNodePath.attachNewNode( 'mopathRecorderIRayCS') self.iRay = SelectionRay(self.iRayCS) # Set up event hooks self.actionEvents = [ ('DIRECT_undo', self.undoHook), ('DIRECT_pushUndo', self.pushUndoHook), ('DIRECT_undoListEmpty', self.undoListEmptyHook), ('DIRECT_redo', self.redoHook), ('DIRECT_pushRedo', self.pushRedoHook), ('DIRECT_redoListEmpty', self.redoListEmptyHook), ('DIRECT_selectedNodePath', self.selectedNodePathHook), ('DIRECT_deselectedNodePath', self.deselectedNodePathHook), ('DIRECT_manipulateObjectStart', self.manipulateObjectStartHook), ('DIRECT_manipulateObjectCleanup', self.manipulateObjectCleanupHook), ] for event, method in self.actionEvents: self.accept(event, method) def createInterface(self): interior = self.interior() # FILE MENU # Get a handle on the file menu so commands can be inserted # before quit item fileMenu = self.menuBar.component('File-menu') fileMenu.insert_command( fileMenu.index('Quit'), label = 'Load Curve', command = self.loadCurveFromFile) fileMenu.insert_command( fileMenu.index('Quit'), label = 'Save Curve', command = self.saveCurveToFile) # Add mopath recorder commands to menubar self.menuBar.addmenu('Recorder', 'Mopath Recorder Panel Operations') self.menuBar.addmenuitem( 'Recorder', 'command', 'Save current curve as a new point set', label = 'Save Point Set', command = self.extractPointSetFromCurveCollection) self.menuBar.addmenuitem( 'Recorder', 'command', 'Toggle widget visability', label = 'Toggle Widget Vis', command = base.direct.toggleWidgetVis) self.menuBar.addmenuitem( 'Recorder', 'command', 'Toggle widget manipulation mode', label = 'Toggle Widget Mode', command = base.direct.manipulationControl.toggleObjectHandlesMode) self.createComboBox(self.menuFrame, 'Mopath', 'History', 'Select input points to fit curve to', '', self.selectPointSetNamed, expand = 1) self.undoButton = Button(self.menuFrame, text = 'Undo', command = base.direct.undo) if base.direct.undoList: self.undoButton['state'] = 'normal' else: self.undoButton['state'] = 'disabled' self.undoButton.pack(side = LEFT, expand = 0) self.bind(self.undoButton, 'Undo last operation') self.redoButton = Button(self.menuFrame, text = 'Redo', command = base.direct.redo) if base.direct.redoList: self.redoButton['state'] = 'normal' else: self.redoButton['state'] = 'disabled' self.redoButton.pack(side = LEFT, expand = 0) self.bind(self.redoButton, 'Redo last operation') # Record button mainFrame = Frame(interior, relief = SUNKEN, borderwidth = 2) frame = Frame(mainFrame) # Active node path # Button to select active node path widget = self.createButton(frame, 'Recording', 'Node Path:', 'Select Active Mopath Node Path', lambda s = self: base.direct.select(s.nodePath), side = LEFT, expand = 0) widget['relief'] = FLAT self.nodePathMenu = Pmw.ComboBox( frame, entry_width = 20, selectioncommand = self.selectNodePathNamed, scrolledlist_items = self.nodePathNames) self.nodePathMenu.selectitem('camera') self.nodePathMenuEntry = ( self.nodePathMenu.component('entryfield_entry')) self.nodePathMenuBG = ( self.nodePathMenuEntry.configure('background')[3]) self.nodePathMenu.pack(side = LEFT, fill = X, expand = 1) self.bind(self.nodePathMenu, 'Select active node path used for recording and playback') # Recording type self.recordingType = StringVar() self.recordingType.set('New Curve') widget = self.createRadiobutton( frame, 'left', 'Recording', 'New Curve', ('Next record session records a new path'), self.recordingType, 'New Curve', expand = 0) widget = self.createRadiobutton( frame, 'left', 'Recording', 'Refine', ('Next record session refines existing path'), self.recordingType, 'Refine', expand = 0) widget = self.createRadiobutton( frame, 'left', 'Recording', 'Extend', ('Next record session extends existing path'), self.recordingType, 'Extend', expand = 0) frame.pack(fill = X, expand = 1) frame = Frame(mainFrame) widget = self.createCheckbutton( frame, 'Recording', 'Record', 'On: path is being recorded', self.toggleRecord, 0, side = LEFT, fill = BOTH, expand = 1) widget.configure(foreground = 'Red', relief = RAISED, borderwidth = 2, anchor = CENTER, width = 16) widget = self.createButton(frame, 'Recording', 'Add Keyframe', 'Add Keyframe To Current Path', self.addKeyframe, side = LEFT, expand = 1) frame.pack(fill = X, expand = 1) mainFrame.pack(expand = 1, fill = X, pady = 3) # Playback controls playbackFrame = Frame(interior, relief = SUNKEN, borderwidth = 2) Label(playbackFrame, text = 'PLAYBACK CONTROLS', font=('MSSansSerif', 12, 'bold')).pack(fill = X) # Main playback control slider widget = self.createEntryScale( playbackFrame, 'Playback', 'Time', 'Set current playback time', resolution = 0.01, command = self.playbackGoTo, side = TOP) widget.component('hull')['relief'] = RIDGE # Kill playback task if drag slider widget['preCallback'] = self.stopPlayback # Jam duration entry into entry scale self.createLabeledEntry(widget.labelFrame, 'Resample', 'Path Duration', 'Set total curve duration', command = self.setPathDuration, side = LEFT, expand = 0) # Start stop buttons frame = Frame(playbackFrame) widget = self.createButton(frame, 'Playback', '<<', 'Jump to start of playback', self.jumpToStartOfPlayback, side = LEFT, expand = 1) widget['font'] = (('MSSansSerif', 12, 'bold')) widget = self.createCheckbutton(frame, 'Playback', 'Play', 'Start/Stop playback', self.startStopPlayback, 0, side = LEFT, fill = BOTH, expand = 1) widget.configure(anchor = 'center', justify = 'center', relief = RAISED, font = ('MSSansSerif', 12, 'bold')) widget = self.createButton(frame, 'Playback', '>>', 'Jump to end of playback', self.jumpToEndOfPlayback, side = LEFT, expand = 1) widget['font'] = (('MSSansSerif', 12, 'bold')) self.createCheckbutton(frame, 'Playback', 'Loop', 'On: loop playback', self.setLoopPlayback, self.loopPlayback, side = LEFT, fill = BOTH, expand = 0) frame.pack(fill = X, expand = 1) # Speed control frame = Frame(playbackFrame) widget = Button(frame, text = 'PB Speed Vernier', relief = FLAT, command = lambda s = self: s.setSpeedScale(1.0)) widget.pack(side = LEFT, expand = 0) self.speedScale = Scale(frame, from_ = -1, to = 1, resolution = 0.01, showvalue = 0, width = 10, orient = 'horizontal', command = self.setPlaybackSF) self.speedScale.pack(side = LEFT, fill = X, expand = 1) self.speedVar = StringVar() self.speedVar.set("0.00") self.speedEntry = Entry(frame, textvariable = self.speedVar, width = 8) self.speedEntry.bind( '', lambda e = None, s = self: s.setSpeedScale( float(s.speedVar.get()))) self.speedEntry.pack(side = LEFT, expand = 0) frame.pack(fill = X, expand = 1) playbackFrame.pack(fill = X, pady = 2) # Create notebook pages self.mainNotebook = Pmw.NoteBook(interior) self.mainNotebook.pack(fill = BOTH, expand = 1) self.resamplePage = self.mainNotebook.add('Resample') self.refinePage = self.mainNotebook.add('Refine') self.extendPage = self.mainNotebook.add('Extend') self.cropPage = self.mainNotebook.add('Crop') self.drawPage = self.mainNotebook.add('Draw') self.optionsPage = self.mainNotebook.add('Options') ## RESAMPLE PAGE label = Label(self.resamplePage, text = 'RESAMPLE CURVE', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) # Resample resampleFrame = Frame( self.resamplePage, relief = SUNKEN, borderwidth = 2) label = Label(resampleFrame, text = 'RESAMPLE CURVE', font=('MSSansSerif', 12, 'bold')).pack() widget = self.createSlider( resampleFrame, 'Resample', 'Num. Samples', 'Number of samples in resampled curve', resolution = 1, min = 2, max = 1000, command = self.setNumSamples) widget.component('hull')['relief'] = RIDGE widget['postCallback'] = self.sampleCurve frame = Frame(resampleFrame) self.createButton( frame, 'Resample', 'Make Even', 'Apply timewarp so resulting path has constant velocity', self.makeEven, side = LEFT, fill = X, expand = 1) self.createButton( frame, 'Resample', 'Face Forward', 'Compute HPR so resulting hpr curve faces along xyz tangent', self.faceForward, side = LEFT, fill = X, expand = 1) frame.pack(fill = X, expand = 0) resampleFrame.pack(fill = X, expand = 0, pady = 2) # Desample desampleFrame = Frame( self.resamplePage, relief = SUNKEN, borderwidth = 2) Label(desampleFrame, text = 'DESAMPLE CURVE', font=('MSSansSerif', 12, 'bold')).pack() widget = self.createSlider( desampleFrame, 'Resample', 'Points Between Samples', 'Specify number of points to skip between samples', min = 1, max = 100, resolution = 1, command = self.setDesampleFrequency) widget.component('hull')['relief'] = RIDGE widget['postCallback'] = self.desampleCurve desampleFrame.pack(fill = X, expand = 0, pady = 2) ## REFINE PAGE ## refineFrame = Frame(self.refinePage, relief = SUNKEN, borderwidth = 2) label = Label(refineFrame, text = 'REFINE CURVE', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) widget = self.createSlider(refineFrame, 'Refine Page', 'Refine From', 'Begin time of refine pass', resolution = 0.01, command = self.setRecordStart) widget['preCallback'] = self.setRefineMode widget['postCallback'] = lambda s = self: s.getPrePoints('Refine') widget = self.createSlider( refineFrame, 'Refine Page', 'Control Start', 'Time when full control of node path is given during refine pass', resolution = 0.01, command = self.setControlStart) widget['preCallback'] = self.setRefineMode widget = self.createSlider( refineFrame, 'Refine Page', 'Control Stop', 'Time when node path begins transition back to original curve', resolution = 0.01, command = self.setControlStop) widget['preCallback'] = self.setRefineMode widget = self.createSlider(refineFrame, 'Refine Page', 'Refine To', 'Stop time of refine pass', resolution = 0.01, command = self.setRefineStop) widget['preCallback'] = self.setRefineMode widget['postCallback'] = self.getPostPoints refineFrame.pack(fill = X) ## EXTEND PAGE ## extendFrame = Frame(self.extendPage, relief = SUNKEN, borderwidth = 2) label = Label(extendFrame, text = 'EXTEND CURVE', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) widget = self.createSlider(extendFrame, 'Extend Page', 'Extend From', 'Begin time of extend pass', resolution = 0.01, command = self.setRecordStart) widget['preCallback'] = self.setExtendMode widget['postCallback'] = lambda s = self: s.getPrePoints('Extend') widget = self.createSlider( extendFrame, 'Extend Page', 'Control Start', 'Time when full control of node path is given during extend pass', resolution = 0.01, command = self.setControlStart) widget['preCallback'] = self.setExtendMode extendFrame.pack(fill = X) ## CROP PAGE ## cropFrame = Frame(self.cropPage, relief = SUNKEN, borderwidth = 2) label = Label(cropFrame, text = 'CROP CURVE', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) widget = self.createSlider( cropFrame, 'Crop Page', 'Crop From', 'Delete all curve points before this time', resolution = 0.01, command = self.setCropFrom) widget = self.createSlider( cropFrame, 'Crop Page', 'Crop To', 'Delete all curve points after this time', resolution = 0.01, command = self.setCropTo) self.createButton(cropFrame, 'Crop Page', 'Crop Curve', 'Crop curve to specified from to times', self.cropCurve, fill = NONE) cropFrame.pack(fill = X) ## DRAW PAGE ## drawFrame = Frame(self.drawPage, relief = SUNKEN, borderwidth = 2) self.sf = Pmw.ScrolledFrame(self.drawPage, horizflex = 'elastic') self.sf.pack(fill = 'both', expand = 1) sfFrame = self.sf.interior() label = Label(sfFrame, text = 'CURVE RENDERING STYLE', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) frame = Frame(sfFrame) Label(frame, text = 'SHOW:').pack(side = LEFT, expand = 0) widget = self.createCheckbutton( frame, 'Style', 'Path', 'On: path is visible', self.setPathVis, 1, side = LEFT, fill = X, expand = 1) widget = self.createCheckbutton( frame, 'Style', 'Knots', 'On: path knots are visible', self.setKnotVis, 1, side = LEFT, fill = X, expand = 1) widget = self.createCheckbutton( frame, 'Style', 'CVs', 'On: path CVs are visible', self.setCvVis, 0, side = LEFT, fill = X, expand = 1) widget = self.createCheckbutton( frame, 'Style', 'Hull', 'On: path hull is visible', self.setHullVis, 0, side = LEFT, fill = X, expand = 1) widget = self.createCheckbutton( frame, 'Style', 'Trace', 'On: record is visible', self.setTraceVis, 0, side = LEFT, fill = X, expand = 1) widget = self.createCheckbutton( frame, 'Style', 'Marker', 'On: playback marker is visible', self.setMarkerVis, 0, side = LEFT, fill = X, expand = 1) frame.pack(fill = X, expand = 1) # Sliders widget = self.createSlider( sfFrame, 'Style', 'Num Segs', 'Set number of segments used to approximate each parametric unit', min = 1.0, max = 400, resolution = 1.0, value = 40, command = self.setNumSegs, side = TOP) widget.component('hull')['relief'] = RIDGE widget = self.createSlider( sfFrame, 'Style', 'Num Ticks', 'Set number of tick marks drawn for each unit of time', min = 0.0, max = 10.0, resolution = 1.0, value = 0.0, command = self.setNumTicks, side = TOP) widget.component('hull')['relief'] = RIDGE widget = self.createSlider( sfFrame, 'Style', 'Tick Scale', 'Set visible size of time tick marks', min = 0.01, max = 100.0, resolution = 0.01, value = 5.0, command = self.setTickScale, side = TOP) widget.component('hull')['relief'] = RIDGE self.createColorEntry( sfFrame, 'Style', 'Path Color', 'Color of curve', command = self.setPathColor, value = [255.0, 255.0, 255.0, 255.0]) self.createColorEntry( sfFrame, 'Style', 'Knot Color', 'Color of knots', command = self.setKnotColor, value = [0, 0, 255.0, 255.0]) self.createColorEntry( sfFrame, 'Style', 'CV Color', 'Color of CVs', command = self.setCvColor, value = [255.0, 0, 0, 255.0]) self.createColorEntry( sfFrame, 'Style', 'Tick Color', 'Color of Ticks', command = self.setTickColor, value = [255.0, 0, 0, 255.0]) self.createColorEntry( sfFrame, 'Style', 'Hull Color', 'Color of Hull', command = self.setHullColor, value = [255.0, 128.0, 128.0, 255.0]) #drawFrame.pack(fill = X) ## OPTIONS PAGE ## optionsFrame = Frame(self.optionsPage, relief = SUNKEN, borderwidth = 2) label = Label(optionsFrame, text = 'RECORDING OPTIONS', font=('MSSansSerif', 12, 'bold')) label.pack(fill = X) # Hooks frame = Frame(optionsFrame) widget = self.createLabeledEntry( frame, 'Recording', 'Record Hook', 'Hook used to start/stop recording', value = self.startStopHook, command = self.setStartStopHook)[0] label = self.getWidget('Recording', 'Record Hook-Label') label.configure(width = 16, anchor = W) self.setStartStopHook() widget = self.createLabeledEntry( frame, 'Recording', 'Keyframe Hook', 'Hook used to add a new keyframe', value = self.keyframeHook, command = self.setKeyframeHook)[0] label = self.getWidget('Recording', 'Keyframe Hook-Label') label.configure(width = 16, anchor = W) self.setKeyframeHook() frame.pack(expand = 1, fill = X) # PreRecordFunc frame = Frame(optionsFrame) widget = self.createComboBox( frame, 'Recording', 'Pre-Record Func', 'Function called before sampling each point', PRF_UTILITIES, self.setPreRecordFunc, history = 1, expand = 1) widget.configure(label_width = 16, label_anchor = W) widget.configure(entryfield_entry_state = 'normal') # Initialize preRecordFunc self.preRecordFunc = eval(PRF_UTILITIES[0]) self.createCheckbutton(frame, 'Recording', 'PRF Active', 'On: Pre Record Func enabled', None, 0, side = LEFT, fill = BOTH, expand = 0) frame.pack(expand = 1, fill = X) # Pack record frame optionsFrame.pack(fill = X, pady = 2) self.mainNotebook.setnaturalsize() def pushUndo(self, fResetRedo = 1): base.direct.pushUndo([self.nodePath]) def undoHook(self, nodePathList = []): # Reflect new changes pass def pushUndoHook(self): # Make sure button is reactivated self.undoButton.configure(state = 'normal') def undoListEmptyHook(self): # Make sure button is deactivated self.undoButton.configure(state = 'disabled') def pushRedo(self): base.direct.pushRedo([self.nodePath]) def redoHook(self, nodePathList = []): # Reflect new changes pass def pushRedoHook(self): # Make sure button is reactivated self.redoButton.configure(state = 'normal') def redoListEmptyHook(self): # Make sure button is deactivated self.redoButton.configure(state = 'disabled') def selectedNodePathHook(self, nodePath): """ Hook called upon selection of a node path used to select playback marker if subnode selected """ taskMgr.remove(self.name + '-curveEditTask') print(nodePath.getKey()) if nodePath.id() in self.playbackMarkerIds: base.direct.select(self.playbackMarker) elif nodePath.id() in self.tangentMarkerIds: base.direct.select(self.tangentMarker) elif nodePath.id() == self.playbackMarker.id(): self.tangentGroup.show() taskMgr.add(self.curveEditTask, self.name + '-curveEditTask') elif nodePath.id() == self.tangentMarker.id(): self.tangentGroup.show() taskMgr.add(self.curveEditTask, self.name + '-curveEditTask') else: self.tangentGroup.hide() def getChildIds(self, nodePath): ids = [nodePath.id()] kids = nodePath.getChildren() for kid in kids: ids += self.getChildIds(kid) return ids def deselectedNodePathHook(self, nodePath): """ Hook called upon deselection of a node path used to select playback marker if subnode selected """ if ((nodePath.id() == self.playbackMarker.id()) or (nodePath.id() == self.tangentMarker.id())): self.tangentGroup.hide() def curveEditTask(self, state): if self.curveCollection != None: # Update curve position if self.manipulandumId == self.playbackMarker.id(): # Show playback marker self.playbackMarker.getChild(0).show() pos = Point3(0) hpr = Point3(0) pos = self.playbackMarker.getPos(self.nodePathParent) hpr = self.playbackMarker.getHpr(self.nodePathParent) self.curveCollection.adjustXyz( self.playbackTime, VBase3(pos[0], pos[1], pos[2])) self.curveCollection.adjustHpr( self.playbackTime, VBase3(hpr[0], hpr[1], hpr[2])) # Note: this calls recompute on the curves self.nurbsCurveDrawer.draw() # Update tangent if self.manipulandumId == self.tangentMarker.id(): # If manipulating marker, update tangent # Hide playback marker self.playbackMarker.getChild(0).hide() # Where is tangent marker relative to playback marker tan = self.tangentMarker.getPos() # Transform this vector to curve space tan2Curve = Vec3( self.playbackMarker.getMat( self.nodePathParent).xformVec(tan)) # Update nurbs curve self.curveCollection.getXyzCurve().adjustTangent( self.playbackTime, tan2Curve[0], tan2Curve[1], tan2Curve[2]) # Note: this calls recompute on the curves self.nurbsCurveDrawer.draw() else: # Show playback marker self.playbackMarker.getChild(0).show() # Update tangent marker line tan = Point3(0) self.curveCollection.getXyzCurve().getTangent( self.playbackTime, tan) # Transform this point to playback marker space tan.assign( self.nodePathParent.getMat( self.playbackMarker).xformVec(tan)) self.tangentMarker.setPos(tan) # In either case update tangent line self.tangentLines.setVertex(1, tan[0], tan[1], tan[2]) return Task.cont def manipulateObjectStartHook(self): self.manipulandumId = None if base.direct.selected.last: if base.direct.selected.last.id() == self.playbackMarker.id(): self.manipulandumId = self.playbackMarker.id() elif base.direct.selected.last.id() == self.tangentMarker.id(): self.manipulandumId = self.tangentMarker.id() def manipulateObjectCleanupHook(self, nodePathList = []): # Clear flag self.manipulandumId = None def onDestroy(self, event): # Remove hooks for event, method in self.actionEvents: self.ignore(event) # remove start stop hook self.ignore(self.startStopHook) self.ignore(self.keyframeHook) self.curveNodePath.reparentTo(self.recorderNodePath) self.trace.reparentTo(self.recorderNodePath) self.recorderNodePath.removeNode() # Make sure markers are deselected base.direct.deselect(self.playbackMarker) base.direct.deselect(self.tangentMarker) # Remove tasks taskMgr.remove(self.name + '-recordTask') taskMgr.remove(self.name + '-playbackTask') taskMgr.remove(self.name + '-curveEditTask') def createNewPointSet(self): self.pointSetName = self.name + '-ps-' + repr(self.pointSetCount) # Update dictionary and record pointer to new point set self.pointSet = self.pointSetDict[self.pointSetName] = [] # Update combo box comboBox = self.getWidget('Mopath', 'History') scrolledList = comboBox.component('scrolledlist') listbox = scrolledList.component('listbox') names = list(listbox.get(0,'end')) names.append(self.pointSetName) scrolledList.setlist(names) comboBox.selectitem(self.pointSetName) # Update count self.pointSetCount += 1 def extractPointSetFromCurveFitter(self): # Get new point set based on newly created curve self.createNewPointSet() for i in range(self.curveFitter.getNumSamples()): time = self.curveFitter.getSampleT(i) pos = Point3(self.curveFitter.getSampleXyz(i)) hpr = Point3(self.curveFitter.getSampleHpr(i)) self.pointSet.append([time, pos, hpr]) def extractPointSetFromCurveCollection(self): # Use curve to compute new point set # Record maxT self.maxT = self.curveCollection.getMaxT() # Determine num samples # Limit point set to 1000 points and samples per second to 30 samplesPerSegment = min(30.0, 1000.0/self.curveCollection.getMaxT()) self.setNumSamples(self.maxT * samplesPerSegment) # Sample the curve but don't create a new curve collection self.sampleCurve(fCompute = 0) # Update widgets based on new data self.updateWidgets() def selectPointSetNamed(self, name): self.pointSet = self.pointSetDict.get(name, None) # Reload points into curve fitter # Reset curve fitters self.curveFitter.reset() for time, pos, hpr in self.pointSet: # Add it to the curve fitters self.curveFitter.addXyzHpr(time, pos, hpr) # Compute curve self.computeCurves() def setPathVis(self): if self.getVariable('Style', 'Path').get(): self.curveNodePath.show() else: self.curveNodePath.hide() def setKnotVis(self): self.nurbsCurveDrawer.setShowKnots( self.getVariable('Style', 'Knots').get()) def setCvVis(self): self.nurbsCurveDrawer.setShowCvs( self.getVariable('Style', 'CVs').get()) def setHullVis(self): self.nurbsCurveDrawer.setShowHull( self.getVariable('Style', 'Hull').get()) def setTraceVis(self): if self.getVariable('Style', 'Trace').get(): self.trace.show() else: self.trace.hide() def setMarkerVis(self): if self.getVariable('Style', 'Marker').get(): self.playbackMarker.reparentTo(self.recorderNodePath) else: self.playbackMarker.reparentTo(hidden) def setNumSegs(self, value): self.numSegs = int(value) self.nurbsCurveDrawer.setNumSegs(self.numSegs) def setNumTicks(self, value): self.nurbsCurveDrawer.setNumTicks(float(value)) def setTickScale(self, value): self.nurbsCurveDrawer.setTickScale(float(value)) def setPathColor(self, color): self.nurbsCurveDrawer.setColor( color[0]/255.0, color[1]/255.0, color[2]/255.0) self.nurbsCurveDrawer.draw() def setKnotColor(self, color): self.nurbsCurveDrawer.setKnotColor( color[0]/255.0, color[1]/255.0, color[2]/255.0) def setCvColor(self, color): self.nurbsCurveDrawer.setCvColor( color[0]/255.0, color[1]/255.0, color[2]/255.0) def setTickColor(self, color): self.nurbsCurveDrawer.setTickColor( color[0]/255.0, color[1]/255.0, color[2]/255.0) def setHullColor(self, color): self.nurbsCurveDrawer.setHullColor( color[0]/255.0, color[1]/255.0, color[2]/255.0) def setStartStopHook(self, event = None): # Clear out old hook self.ignore(self.startStopHook) # Record new one hook = self.getVariable('Recording', 'Record Hook').get() self.startStopHook = hook # Add new one self.accept(self.startStopHook, self.toggleRecordVar) def setKeyframeHook(self, event = None): # Clear out old hook self.ignore(self.keyframeHook) # Record new one hook = self.getVariable('Recording', 'Keyframe Hook').get() self.keyframeHook = hook # Add new one self.accept(self.keyframeHook, self.addKeyframe) def reset(self): self.pointSet = [] self.hasPoints = 0 self.curveCollection = None self.curveFitter.reset() self.nurbsCurveDrawer.hide() def setSamplingMode(self, mode): self.samplingMode = mode def disableKeyframeButton(self): self.getWidget('Recording', 'Add Keyframe')['state'] = 'disabled' def enableKeyframeButton(self): self.getWidget('Recording', 'Add Keyframe')['state'] = 'normal' def setRecordingType(self, type): self.recordingType.set(type) def setNewCurveMode(self): self.setRecordingType('New Curve') def setRefineMode(self): self.setRecordingType('Refine') def setExtendMode(self): self.setRecordingType('Extend') def toggleRecordVar(self): # Get recording variable v = self.getVariable('Recording', 'Record') # Toggle it v.set(1 - v.get()) # Call the command self.toggleRecord() def toggleRecord(self): if self.getVariable('Recording', 'Record').get(): # Kill old tasks taskMgr.remove(self.name + '-recordTask') taskMgr.remove(self.name + '-curveEditTask') # Remove old curve self.nurbsCurveDrawer.hide() # Reset curve fitters self.curveFitter.reset() # Update sampling mode button if necessary if self.samplingMode == 'Continuous': self.disableKeyframeButton() # Create a new point set to hold raw data self.createNewPointSet() # Clear out old trace, get ready to draw new self.initTrace() # Keyframe mode? if (self.samplingMode == 'Keyframe'): # Record first point self.lastPos.assign(Point3( self.nodePath.getPos(self.nodePathParent))) # Init delta time self.deltaTime = 0.0 # Record first point self.recordPoint(self.recordStart) # Everything else else: if ((self.recordingType.get() == 'Refine') or (self.recordingType.get() == 'Extend')): # Turn off looping playback self.loopPlayback = 0 # Update widget to reflect new value self.getVariable('Playback', 'Loop').set(0) # Select tempCS as playback nodepath self.oldPlaybackNodePath = self.playbackNodePath self.setPlaybackNodePath(self.tempCS) # Parent record node path to temp self.nodePath.reparentTo(self.playbackNodePath) # Align with temp self.nodePath.setPosHpr(0, 0, 0, 0, 0, 0) # Set playback start to self.recordStart self.playbackGoTo(self.recordStart) # start flying nodePath along path self.startPlayback() # Start new task t = taskMgr.add( self.recordTask, self.name + '-recordTask') t.startTime = globalClock.getFrameTime() else: if self.samplingMode == 'Continuous': # Kill old task taskMgr.remove(self.name + '-recordTask') if ((self.recordingType.get() == 'Refine') or (self.recordingType.get() == 'Extend')): # Reparent node path back to parent self.nodePath.wrtReparentTo(self.nodePathParent) # Restore playback Node Path self.setPlaybackNodePath(self.oldPlaybackNodePath) else: # Add last point self.addKeyframe(0) # Reset sampling mode self.setSamplingMode('Continuous') self.enableKeyframeButton() # Clean up after refine or extend if ((self.recordingType.get() == 'Refine') or (self.recordingType.get() == 'Extend')): # Merge prePoints, pointSet, postPoints self.mergePoints() # Clear out pre and post list self.prePoints = [] self.postPoints = [] # Reset recording mode self.setNewCurveMode() # Compute curve self.computeCurves() def recordTask(self, state): # Record raw data point time = self.recordStart + ( globalClock.getFrameTime() - state.startTime) self.recordPoint(time) return Task.cont def addKeyframe(self, fToggleRecord = 1): # Make sure we're in a recording mode! if (fToggleRecord and (not self.getVariable('Recording', 'Record').get())): # Set sampling mode self.setSamplingMode('Keyframe') # This will automatically add the first point self.toggleRecordVar() else: # Use distance as a time pos = self.nodePath.getPos(self.nodePathParent) deltaPos = Vec3(pos - self.lastPos).length() if deltaPos != 0: # If we've moved at all, use delta Pos as time self.deltaTime = self.deltaTime + deltaPos else: # Otherwise add one second self.deltaTime = self.deltaTime + 1.0 # Record point at new time self.recordPoint(self.recordStart + self.deltaTime) # Update last pos self.lastPos.assign(pos) def easeInOut(self, t): x = t * t return (3 * x) - (2 * t * x) def setPreRecordFunc(self, func): # Note: If func is one defined at command prompt, need to set # __builtins__.func = func at command line self.preRecordFunc = eval(func) # Update widget to reflect new value self.getVariable('Recording', 'PRF Active').set(1) def recordPoint(self, time): # Call user define callback before recording point if (self.getVariable('Recording', 'PRF Active').get() and (self.preRecordFunc != None)): self.preRecordFunc() # Get point pos = self.nodePath.getPos(self.nodePathParent) hpr = self.nodePath.getHpr(self.nodePathParent) qNP = Quat() qNP.setHpr(hpr) # Blend between recordNodePath and self.nodePath if ((self.recordingType.get() == 'Refine') or (self.recordingType.get() == 'Extend')): if ((time < self.controlStart) and ((self.controlStart - self.recordStart) != 0.0)): rPos = self.playbackNodePath.getPos(self.nodePathParent) rHpr = self.playbackNodePath.getHpr(self.nodePathParent) qR = Quat() qR.setHpr(rHpr) t = self.easeInOut(((time - self.recordStart)/ (self.controlStart - self.recordStart))) # Transition between the recorded node path and the driven one pos = (rPos * (1 - t)) + (pos * t) q = qSlerp(qR, qNP, t) hpr.assign(q.getHpr()) elif ((self.recordingType.get() == 'Refine') and (time > self.controlStop) and ((self.recordStop - self.controlStop) != 0.0)): rPos = self.playbackNodePath.getPos(self.nodePathParent) rHpr = self.playbackNodePath.getHpr(self.nodePathParent) qR = Quat() qR.setHpr(rHpr) t = self.easeInOut(((time - self.controlStop)/ (self.recordStop - self.controlStop))) # Transition between the recorded node path and the driven one pos = (pos * (1 - t)) + (rPos * t) q = qSlerp(qNP, qR, t) hpr.assign(q.getHpr()) # Add it to the point set self.pointSet.append([time, pos, hpr]) # Add it to the curve fitters self.curveFitter.addXyzHpr(time, pos, hpr) # Update trace now if recording keyframes if (self.samplingMode == 'Keyframe'): self.trace.reset() for t, p, h in self.pointSet: self.trace.drawTo(p[0], p[1], p[2]) self.trace.create() def computeCurves(self): # Check to make sure curve fitters have points if (self.curveFitter.getNumSamples() == 0): print('MopathRecorder.computeCurves: Must define curve first') return # Create curves # XYZ self.curveFitter.sortPoints() self.curveFitter.wrapHpr() self.curveFitter.computeTangents(1) # This is really a collection self.curveCollection = self.curveFitter.makeNurbs() self.nurbsCurveDrawer.setCurves(self.curveCollection) self.nurbsCurveDrawer.draw() # Update widget based on new curve self.updateWidgets() def initTrace(self): self.trace.reset() # Put trace line segs under node path's parent self.trace.reparentTo(self.nodePathParent) # Show it self.trace.show() def updateWidgets(self): if not self.curveCollection: return self.fAdjustingValues = 1 # Widgets depending on max T maxT = self.curveCollection.getMaxT() maxT_text = '%0.2f' % maxT # Playback controls self.getWidget('Playback', 'Time').configure(max = maxT_text) self.getVariable('Resample', 'Path Duration').set(maxT_text) # Refine widgets widget = self.getWidget('Refine Page', 'Refine From') widget.configure(max = maxT) widget.set(0.0) widget = self.getWidget('Refine Page', 'Control Start') widget.configure(max = maxT) widget.set(0.0) widget = self.getWidget('Refine Page', 'Control Stop') widget.configure(max = maxT) widget.set(float(maxT)) widget = self.getWidget('Refine Page', 'Refine To') widget.configure(max = maxT) widget.set(float(maxT)) # Extend widgets widget = self.getWidget('Extend Page', 'Extend From') widget.configure(max = maxT) widget.set(float(0.0)) widget = self.getWidget('Extend Page', 'Control Start') widget.configure(max = maxT) widget.set(float(0.0)) # Crop widgets widget = self.getWidget('Crop Page', 'Crop From') widget.configure(max = maxT) widget.set(float(0.0)) widget = self.getWidget('Crop Page', 'Crop To') widget.configure(max = maxT) widget.set(float(maxT)) self.maxT = float(maxT) # Widgets depending on number of samples numSamples = self.curveFitter.getNumSamples() widget = self.getWidget('Resample', 'Points Between Samples') widget.configure(max=numSamples) widget = self.getWidget('Resample', 'Num. Samples') widget.configure(max = 4 * numSamples) widget.set(numSamples, 0) self.fAdjustingValues = 0 def selectNodePathNamed(self, name): nodePath = None if name == 'init': nodePath = self.nodePath # Add Combo box entry for the initial node path self.addNodePath(nodePath) elif name == 'selected': nodePath = base.direct.selected.last # Add Combo box entry for this selected object self.addNodePath(nodePath) else: nodePath = self.nodePathDict.get(name, None) if (nodePath == None): # See if this evaluates into a node path try: nodePath = eval(name) if isinstance(nodePath, NodePath): self.addNodePath(nodePath) else: # Good eval but not a node path, give up nodePath = None except: # Bogus eval nodePath = None # Clear bogus entry from listbox listbox = self.nodePathMenu.component('scrolledlist') listbox.setlist(self.nodePathNames) else: if name == 'widget': # Record relationship between selected nodes and widget base.direct.selected.getWrtAll() if name == 'marker': self.playbackMarker.show() # Initialize tangent marker position tan = Point3(0) if self.curveCollection != None: self.curveCollection.getXyzCurve().getTangent( self.playbackTime, tan) self.tangentMarker.setPos(tan) else: self.playbackMarker.hide() # Update active node path self.setNodePath(nodePath) def setNodePath(self, nodePath): self.playbackNodePath = self.nodePath = nodePath if self.nodePath: # Record nopath's parent self.nodePathParent = self.nodePath.getParent() # Put curve drawer under record node path's parent self.curveNodePath.reparentTo(self.nodePathParent) # Set entry color self.nodePathMenuEntry.configure( background = self.nodePathMenuBG) else: # Flash entry self.nodePathMenuEntry.configure(background = 'Pink') def setPlaybackNodePath(self, nodePath): self.playbackNodePath = nodePath def addNodePath(self, nodePath): self.addNodePathToDict(nodePath, self.nodePathNames, self.nodePathMenu, self.nodePathDict) def addNodePathToDict(self, nodePath, names, menu, dict): if not nodePath: return # Get node path's name name = nodePath.getName() if name in ['mopathRecorderTempCS', 'widget', 'camera', 'marker']: dictName = name else: # Generate a unique name for the dict dictName = name + '-' + repr(nodePath.id()) if dictName not in dict: # Update combo box to include new item names.append(dictName) listbox = menu.component('scrolledlist') listbox.setlist(names) # Add new item to dictionary dict[dictName] = nodePath menu.selectitem(dictName) def setLoopPlayback(self): self.loopPlayback = self.getVariable('Playback', 'Loop').get() def playbackGoTo(self, time): if self.curveCollection == None: return self.playbackTime = CLAMP(time, 0.0, self.maxT) if self.curveCollection != None: pos = Point3(0) hpr = Point3(0) self.curveCollection.evaluate(self.playbackTime, pos, hpr) self.playbackNodePath.setPosHpr(self.nodePathParent, pos, hpr) def startPlayback(self): if self.curveCollection == None: return # Kill any existing tasks self.stopPlayback() # Make sure checkbutton is set self.getVariable('Playback', 'Play').set(1) # Start new playback task t = taskMgr.add( self.playbackTask, self.name + '-playbackTask') t.currentTime = self.playbackTime t.lastTime = globalClock.getFrameTime() def setSpeedScale(self, value): self.speedScale.set(math.log10(value)) def setPlaybackSF(self, value): self.playbackSF = pow(10.0, float(value)) self.speedVar.set('%0.2f' % self.playbackSF) def playbackTask(self, state): time = globalClock.getFrameTime() dTime = self.playbackSF * (time - state.lastTime) state.lastTime = time if self.loopPlayback: cTime = (state.currentTime + dTime) % self.maxT else: cTime = state.currentTime + dTime # Stop task if not looping and at end of curve # Or if refining curve and past recordStop if ((self.recordingType.get() == 'Refine') and (cTime > self.recordStop)): # Go to recordStop self.getWidget('Playback', 'Time').set(self.recordStop) # Then stop playback self.stopPlayback() # Also kill record task self.toggleRecordVar() return Task.done elif ((self.loopPlayback == 0) and (cTime > self.maxT)): # Go to maxT self.getWidget('Playback', 'Time').set(self.maxT) # Then stop playback self.stopPlayback() return Task.done elif ((self.recordingType.get() == 'Extend') and (cTime > self.controlStart)): # Go to final point self.getWidget('Playback', 'Time').set(self.controlStart) # Stop playback self.stopPlayback() return Task.done # Otherwise go to specified time and continue self.getWidget('Playback', 'Time').set(cTime) state.currentTime = cTime return Task.cont def stopPlayback(self): self.getVariable('Playback', 'Play').set(0) taskMgr.remove(self.name + '-playbackTask') def jumpToStartOfPlayback(self): self.stopPlayback() self.getWidget('Playback', 'Time').set(0.0) def jumpToEndOfPlayback(self): self.stopPlayback() if self.curveCollection != None: self.getWidget('Playback', 'Time').set(self.maxT) def startStopPlayback(self): if self.getVariable('Playback', 'Play').get(): self.startPlayback() else: self.stopPlayback() def setDesampleFrequency(self, frequency): self.desampleFrequency = frequency def desampleCurve(self): if (self.curveFitter.getNumSamples() == 0): print('MopathRecorder.desampleCurve: Must define curve first') return # NOTE: This is destructive, points will be deleted from curve fitter self.curveFitter.desample(self.desampleFrequency) # Compute new curve based on desampled data self.computeCurves() # Get point set from the curve fitter self.extractPointSetFromCurveFitter() def setNumSamples(self, numSamples): self.numSamples = int(numSamples) def sampleCurve(self, fCompute = 1): if self.curveCollection == None: print('MopathRecorder.sampleCurve: Must define curve first') return # Reset curve fitters self.curveFitter.reset() # Sample curve using specified number of samples self.curveFitter.sample(self.curveCollection, self.numSamples) if fCompute: # Now recompute curves self.computeCurves() # Get point set from the curve fitter self.extractPointSetFromCurveFitter() def makeEven(self): # Note: segments_per_unit = 2 seems to give a good fit self.curveCollection.makeEven(self.maxT, 2) # Get point set from curve self.extractPointSetFromCurveCollection() def faceForward(self): # Note: segments_per_unit = 2 seems to give a good fit self.curveCollection.faceForward(2) # Get point set from curve self.extractPointSetFromCurveCollection() def setPathDuration(self, event): newMaxT = float(self.getWidget('Resample', 'Path Duration').get()) self.setPathDurationTo(newMaxT) def setPathDurationTo(self, newMaxT): # Compute scale factor sf = newMaxT/self.maxT # Scale curve collection self.curveCollection.resetMaxT(newMaxT) # Scale point set # Save handle to old point set oldPointSet = self.pointSet # Create new point set self.createNewPointSet() # Reset curve fitters self.curveFitter.reset() # Now scale values for time, pos, hpr in oldPointSet: newTime = time * sf # Update point set self.pointSet.append([newTime, Point3(pos), Point3(hpr)]) # Add it to the curve fitters self.curveFitter.addXyzHpr(newTime, pos, hpr) # Update widgets self.updateWidgets() # Compute curve #self.computeCurves() def setRecordStart(self, value): self.recordStart = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 # Adjust refine widgets # Make sure we're in sync self.getWidget('Refine Page', 'Refine From').set( self.recordStart) self.getWidget('Extend Page', 'Extend From').set( self.recordStart) # Check bounds if self.recordStart > self.controlStart: self.getWidget('Refine Page', 'Control Start').set( self.recordStart) self.getWidget('Extend Page', 'Control Start').set( self.recordStart) if self.recordStart > self.controlStop: self.getWidget('Refine Page', 'Control Stop').set( self.recordStart) if self.recordStart > self.recordStop: self.getWidget('Refine Page', 'Refine To').set(self.recordStart) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def getPrePoints(self, type = 'Refine'): # Switch to appropriate recording type self.setRecordingType(type) # Reset prePoints self.prePoints = [] # See if we need to save any points before recordStart for i in range(len(self.pointSet)): # Have we passed recordStart? if self.recordStart < self.pointSet[i][0]: # Get a copy of the points prior to recordStart self.prePoints = self.pointSet[:i-1] break def setControlStart(self, value): self.controlStart = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 # Adjust refine widgets # Make sure both pages are in sync self.getWidget('Refine Page', 'Control Start').set( self.controlStart) self.getWidget('Extend Page', 'Control Start').set( self.controlStart) # Check bounds on other widgets if self.controlStart < self.recordStart: self.getWidget('Refine Page', 'Refine From').set( self.controlStart) self.getWidget('Extend Page', 'Extend From').set( self.controlStart) if self.controlStart > self.controlStop: self.getWidget('Refine Page', 'Control Stop').set( self.controlStart) if self.controlStart > self.recordStop: self.getWidget('Refine Page', 'Refine To').set( self.controlStart) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def setControlStop(self, value): self.controlStop = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 if self.controlStop < self.recordStart: self.getWidget('Refine Page', 'Refine From').set( self.controlStop) if self.controlStop < self.controlStart: self.getWidget('Refine Page', 'Control Start').set( self.controlStop) if self.controlStop > self.recordStop: self.getWidget('Refine Page', 'Refine To').set( self.controlStop) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def setRefineStop(self, value): self.recordStop = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 if self.recordStop < self.recordStart: self.getWidget('Refine Page', 'Refine From').set( self.recordStop) if self.recordStop < self.controlStart: self.getWidget('Refine Page', 'Control Start').set( self.recordStop) if self.recordStop < self.controlStop: self.getWidget('Refine Page', 'Control Stop').set( self.recordStop) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def getPostPoints(self): # Set flag so we know to do a refine pass self.setRefineMode() # Reset postPoints self.postPoints = [] # See if we need to save any points after recordStop for i in range(len(self.pointSet)): # Have we reached recordStop? if self.recordStop < self.pointSet[i][0]: # Get a copy of the points after recordStop self.postPoints = self.pointSet[i:] break def mergePoints(self): # prepend pre points self.pointSet[0:0] = self.prePoints for time, pos, hpr in self.prePoints: # Add it to the curve fitters self.curveFitter.addXyzHpr(time, pos, hpr) # And post points # What is end time of pointSet? endTime = self.pointSet[-1][0] for time, pos, hpr in self.postPoints: adjustedTime = endTime + (time - self.recordStop) # Add it to point set self.pointSet.append([adjustedTime, pos, hpr]) # Add it to the curve fitters self.curveFitter.addXyzHpr(adjustedTime, pos, hpr) def setCropFrom(self, value): self.cropFrom = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 if self.cropFrom > self.cropTo: self.getWidget('Crop Page', 'Crop To').set( self.cropFrom) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def setCropTo(self, value): self.cropTo = value # Someone else is adjusting values, let them take care of it if self.fAdjustingValues: return self.fAdjustingValues = 1 if self.cropTo < self.cropFrom: self.getWidget('Crop Page', 'Crop From').set( self.cropTo) # Move playback node path to specified time self.getWidget('Playback', 'Time').set(value) self.fAdjustingValues = 0 def cropCurve(self): if self.pointSet == None: print('Empty Point Set') return # Keep handle on old points oldPoints = self.pointSet # Create new point set self.createNewPointSet() # Copy over points between from/to # Reset curve fitters self.curveFitter.reset() # Add start point pos = Point3(0) hpr = Point3(0) self.curveCollection.evaluate(self.cropFrom, pos, hpr) self.curveFitter.addXyzHpr(0.0, pos, hpr) # Get points within bounds for time, pos, hpr in oldPoints: # Is it within the time? if ((time > self.cropFrom) and (time < self.cropTo)): # Add it to the curve fitters t = time - self.cropFrom self.curveFitter.addXyzHpr(t, pos, hpr) # And the point set self.pointSet.append([t, pos, hpr]) # Add last point pos = Vec3(0) hpr = Vec3(0) self.curveCollection.evaluate(self.cropTo, pos, hpr) self.curveFitter.addXyzHpr(self.cropTo - self.cropFrom, pos, hpr) # Compute curve self.computeCurves() def loadCurveFromFile(self): # Use first directory in model path mPath = getModelPath() if mPath.getNumDirectories() > 0: if repr(mPath.getDirectory(0)) == '.': path = '.' else: path = mPath.getDirectory(0).toOsSpecific() else: path = '.' if not os.path.isdir(path): print('MopathRecorder Info: Empty Model Path!') print('Using current directory') path = '.' mopathFilename = askopenfilename( defaultextension = '.egg', filetypes = (('Egg Files', '*.egg'), ('Bam Files', '*.bam'), ('All files', '*')), initialdir = path, title = 'Load Nurbs Curve', parent = self.parent) if mopathFilename: self.reset() nodePath = loader.loadModel( Filename.fromOsSpecific(mopathFilename)) self.curveCollection = ParametricCurveCollection() # MRM: Add error check self.curveCollection.addCurves(nodePath.node()) nodePath.removeNode() if self.curveCollection: # Draw the curve self.nurbsCurveDrawer.setCurves(self.curveCollection) self.nurbsCurveDrawer.draw() # Save a pointset for this curve self.extractPointSetFromCurveCollection() else: self.reset() def saveCurveToFile(self): # Use first directory in model path mPath = getModelPath() if mPath.getNumDirectories() > 0: if repr(mPath.getDirectory(0)) == '.': path = '.' else: path = mPath.getDirectory(0).toOsSpecific() else: path = '.' if not os.path.isdir(path): print('MopathRecorder Info: Empty Model Path!') print('Using current directory') path = '.' mopathFilename = asksaveasfilename( defaultextension = '.egg', filetypes = (('Egg Files', '*.egg'), ('Bam Files', '*.bam'), ('All files', '*')), initialdir = path, title = 'Save Nurbs Curve as', parent = self.parent) if mopathFilename: self.curveCollection.writeEgg(Filename(mopathFilename)) def followTerrain(self, height = 1.0): self.iRay.rayCollisionNodePath.reparentTo(self.nodePath) entry = self.iRay.pickGeom3D() if entry: hitPtDist = Vec3(entry.getFromIntersectionPoint()).length() self.nodePath.setZ(self.nodePath, height - hitPtDist) self.iRay.rayCollisionNodePath.reparentTo(self.recorderNodePath) ## WIDGET UTILITY FUNCTIONS ## def addWidget(self, widget, category, text): self.widgetDict[category + '-' + text] = widget def getWidget(self, category, text): return self.widgetDict[category + '-' + text] def getVariable(self, category, text): return self.variableDict[category + '-' + text] def createLabeledEntry(self, parent, category, text, balloonHelp, value = '', command = None, relief = 'sunken', side = LEFT, expand = 1, width = 12): frame = Frame(parent) variable = StringVar() variable.set(value) label = Label(frame, text = text) label.pack(side = LEFT, fill = X) self.bind(label, balloonHelp) self.widgetDict[category + '-' + text + '-Label'] = label entry = Entry(frame, width = width, relief = relief, textvariable = variable) entry.pack(side = LEFT, fill = X, expand = expand) self.bind(entry, balloonHelp) self.widgetDict[category + '-' + text] = entry self.variableDict[category + '-' + text] = variable if command: entry.bind('', command) frame.pack(side = side, fill = X, expand = expand) return (frame, label, entry) def createButton(self, parent, category, text, balloonHelp, command, side = 'top', expand = 0, fill = X): widget = Button(parent, text = text) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(side = side, fill = fill, expand = expand) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createCheckbutton(self, parent, category, text, balloonHelp, command, initialState, side = 'top', fill = X, expand = 0): bool = BooleanVar() bool.set(initialState) widget = Checkbutton(parent, text = text, anchor = W, variable = bool) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(side = side, fill = fill, expand = expand) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget self.variableDict[category + '-' + text] = bool return widget def createRadiobutton(self, parent, side, category, text, balloonHelp, variable, value, command = None, fill = X, expand = 0): widget = Radiobutton(parent, text = text, anchor = W, variable = variable, value = value) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(side = side, fill = fill, expand = expand) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createFloater(self, parent, category, text, balloonHelp, command = None, min = 0.0, resolution = None, maxVelocity = 10.0, **kw): kw['text'] = text kw['min'] = min kw['maxVelocity'] = maxVelocity kw['resolution'] = resolution widget = Floater.Floater(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createAngleDial(self, parent, category, text, balloonHelp, command = None, **kw): kw['text'] = text widget = Dial.AngleDial(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createSlider(self, parent, category, text, balloonHelp, command = None, min = 0.0, max = 1.0, resolution = None, side = TOP, fill = X, expand = 1, **kw): kw['text'] = text kw['min'] = min kw['max'] = max kw['resolution'] = resolution #widget = apply(EntryScale.EntryScale, (parent,), kw) from direct.tkwidgets import Slider widget = Slider.Slider(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(side = side, fill = fill, expand = expand) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createEntryScale(self, parent, category, text, balloonHelp, command = None, min = 0.0, max = 1.0, resolution = None, side = TOP, fill = X, expand = 1, **kw): kw['text'] = text kw['min'] = min kw['max'] = max kw['resolution'] = resolution widget = EntryScale.EntryScale(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(side = side, fill = fill, expand = expand) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createVector2Entry(self, parent, category, text, balloonHelp, command = None, **kw): # Set label's text kw['text'] = text widget = VectorWidgets.Vector2Entry(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createVector3Entry(self, parent, category, text, balloonHelp, command = None, **kw): # Set label's text kw['text'] = text widget = VectorWidgets.Vector3Entry(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createColorEntry(self, parent, category, text, balloonHelp, command = None, **kw): # Set label's text kw['text'] = text widget = VectorWidgets.ColorEntry(parent, **kw) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget, balloonHelp) self.widgetDict[category + '-' + text] = widget return widget def createOptionMenu(self, parent, category, text, balloonHelp, items, command): optionVar = StringVar() if len(items) > 0: optionVar.set(items[0]) widget = Pmw.OptionMenu(parent, labelpos = W, label_text = text, label_width = 12, menu_tearoff = 1, menubutton_textvariable = optionVar, items = items) # Do this after the widget so command isn't called on creation widget['command'] = command widget.pack(fill = X) self.bind(widget.component('menubutton'), balloonHelp) self.widgetDict[category + '-' + text] = widget self.variableDict[category + '-' + text] = optionVar return optionVar def createComboBox(self, parent, category, text, balloonHelp, items, command, history = 0, side = LEFT, expand = 0, fill = X): widget = Pmw.ComboBox(parent, labelpos = W, label_text = text, label_anchor = 'e', label_width = 12, entry_width = 16, history = history, scrolledlist_items = items) # Don't allow user to edit entryfield widget.configure(entryfield_entry_state = 'disabled') # Select first item if it exists if len(items) > 0: widget.selectitem(items[0]) # Bind selection command widget['selectioncommand'] = command widget.pack(side = side, fill = fill, expand = expand) # Bind help self.bind(widget, balloonHelp) # Record widget self.widgetDict[category + '-' + text] = widget return widget def makeCameraWindow(self): # First, we need to make a new layer on the window. chan = base.win.getChannel(0) self.cLayer = chan.makeLayer(1) self.layerIndex = 1 self.cDr = self.cLayer.makeDisplayRegion(0.6, 1.0, 0, 0.4) self.cDr.setClearDepthActive(1) self.cDr.setClearColorActive(1) self.cDr.setClearColor(Vec4(0)) # It gets its own camera self.cCamera = render.attachNewNode('cCamera') self.cCamNode = Camera('cCam') self.cLens = PerspectiveLens() self.cLens.setFov(40, 40) self.cLens.setNear(0.1) self.cLens.setFar(100.0) self.cCamNode.setLens(self.cLens) self.cCamNode.setScene(render) self.cCam = self.cCamera.attachNewNode(self.cCamNode) self.cDr.setCamera(self.cCam)