panda3d/doc/SceneEditor/seMopathRecorder.py
2006-09-26 17:24:35 +00:00

2074 lines
84 KiB
Python

#################################################################
# seMopathRecorder.py
# Originally from MopathRecorder.py
# Altered by Yi-Hong Lin, yihhongl@andrew.cmu.edu, 2004
#
# We have to change something about data flow and UI
# so the curves data can be saved into our dataHolder.
# Other things we have changed here is that we have added a copy model
# of target nodePath under the render when the recording begins.
# And, of course, we have removed it right after the recording ends.
# You can find it in toggleRecord function.
#
#################################################################
from direct.showbase.DirectObject import DirectObject
from direct.tkwidgets.AppShell import AppShell
#from direct.directtools.DirectGlobals import *
#from direct.directtools.DirectUtil import *
from seGeometry import *
from seSelection import *
from direct.task.Task import Task
from direct.tkwidgets.Dial import AngleDial
from direct.tkwidgets.Floater import Floater
from direct.tkwidgets.Slider import Slider
from direct.tkwidgets.EntryScale import EntryScale
from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
from direct.tkwidgets.VectorWidgets import ColorEntry
from Tkinter import Button, Frame, Radiobutton, Checkbutton, Label
from Tkinter import StringVar, BooleanVar, Entry, Scale
import os, string, Tkinter, Pmw
import __builtin__
PRF_UTILITIES = [
'lambda: camera.lookAt(render)',
'lambda: 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')
self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
def appInit(self):
self.mopathRecorderNode = render.attachNewNode("MopathRecorder")
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 = self.mopathRecorderNode.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/sphere') ###
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'] = camera
self.nodePathDict['widget'] = SEditor.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-' + `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 = self.toggleWidgetVis)
self.menuBar.addmenuitem(
'Recorder', 'command',
'Toggle widget manipulation mode',
label = 'Toggle Widget Mode',
command = SEditor.manipulationControl.toggleObjectHandlesMode)
self.historyWidget = self.createComboBox(self.menuFrame, 'Mopath', 'Path:',
'Select input points to fit curve to', '',
self.selectPointSetNamed, expand = 1)
self.undoButton = Button(self.menuFrame, text = 'Undo',
command = SEditor.undo)
if SEditor.undoList:
self.undoButton['state'] = 'normal'
else:
self.undoButton['state'] = 'disabled'
self.undoButton.pack(side = Tkinter.LEFT, expand = 0)
self.bind(self.undoButton, 'Undo last operation')
self.redoButton = Button(self.menuFrame, text = 'Redo',
command = SEditor.redo)
if SEditor.redoList:
self.redoButton['state'] = 'normal'
else:
self.redoButton['state'] = 'disabled'
self.redoButton.pack(side = Tkinter.LEFT, expand = 0)
self.bind(self.redoButton, 'Redo last operation')
# Record button
mainFrame = Frame(interior, relief = Tkinter.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: SEditor.select(s.nodePath),
side = Tkinter.LEFT, expand = 0)
widget['relief'] = Tkinter.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 = Tkinter.LEFT, fill = Tkinter.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 = Tkinter.X, expand = 1)
frame = Frame(mainFrame)
widget = self.createCheckbutton(
frame, 'Recording', 'Record',
'On: path is being recorded', self.toggleRecord, 0,
side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 1)
widget.configure(foreground = 'Red', relief = Tkinter.RAISED, borderwidth = 2,
anchor = Tkinter.CENTER, width = 16)
widget = self.createButton(frame, 'Recording', 'Add Keyframe',
'Add Keyframe To Current Path',
self.addKeyframe,
side = Tkinter.LEFT, expand = 1)
widget = self.createButton(frame, 'Recording', 'Bind Path to Node',
'Bind Motion Path to selected Object',
self.bindMotionPathToNode,
side = Tkinter.LEFT, expand = 1)
frame.pack(fill = Tkinter.X, expand = 1)
mainFrame.pack(expand = 1, fill = Tkinter.X, pady = 3)
# Playback controls
playbackFrame = Frame(interior, relief = Tkinter.SUNKEN,
borderwidth = 2)
Label(playbackFrame, text = 'PLAYBACK CONTROLS',
font=('MSSansSerif', 12, 'bold')).pack(fill = Tkinter.X)
# Main playback control slider
widget = self.createEntryScale(
playbackFrame, 'Playback', 'Time', 'Set current playback time',
resolution = 0.01, command = self.playbackGoTo, side = Tkinter.TOP)
widget.component('hull')['relief'] = Tkinter.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 = Tkinter.LEFT, expand = 0)
# Start stop buttons
frame = Frame(playbackFrame)
widget = self.createButton(frame, 'Playback', '<<',
'Jump to start of playback',
self.jumpToStartOfPlayback,
side = Tkinter.LEFT, expand = 1)
widget['font'] = (('MSSansSerif', 12, 'bold'))
widget = self.createCheckbutton(frame, 'Playback', 'Play',
'Start/Stop playback',
self.startStopPlayback, 0,
side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 1)
widget.configure(anchor = 'center', justify = 'center',
relief = Tkinter.RAISED, font = ('MSSansSerif', 12, 'bold'))
widget = self.createButton(frame, 'Playback', '>>',
'Jump to end of playback',
self.jumpToEndOfPlayback,
side = Tkinter.LEFT, expand = 1)
widget['font'] = (('MSSansSerif', 12, 'bold'))
self.createCheckbutton(frame, 'Playback', 'Loop',
'On: loop playback',
self.setLoopPlayback, self.loopPlayback,
side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 0)
frame.pack(fill = Tkinter.X, expand = 1)
# Speed control
frame = Frame(playbackFrame)
widget = Button(frame, text = 'PB Speed Vernier', relief = Tkinter.FLAT,
command = lambda s = self: s.setSpeedScale(1.0))
widget.pack(side = Tkinter.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 = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
self.speedVar = StringVar()
self.speedVar.set("0.00")
self.speedEntry = Entry(frame, textvariable = self.speedVar,
width = 8)
self.speedEntry.bind(
'<Return>',
lambda e = None, s = self: s.setSpeedScale(
string.atof(s.speedVar.get())))
self.speedEntry.pack(side = Tkinter.LEFT, expand = 0)
frame.pack(fill = Tkinter.X, expand = 1)
playbackFrame.pack(fill = Tkinter.X, pady = 2)
# Create notebook pages
self.mainNotebook = Pmw.NoteBook(interior)
self.mainNotebook.pack(fill = Tkinter.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 = Tkinter.X)
# Resample
resampleFrame = Frame(
self.resamplePage, relief = Tkinter.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'] = Tkinter.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 = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
self.createButton(
frame, 'Resample', 'Face Forward',
'Compute HPR so resulting hpr curve faces along xyz tangent',
self.faceForward, side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
frame.pack(fill = Tkinter.X, expand = 0)
resampleFrame.pack(fill = Tkinter.X, expand = 0, pady = 2)
# Desample
desampleFrame = Frame(
self.resamplePage, relief = Tkinter.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'] = Tkinter.RIDGE
widget['postCallback'] = self.desampleCurve
desampleFrame.pack(fill = Tkinter.X, expand = 0, pady = 2)
## REFINE PAGE ##
refineFrame = Frame(self.refinePage, relief = Tkinter.SUNKEN,
borderwidth = 2)
label = Label(refineFrame, text = 'REFINE CURVE',
font=('MSSansSerif', 12, 'bold'))
label.pack(fill = Tkinter.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 = Tkinter.X)
## EXTEND PAGE ##
extendFrame = Frame(self.extendPage, relief = Tkinter.SUNKEN,
borderwidth = 2)
label = Label(extendFrame, text = 'EXTEND CURVE',
font=('MSSansSerif', 12, 'bold'))
label.pack(fill = Tkinter.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 = Tkinter.X)
## CROP PAGE ##
cropFrame = Frame(self.cropPage, relief = Tkinter.SUNKEN,
borderwidth = 2)
label = Label(cropFrame, text = 'CROP CURVE',
font=('MSSansSerif', 12, 'bold'))
label.pack(fill = Tkinter.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 = Tkinter.NONE)
cropFrame.pack(fill = Tkinter.X)
## DRAW PAGE ##
drawFrame = Frame(self.drawPage, relief = Tkinter.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 = Tkinter.X)
frame = Frame(sfFrame)
Label(frame, text = 'SHOW:').pack(side = Tkinter.LEFT, expand = 0)
widget = self.createCheckbutton(
frame, 'Style', 'Path',
'On: path is visible', self.setPathVis, 1,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
widget = self.createCheckbutton(
frame, 'Style', 'Knots',
'On: path knots are visible', self.setKnotVis, 1,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
widget = self.createCheckbutton(
frame, 'Style', 'CVs',
'On: path CVs are visible', self.setCvVis, 0,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
widget = self.createCheckbutton(
frame, 'Style', 'Hull',
'On: path hull is visible', self.setHullVis, 0,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
widget = self.createCheckbutton(
frame, 'Style', 'Trace',
'On: record is visible', self.setTraceVis, 0,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
widget = self.createCheckbutton(
frame, 'Style', 'Marker',
'On: playback marker is visible', self.setMarkerVis, 0,
side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
frame.pack(fill = Tkinter.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 = Tkinter.TOP)
widget.component('hull')['relief'] = Tkinter.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 = Tkinter.TOP)
widget.component('hull')['relief'] = Tkinter.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 = Tkinter.TOP)
widget.component('hull')['relief'] = Tkinter.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 = Tkinter.X)
## OPTIONS PAGE ##
optionsFrame = Frame(self.optionsPage, relief = Tkinter.SUNKEN,
borderwidth = 2)
label = Label(optionsFrame, text = 'RECORDING OPTIONS',
font=('MSSansSerif', 12, 'bold'))
label.pack(fill = Tkinter.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 = Tkinter.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 = Tkinter.W)
self.setKeyframeHook()
frame.pack(expand = 1, fill = Tkinter.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 = Tkinter.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 = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 0)
frame.pack(expand = 1, fill = Tkinter.X)
# Pack record frame
optionsFrame.pack(fill = Tkinter.X, pady = 2)
self.mainNotebook.setnaturalsize()
def pushUndo(self, fResetRedo = 1):
SEditor.pushUndo([self.nodePath])
def undoHook(self):
# 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):
SEditor.pushRedo([self.nodePath])
def redoHook(self):
# 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.id()
if nodePath.id() in self.playbackMarkerIds:
SEditor.select(self.playbackMarker)
elif nodePath.id() in self.tangentMarkerIds:
SEditor.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.getChildrenAsList()
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 SEditor.selected.last:
if SEditor.selected.last.id() == self.playbackMarker.id():
self.manipulandumId = self.playbackMarker.id()
elif SEditor.selected.last.id() == self.tangentMarker.id():
self.manipulandumId = self.tangentMarker.id()
def manipulateObjectCleanupHook(self):
# 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
SEditor.deselect(self.playbackMarker)
SEditor.deselect(self.tangentMarker)
# Remove tasks
taskMgr.remove(self.name + '-recordTask')
taskMgr.remove(self.name + '-playbackTask')
taskMgr.remove(self.name + '-curveEditTask')
self.mopathRecorderNode.removeChildren()
self.mopathRecorderNode.removeNode()
messenger.send('mPath_close')
messenger.send('SGE_Update Explorer',[render])
def createNewPointSet(self, curveName = None):
if curveName == None:
self.pointSetName = self.name + '-ps-' + `self.pointSetCount`
else:
self.pointSetName = curveName
# Update dictionary and record pointer to new point set
self.pointSet = self.pointSetDict[self.pointSetName] = []
# Update combo box
comboBox = self.getWidget('Mopath', 'Path:')
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, curveName = None):
# Get new point set based on newly created curve
self.createNewPointSet(curveName)
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, curveName=None):
# 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, curveName = curveName)
# 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():
# Reparent a Marker to target nodePath to show where the recording start
self.markingNode = self.nodePath.getParent().attachNewNode('MopthMarkerNode')
self.nodePath.copyTo(self.markingNode)
self.markingNode.wrtReparentTo(render)
# 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:
self.markingNode.removeNode() # Hide the marker in the end of recording
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 = SEditor.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
SEditor.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)
messenger.send('mPath_requestCurveList',[nodePath,self.name])
self.accept('curveListFor'+self.name, self.addCurvesFromNodepath)
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 # + '-' + `nodePath.id()`
if not dict.has_key(dictName):
# 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, curveName = None):
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(curveName)
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 `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 `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:
fromNodePath = entry.getFromNodePath()
hitPtDist = Vec3(entry.getSurfacePoint(fromNodePath))
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 = Tkinter.LEFT,
expand = 1, width = 12):
frame = Frame(parent)
variable = StringVar()
variable.set(value)
label = Label(frame, text = text)
label.pack(side = Tkinter.LEFT, fill = Tkinter.X)
self.bind(label, balloonHelp)
self.widgetDict[category + '-' + text + '-Label'] = label
entry = Entry(frame, width = width, relief = relief,
textvariable = variable)
entry.pack(side = Tkinter.LEFT, fill = Tkinter.X, expand = expand)
self.bind(entry, balloonHelp)
self.widgetDict[category + '-' + text] = entry
self.variableDict[category + '-' + text] = variable
if command:
entry.bind('<Return>', command)
frame.pack(side = side, fill = Tkinter.X, expand = expand)
return (frame, label, entry)
def createButton(self, parent, category, text, balloonHelp, command,
side = 'top', expand = 0, fill = Tkinter.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 = Tkinter.X, expand = 0):
bool = BooleanVar()
bool.set(initialState)
widget = Checkbutton(parent, text = text, anchor = Tkinter.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 = Tkinter.X, expand = 0):
widget = Radiobutton(parent, text = text, anchor = Tkinter.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 = apply(Floater, (parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
widget.pack(fill = Tkinter.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 = apply(AngleDial,(parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
widget.pack(fill = Tkinter.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 = Tkinter.TOP, fill = Tkinter.X, expand = 1, **kw):
kw['text'] = text
kw['min'] = min
kw['max'] = max
kw['resolution'] = resolution
#widget = apply(EntryScale, (parent,), kw)
widget = apply(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 = Tkinter.TOP, fill = Tkinter.X, expand = 1, **kw):
kw['text'] = text
kw['min'] = min
kw['max'] = max
kw['resolution'] = resolution
widget = apply(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 = apply(Vector2Entry, (parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
widget.pack(fill = Tkinter.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 = apply(Vector3Entry, (parent,), kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
widget.pack(fill = Tkinter.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 = apply(ColorEntry, (parent,) ,kw)
# Do this after the widget so command isn't called on creation
widget['command'] = command
widget.pack(fill = Tkinter.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 = Tkinter.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 = Tkinter.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 = Tkinter.LEFT, expand = 0, fill = Tkinter.X):
widget = Pmw.ComboBox(parent,
labelpos = Tkinter.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)
def toggleWidgetVis(self):
## In order to make sure everything is going on right way...
messenger.send('SEditor-ToggleWidgetVis')
SEditor.toggleWidgetVis()
def bindMotionPathToNode(self):
if self.curveCollection == None:
print '----Error: you need to select or create a curve first!'
return
self.accept('MP_checkName', self.bindMotionPath)
self.askName = namePathPanel(MopathRecorder.count)
return
def bindMotionPath(self,name=None,test=None):
print test
self.ignore('MP_checkName')
del self.askName
self.curveCollection.getCurve(0).setName(name)
comboBox = self.getWidget('Mopath', 'Path:')
oldName = comboBox.get()
self.pointSetDict[name] = self.pointSetDict[oldName]
del self.pointSetDict[oldName]
scrolledList = comboBox.component('scrolledlist')
listbox = scrolledList.component('listbox')
names = list(listbox.get(0,'end'))
num = names.index(oldName)
names.pop(num)
names.append(name)
scrolledList.setlist(names)
comboBox.selectitem(name)
messenger.send('mPath_bindPathToNode',[self.playbackNodePath, self.curveCollection])
return
def addCurvesFromNodepath(self,curveList):
'''addCurvesFromNodepath(self,curveList)
This function will take a curveCollection list as a input.
If the list is not None, it will put the vurve back into the curve list.
else, do nothing.
'''
print curveList
self.ignore('curveListFor'+self.name)
if curveList != None:
for collection in curveList:
self.curveCollection = collection
self.extractPointSetFromCurveCollection(curveName=self.curveCollection.getCurve(0).getName())
else:
pass
return
class namePathPanel(AppShell):
# Override class variables
appname = 'Name the Path'
frameWidth = 575
frameHeight = 200
usecommandarea = 0
usestatusarea = 0
index = 0
def __init__(self, count, parent = None, **kw):
INITOPT = Pmw.INITOPT
self.id = 'Name the Path'
self.appname = self.id
optiondefs = (
('title', self.appname, None),
)
self.defineoptions(kw, optiondefs)
# Initialize the superclass
AppShell.__init__(self)
self.parent.resizable(False,False)
# Execute option callbacks
self.initialiseoptions(namePathPanel)
def createInterface(self):
self.menuBar.destroy()
interior = self.interior()
mainFrame = Frame(interior)
dataFrame = Frame(mainFrame)
label = Label(dataFrame, text='This name will be used as a reference for this Path.',font=('MSSansSerif', 10))
label.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X)
dataFrame.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X, padx=5, pady=10)
dataFrame = Frame(mainFrame)
self.inputZone = Pmw.EntryField(dataFrame, labelpos='w', label_text = 'Name Selected Path: ',
entry_font=('MSSansSerif', 10),
label_font=('MSSansSerif', 10),
validate = None,
entry_width = 20)
self.inputZone.pack(side = Tkinter.LEFT, fill=Tkinter.X,expand=0)
self.button_ok = Button(dataFrame, text="OK", command=self.ok_press,width=10)
self.button_ok.pack(fill=Tkinter.X,expand=0,side=Tkinter.LEFT, padx = 3)
dataFrame.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X, padx=10, pady=10)
mainFrame.pack(expand = 1, fill = Tkinter.BOTH)
def onDestroy(self, event):
'''
If you have open any thing, please rewrite here!
'''
pass
def ok_press(self):
name = self.inputZone.getvalue()
messenger.send('MP_checkName',[name])
self.quit()
return