#----------------------------------------------------------------------------- # ERC builder 8 # Jan 17, 2006 # Copyright (c) 2006, David G. Drumright [ockham] # adding NOFIG # adding PZ2 #----------------------------------------------------------------------------- import poser scene = poser.Scene() #----------------------------------------------------------------------------- # Check the figure first. TheFig=None try: TheFig=scene.CurrentFigure() TheFigName=TheFig.Name() except: pass if not TheFig: raise 'Must select a figure!' # Form a list of known and permissible 'left sides' for parameter channels. PossParms=[ 'valueParm', 'targetGeom', 'translateX', 'translateY','translateZ', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX','scaleY', 'scaleZ', 'propagatingScale','propagatingScaleX','propagatingScaleY', 'propagatingScaleZ' 'twistX', 'twistY', 'twistZ', 'jointX', 'jointY', 'jointZ', 'xOffsetB', 'yOffsetB', 'zOffsetB', 'xOffsetA', 'yOffsetA', 'zOffsetA', 'smoothScaleX', 'smoothScaleY', 'smoothScaleZ', 'taperX', 'taperY', 'taperZ', 'shaderNodeParm','controlProp'] # The last one belongs to those CenterOfMass things, and is here only so we # can use it to exclude parms.... # Paragraph for a standard ValueParm, set up for convenient writing. vp1='\t\tvalueParm %s\n' # Put in name vp2='\t\t\t{\n\t\t\tname %s\n' # Put in same name vp3='\t\t\tinitValue 0\n\t\t\thidden 0\n\t\t\tforceLimits 4\n\t\t\tmin %f\n\t\t\tmax %f\n' # %(min,max) vp4='\t\t\ttrackingScale 1\n\t\t\tkeys\n\t\t\t\t{\n\t\t\t\tstatic 0\n\t\t\t\tk 0 0\n\t\t\t\t}\n\t\t\tinterpStyleLocked 0\n\t\t\t}\n' # no vars in last part PZ2FileHeader = "{\n\nversion\n\t{\n\tnumber 3.0\n\t}\n\nthighLength 0\n\n" PZ2FileFooter = "\n\nfigure\n\t{\n\t}\n}" TempName='erctemp.dat' # Get rid of previous if any. try: os.remove(TempName) except: pass # Typecode enum ParmByCode = { 1: 'rotateX', 2: 'rotateY', 3: 'rotateZ', 4: 'translateX', 5: 'translateY', 6: 'translateZ', 7: 'scaleX', 8: 'scaleY', 9: 'scaleZ', 10: 'scale', 42: 'targetGeom', 65: 'valueParm', 102: 'shaderNodeParm' } #----------------------------------------------------------------------------- # Start tk after checking from Tkinter import * import tkFileDialog import sys import os import pickle root=Tk() # for intvar #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Next section is TK #------------------------------------------------------------ class App: def __init__(self, master, textMessage): self.master = master # Init these so we can detect when empty self.SrcPartName=None self.SrcParmName=None self.DstPartName=None self.DstParmName=None self.ValueParms=[] self.NOFIG=IntVar() self.NOFIG.set(0) master.title('ERC builder') self.StatusEntry = Entry(self.master,width=50) self.StatusEntry.grid(row=0,column=0,columnspan=2) self.StatusEntry.insert(0,'Select actors, with a DOUBLE-CLICK.') self.SrcFrame=Frame(self.master,borderwidth=2,relief=RIDGE) self.DstFrame=Frame(self.master,borderwidth=2,relief=RIDGE) self.SrcFrame.grid(row=1,column=0,sticky=N+S+E,pady=2) self.DstFrame.grid(row=1,column=1,sticky=N+S+E,pady=2) self.NumFrame=Frame(self.master,borderwidth=2,relief=RIDGE) self.NumFrame.grid(row=2,column=0,sticky=W+E,pady=2,columnspan=2) self.NewFrame=Frame(self.master,borderwidth=2,relief=RIDGE) self.NewFrame.grid(row=3,column=0,sticky=W+E,pady=2,columnspan=2) self.ButFrame=Frame(self.master,borderwidth=2,relief=RIDGE) self.ButFrame.grid(row=4,column=0,sticky=W+E,pady=2,columnspan=2) #------- Label(self.SrcFrame,text='Master actor (DOUBLE-CLICK)').grid(row=0, column=0,sticky=N,columnspan=2) self.SrcPartScroll=Scrollbar(self.SrcFrame, orient=VERTICAL) self.SrcPartScroll.grid(row=1, column=0,sticky=N+S+E) self.SrcPartList=Listbox(self.SrcFrame, height=10, selectmode=SINGLE,yscrollcommand=self.SrcPartScroll.set,exportselection=0) self.SrcPartList.grid( row=1, column=1) self.SrcPartScroll['command']=self.SrcPartList.yview Label(self.SrcFrame,text='Master parm').grid(row=2, column=1,sticky=N) self.SrcParmScroll=Scrollbar(self.SrcFrame, orient=VERTICAL) self.SrcParmScroll.grid(row=3, column=0,sticky=N+S+E) self.SrcParmList=Listbox(self.SrcFrame, height=10, selectmode=SINGLE,yscrollcommand=self.SrcParmScroll.set,exportselection=0) self.SrcParmList.grid( row=3, column=1) self.SrcParmScroll['command']=self.SrcParmList.yview # Use Start and End here to mean Start and End of motion range, not startframe as usual. Label(self.SrcFrame,text='Start').grid(row=4,column=0) self.SrcStartEntry=Entry(self.SrcFrame,width=8) self.SrcStartEntry.grid(row=4,column=1,sticky=W,pady=3) Label(self.SrcFrame,text='End').grid(row=5,column=0,pady=3) self.SrcEndEntry=Entry(self.SrcFrame,width=8) self.SrcEndEntry.grid(row=5,column=1,sticky=W,pady=3) #------- Label(self.DstFrame,text='Slave actor (DOUBLE-CLICK)').grid(row=0, column=0,sticky=N,columnspan=2) self.DstPartScroll=Scrollbar(self.DstFrame, orient=VERTICAL) self.DstPartScroll.grid(row=1, column=0,sticky=N+S+E) self.DstPartList=Listbox(self.DstFrame, height=10, selectmode=SINGLE,yscrollcommand=self.DstPartScroll.set,exportselection=0) self.DstPartList.grid( row=1, column=1) self.DstPartScroll['command']=self.DstPartList.yview Label(self.DstFrame,text='Slave parm').grid(row=2, column=1,sticky=N) self.DstParmScroll=Scrollbar(self.DstFrame, orient=VERTICAL) self.DstParmScroll.grid(row=3, column=0,sticky=N+S+E) self.DstParmList=Listbox(self.DstFrame, height=10, selectmode=SINGLE,yscrollcommand=self.DstParmScroll.set,exportselection=0) self.DstParmList.grid( row=3, column=1) self.DstParmScroll['command']=self.DstParmList.yview # Use Start and End here to mean Start and End of motion range, not startframe as usual. Label(self.DstFrame,text='Start').grid(row=4,column=0) self.DstStartEntry=Entry(self.DstFrame,width=8) self.DstStartEntry.grid(row=4,column=1,sticky=W,pady=3) Label(self.DstFrame,text='End').grid(row=5,column=0,pady=3) self.DstEndEntry=Entry(self.DstFrame,width=8) self.DstEndEntry.grid(row=5,column=1,sticky=W,pady=3) #------- Label(self.NumFrame,text='Control Ratio').grid(row=0,column=0,pady=3) self.MultEntry = Entry(self.NumFrame,width=8) self.MultEntry.grid(row=0,column=1,sticky=W,pady=3) # Leave this blank so the precedence will work. Label(self.NumFrame,text='NO_FIG').grid(row=1,column=0,pady=3) self.NOFIGcheck=Checkbutton(self.NumFrame,var=self.NOFIG) self.NOFIGcheck.grid(row=1,column=1) #------- # Fill the lists. self.FillPartLists() #------- # The value section Label(self.NewFrame,text='Name').grid(row=0,column=0) self.entryName = Entry(self.NewFrame, width=20) self.entryName.grid(row=0, column=1) Label(self.NewFrame,text='Min').grid(row=0,column=2) self.entryMin = Entry(self.NewFrame, width=5) self.entryMin.grid(row=0, column=3) Label(self.NewFrame,text='Max').grid(row=0,column=4) self.entryMax = Entry(self.NewFrame, width=5) self.entryMax.grid(row=0, column=5) self.buttonNew = Button(self.NewFrame, text='New', command=self.HandleNew) self.buttonNew.grid(row=0, column=6,pady=2) # Main buttons self.buttonTest = Button(self.ButFrame, text='Test', command=self.HandleTest) self.buttonTest.grid(row=0, column=0,padx=10,pady=2) self.buttonHold = Button(self.ButFrame, text='Confirm', command=self.HandleConfirm) self.buttonHold.grid(row=0, column=1,padx=10,pady=2) self.buttonCR2 = Button(self.ButFrame, text='Build CR2', command=self.HandleCR2) self.buttonCR2.grid(row=0, column=2,padx=10,pady=2) self.buttonPZ2 = Button(self.ButFrame, text='Build PZ2', command=self.HandlePZ2) self.buttonPZ2.grid(row=0, column=3,padx=10,pady=2) # List binding to get the parms of the Dst. self.DstPartList.bind('',self.FillDstParmList) self.SrcPartList.bind('',self.FillSrcParmList) self.master.protocol('WM_DELETE_WINDOW', self.HandleCancel) # - - - - - - - - - - - - - - - - - - def HandleNew(self): self.GetParms() if not self.SrcPartName: self.ShowStatus('Must select a master actor') return m=self.entryMax.get() if m: max=float(m) else: max=100.0 m=self.entryMin.get() if m: min=float(m) else: min=0.0 ValueName='' # Check the entry box. If empty, use random name. n = self.entryName.get() if n: ValueName=n else: # Assign ValueNN Existing=self.SrcPartList.get(0,END) for i in range(100): ValueName='Value %d' % i if ValueName not in Existing: break self.SrcParmList.insert(0,ValueName) self.SrcParmList.selection_set(0) # Force it onto the selected part. SrcPart=TheFig.Actor(self.SrcPartName) SrcPart.CreateValueParameter(ValueName) self.ValueParms.append( (self.SrcPartName,ValueName,min,max) ) # So we can add the paragraph to the CR2 self.ShowStatus('Now fit slaves to this new parm.') # That's all we need to do here. return # - - - - - - - - - - - - - - - - - - def FillPartLists(self): self.SrcPartList.delete(0,END) # clear the visible self.DstPartList.delete(0,END) # clear the visible for OneActor in TheFig.Actors(): # Put actor first, then fig, so we can split at the left parenthesis. if string.count(OneActor.Name(),'Goal'): continue self.SrcPartList.insert(END,OneActor.Name()) self.DstPartList.insert(END,OneActor.Name()) # - - - - - - - - - - - - - - - - - - def FillSrcParmList(self,event): # Called by double-click on SrcPart. First get the # Actor, then its parms. Sels=event.widget.curselection() self.SrcPartName=event.widget.get(int(Sels[0])) SrcPart=TheFig.Actor(self.SrcPartName) # Clear then refill the Parm list self.SrcParmList.delete(0,END) # clear the visible for OneParm in SrcPart.Parameters(): if OneParm.Hidden(): continue self.SrcParmList.insert(END,OneParm.Name()) # - - - - - - - - - - - - - - - - - - def FillDstParmList(self,event): # Called by double-click on DstPart. First get the # Actor, then its parms. Sels=event.widget.curselection() self.DstPartName=event.widget.get(int(Sels[0])) DstPart=TheFig.Actor(self.DstPartName) # Clear then refill the Parm list self.DstParmList.delete(0,END) # clear the visible for OneParm in DstPart.Parameters(): if OneParm.Hidden(): continue self.DstParmList.insert(END,OneParm.Name()) # - - - - - - - - - - - - - - - - - - def HandleCR2(self): # Pop up file box to get name. BasePathUncut=poser.AppLocation() BasePath=BasePathUncut[:(-len('poser.exe'))] MyPath=os.path.normpath(BasePath+'/runtime/libraries/character/*.*') fn=tkFileDialog.askopenfilename(initialfile=MyPath, defaultextension=[('Fig or prop', '*.CR2 *.cr2 *.PP2 *.pp2')] ) if not fn: self.ShowStatus('No file selected. Try again.') return 0 # Open the file InFileName=os.path.abspath(fn) # Form outname: if not InFileName: self.ShowStatus('Must pick file first') return Front,Ext=os.path.splitext(InFileName) OutFileName=Front+'_ERC'+Ext # Read the temp file. fpDat=None try: fpDat=open(TempName,'rt') except: pass if fpDat: TempMain=pickle.load(fpDat) fpDat.close() if not TempMain: self.ShowStatus('No data stored. Do the "Confirm" stage.') return self.ShowStatus('Working......') self.CR2Action(InFileName,OutFileName,TempMain) self.ShowStatus('Done with %s' % OutFileName) # - - - - - - - - - - - - - - - - - - def HandlePZ2(self): # Pop up file box to get name. BasePathUncut=poser.AppLocation() BasePath=BasePathUncut[:(-len('poser.exe'))] MyPath=os.path.normpath(BasePath+'/runtime/libraries/pose/*.pz2') fn=tkFileDialog.asksaveasfilename(initialfile=MyPath, defaultextension=[('Pose file', '*.PZ2 *.pz2')] ) if not fn: self.ShowStatus('No file selected. Try again.') return 0 # Open the file OutFileName=os.path.abspath(fn) if not OutFileName: self.ShowStatus('Must pick file first') return # Read the temp file. fpDat=None try: fpDat=open(TempName,'rt') except: pass if fpDat: TempMain=pickle.load(fpDat) fpDat.close() if not TempMain: self.ShowStatus('No data stored. Do the "Confirm" stage.') return self.PZ2Action(OutFileName,TempMain) self.ShowStatus('Done with %s' % OutFileName) # - - - - - - - - - - - - - - - - - - def GetParms(self): # repeated code # Get the numbers first: self.Multiplier=1.0 SrcStartString = self.SrcStartEntry.get() SrcEndString = self.SrcEndEntry.get() DstStartString = self.DstStartEntry.get() DstEndString = self.DstEndEntry.get() MultString = self.MultEntry.get() SrcRange = 0.0 DstRange = 0.0 # If the Ratio is given, it takes precedence. # If not, we check start and end. Assume 0 for unfilled entries. if MultString: self.Multiplier = float(MultString) else: if SrcStartString: SrcStart = float(SrcStartString) else: SrcStart=0.0 if SrcEndString: SrcEnd = float(SrcEndString) else: SrcEnd=0.0 if SrcEnd==SrcStart: self.ShowStatus('Must give either Ratio or different Start and End.') return 0 else: SrcRange=SrcEnd-SrcStart if DstStartString: DstStart = float(DstStartString) else: DstStart=0.0 if DstEndString: DstEnd = float(DstEndString) else: DstEnd=0.0 if DstEnd==DstStart: self.ShowStatus('Must give either Ratio or different Start and End.') return 0 else: DstRange=DstEnd-DstStart if (SrcRange != 0.0) and (DstRange != 0.0): self.Multiplier = DstRange / SrcRange # Get the selections. Note that we already have both the Actors; # the Src by preselection and the Dst by doubleclick. # We only need to get the parms here. SrcParmSels=self.SrcParmList.curselection() # single if not SrcParmSels: return 0 self.SrcParmName=self.SrcParmList.get(int(SrcParmSels[0])) # The DstPart has already been selected by the doubleclick. DstParmSels=self.DstParmList.curselection() if not DstParmSels: self.ShowStatus('No follower parms selected!') return 0 self.DstParmName = self.DstParmList.get(int(DstParmSels[0]) ) return 1 # - - - - - - - - - - - - - - - - - - def HandleTest(self): # This has to work only on the setting shown in listboxes. Otherwise # it just gets too complicated! # Check the max and min of master. Move toward whichever is farther. # Move all the slaves by their multiples. Then go back to original. # Try 4 back/forth cycles. Might need delay. if not self.GetParms(): return # Get current settings for Wiggle in range(4): # Do 4 times sP=TheFig.Actor(self.SrcPartName).Parameter(self.SrcParmName) dP=TheFig.Actor(self.DstPartName).Parameter(self.DstParmName) Delta=self.Multiplier Orig=sP.Value() Max=sP.MaxValue() Min=sP.MinValue() Maxdif=abs(Orig-Max) Mindif=abs(Orig-Min) if Maxdif > Mindif: # Use min and max to determine likely direction W = .2 * Maxdif # More move toward Max else: W = -.2 * Mindif # More move toward Min sP.SetValue(sP.Value() + W ) dP.SetValue(dP.Value() + W * Delta) scene.DrawAll() for t in range(30000): x=1 # Return to orig sP.SetValue(sP.Value() - W ) dP.SetValue(dP.Value() - W * Delta) scene.DrawAll() for t in range(30000): x=1 # See if this gives enough delay.... # - - - - - - - - - - - - - - - - - - def HandleConfirm(self): # Get the numbers and parts: if not self.GetParms(): return # All we do here is store the names and mult in temp file so you can do several # connections before forming the actual CR2. # Do an indirect append to file: open the file, read what's there into temp array, add new # stuff to temp array, then put back. fpDat=None try: fpDat=open(TempName,'rt') except: pass if fpDat: TempMain=pickle.load(fpDat) fpDat.close() else: TempMain=[] # List Each item is tuple: (SrcPart,SrcParm, DstPart, DstParm, Mult) # Rewrite the contents using internal names, also for CR2 convenience. # De-colonize each name. SrcPart=TheFig.Actor(self.SrcPartName) SrcPartIntName=SrcPart.InternalName() Parts=string.split(SrcPartIntName,':',1) if Parts and (len(Parts)>1): SrcPartIntName=Parts[0] SrcParm=SrcPart.Parameter(self.SrcParmName) SrcParmIntName=SrcParm.InternalName() SrcParmTypeCode=SrcParm.TypeCode() DstPart=TheFig.Actor(self.DstPartName) DstPartIntName=DstPart.InternalName() Parts=string.split(DstPartIntName,':',1) if Parts and (len(Parts)>1): DstPartIntName=Parts[0] DstParm=DstPart.Parameter(self.DstParmName) DstParmIntName=DstParm.InternalName() DstParmTypeCode=DstParm.TypeCode() TempMain.append((SrcPartIntName,SrcParmIntName,SrcParmTypeCode,DstPartIntName,DstParmIntName,DstParmTypeCode,self.Multiplier) ) fpDat=open(TempName,'wt') # open new pickle.dump(TempMain,fpDat) # re-write list, either new or expanded. fpDat.close() # - - - - - - - - - - - - - - - - - - def PZ2Action(self,OutName,TempData): # Use TempMain to write the valueOpDelta paragraphs. fpOut=open(OutName,'wt') if self.NOFIG.get(): FigName='NO_FIG' else: FigName=TheFigName # Write the usual PZ2 header fpOut.write(PZ2FileHeader) # Here we don't have any source file to modify. We just write # a standard PZ2, using the specialized 'front names'. Skip # all NEW value parms, because a PZ2 can't add those. # For now just write Actor and Parm as they come in from TempData. # If Poser doesn't like this, will have to sort the data so we write # all parms for each actor together. I think Poser can take separate items, though. for (sA,sP,sT, dA,dP,dT, Delta) in TempData: # Skip if source is a NEW valueparm IsOurVP=0 for (vA,vP,mn,mx) in self.ValueParms: if (vA == sA) and (vP == sP): IsOurVP=1 break if IsOurVP: continue fpOut.write('actor %s\n' % dA) # Start dest actor fpOut.write('\t{\n\tchannels\n\t\t{\n') # Start channels FrontName = ParmByCode[dT] fpOut.write('\t\t%s %s\n' % (FrontName,dP) ) # Run the special paragraph. fpOut.write('{\n\t\tvalueOpDeltaAdd\n') fpOut.write('\t\t\t%s\n' % FigName) fpOut.write('\t\t\t%s\n' % sA) fpOut.write('\t\t\t%s\n' % sP) fpOut.write('\t\tdeltaAddDelta %f\n\t\t\t}\n}\n' % Delta) fpOut.write('}\n') # End actor fpOut.write(PZ2FileFooter) # Finish off fpOut.close() # Get rid of the temp file os.remove(TempName) # - - - - - - - - - - - - - - - - - - def CR2Action(self,InName,OutName,TempData): # Use TempMain to write the valueOpDelta paragraphs. fpIn=open(InName,'rt') fpOut=open(OutName,'wt') dAname=None dPname=None WaitingForValue=0 if self.NOFIG.get(): FigName='NO_FIG' elif string.count(TheFigName,'Figure'): FigName='Figure 1' # DEGUG see if this helps else: FigName=TheFigName while 1: st=fpIn.readline() if not st: break # DEGUG Rewrite the line in all cases, but without its number. subParts=string.split(st,':',1) # This leaves the leading spaces if subParts and len(subParts): num=None sp=subParts[0] nump=subParts[0] # colon could introduce a path, so we just deal with single number. try: num=int(nump) except: pass if num: fpOut.write(subParts[0]) fpOut.write('\n') else: fpOut.write(st) stX=string.strip(st) Parts=string.split(stX,' ',1) # The 1 insures that second part includes spaces if len(Parts)>1: P0=Parts[0] P1=Parts[1] if string.count(P1,':'): # Take off the :3 if present subParts=string.split(P1,':',1) P1=subParts[0] if (P0=='actor') or (P0=='prop'): dAname=P1 # assign actor name dPname=None # freshen parm name if P0 in PossParms: dPname=P1 # assign parm name if (P0=='interpStyleLocked'): # Write a valueOpDeltaAdd paragraph # Look through TempData for an item with same DstPart and DstParm. for (sA,sP,sT, dA,dP,dT, Delta) in TempData: if (dAname == dA) and (dPname == dP): # Run the special paragraph. fpOut.write('\t\t\tvalueOpDeltaAdd\n') fpOut.write('\t\t\t\t%s\n' % FigName) # Constant fpOut.write('\t\t\t\t%s\n' % sA) fpOut.write('\t\t\t\t%s\n' % sP) fpOut.write('\t\t\tdeltaAddDelta %f\n' % Delta) break if stX=='channels': # This is the cue for writing valueparm WaitingForValue=1 if (stX=='{'): if WaitingForValue: if (len(self.ValueParms)>0): WaitingForValue=0 # Reset whether we have any vp's or not. # After writing the bracket we just found, it's time to fill in valueparms if any # so they will be first in line under the BODY or whatever. for (vAname,vPname,max,min) in self.ValueParms: if string.upper(vAname)==string.upper(dAname): fpOut.write(vp1 % vPname) fpOut.write(vp2 % vPname) fpOut.write(vp3 % (max,min)) fpOut.write(vp4) # All reading done. fpIn.close() fpOut.close() # Get rid of the temp file os.remove(TempName) # - - - - - - - - - - - - - - - - - - def ShowStatus(self,S): # Just saving repetition self.StatusEntry.delete(0,END) self.StatusEntry.insert(0,S) self.StatusEntry.update_idletasks() # - - - - - - - - - - - - - - - - - - def Update(self): if scene: scene.ProcessSomeEvents() root.lift() root.after(100, self.Update) # - - - - - - - - - - - - - - - - - - def HandleCancel(self): self.master.destroy() #------------------------------------------------------------ # Activate the loop #------------------------------------------------------------ #root = Tk() app = App(root, '') root.mainloop() #------------------------------------------------------------ # End TK loop. #-----------------------------------------------------------------------------