1919 lines
77 KiB
Python
1919 lines
77 KiB
Python
|
""" 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(
|
||
|
'<Return>',
|
||
|
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('<Return>', 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)
|