No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchReplacePanel.py 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from xml.etree import ElementTree
  2. import nuke
  3. import nukescripts
  4. import os
  5. import re
  6. class SearchReplacePanel( nukescripts.PythonPanel ):
  7. def __init__( self, historyFile='~/.nuke/srhistory.xml', maxSteps=10 ):
  8. '''
  9. Search and Replace panel
  10. args:
  11. historyFile - file to manage recent search&replace actions
  12. maxSteps - amount of steps to keep in history file
  13. '''
  14. nukescripts.PythonPanel.__init__( self, 'Search and Replace', 'com.ohufx.SearchReplace')
  15. # VARS
  16. self.historyFile = os.path.expandvars( os.path.expanduser( historyFile ) )
  17. self.maxSteps = maxSteps
  18. self.delimiter = ' |>>| '
  19. # CREATE KNOBS
  20. self.nodesChoice = nuke.Enumeration_Knob( 'nodes', 'Source Nodes', ['all', 'selected'])
  21. self.nodesChoice.setTooltip( 'Chose to perform action on all nodes with file knobs or only selected ones' )
  22. self.history = nuke.Enumeration_Knob( 'history', 'Recent Searches', self.loadHistory() )
  23. self.history.setTooltip( 'Use the history to quicky access previous search&replace actions.\n By default the history file is stored as "~/.nuke/srhistory.xml" but this can be changed via the "historyFile" argument when creating the panel object. It is also possible to change the size of the history via the "maxSteps" argument to the panel object. Default is 10' )
  24. self.case = nuke.Boolean_Knob( 'case', 'case sensitive' )
  25. self.case.setFlag( nuke.STARTLINE )
  26. self.case.setValue( True )
  27. self.case.setTooltip( 'Set whether or not the search should be case sensitive' )
  28. self.searchStr = nuke.String_Knob('searchStr', 'Search for:')
  29. self.searchStr.setTooltip( 'The text to search for' )
  30. self.update = nuke.PyScript_Knob('update', 'Update')
  31. self.update.setTooltip( 'update the search result and preview. This is automaticaly performed and usually should only be required when the node selection has canged' )
  32. self.replaceStr = nuke.String_Knob('replaceStr', 'Replace with:')
  33. self.replaceStr.setTooltip( 'Text to replace the found text with' )
  34. self.replace = nuke.PyScript_Knob('replace', 'Replace')
  35. self.replace.setTooltip( 'Perform replace action. The preview will update afterwards and the action is added to the history' )
  36. self.info = nuke.Multiline_Eval_String_Knob('info', 'Found')
  37. self.info.setEnabled( False )
  38. self.info.setTooltip( 'See the search results and a preview of the replace action before it is performed' )
  39. # ADD KNOBS
  40. for k in ( self.nodesChoice, self.history, self.case, self.searchStr, self.update, self.replaceStr, self.replace, self.info):
  41. self.addKnob( k )
  42. self.matches = None
  43. def loadHistory( self ):
  44. '''load history file to update knob'''
  45. print("loading search&replace history")
  46. # GET EXISTING HISTORY
  47. if not os.path.isfile( self.historyFile ):
  48. return []
  49. # READ EXISTING FILE
  50. xmlTree = ElementTree.parse( self.historyFile )
  51. itemList = ['%s%s%s' % ( n.attrib['search'], self.delimiter, n.attrib['replace'] ) for n in xmlTree.findall( 'ITEM' )][-self.maxSteps:]
  52. itemList.reverse()
  53. itemList.insert( 0, '-- select --' )
  54. return itemList
  55. def updateHistory( self, sString, rString ):
  56. '''
  57. updates history file
  58. args:
  59. sString - search string to add to history
  60. rString - replace string to add to history
  61. TODO - IMPLEMENT MAX VALUE
  62. '''
  63. itemList = []
  64. # READ EXISTING FILE
  65. if os.path.isfile( self.historyFile ):
  66. xmlTree = ElementTree.parse( self.historyFile )
  67. for n in xmlTree.findall( 'ITEM' ):
  68. attr = n.attrib
  69. itemList.append( attr )
  70. # IGNORE ATTRIBUTES THAT ARE ALREADY IN HISTORY
  71. entryExists = False
  72. for i in itemList:
  73. if i['search'] == sString and i['replace']==rString:
  74. entryExists = True
  75. break
  76. # IF ATTRIBUTES DONT EXIST IN HISTORY, ADD THEM
  77. if not entryExists:
  78. # PREP DICTIONARY FOR XML DUMP
  79. srItem = dict(search=sString, replace=rString )
  80. itemList.append( srItem )
  81. # BUILD XML TREE
  82. root = ElementTree.Element( 'SearchReplacePanel')
  83. for i in itemList[-self.maxSteps:]:
  84. ElementTree.SubElement( root, 'ITEM', attrib=i )
  85. tree = ElementTree.ElementTree( root )
  86. # DUMP XML TREE
  87. print("WRITING TO:", self.historyFile)
  88. tree.write( self.historyFile )
  89. def search( self, searchstr, nodes ):
  90. """ Search in nodes with file knobs. """
  91. fileKnobNodes = [i for i in nodes if self.__NodeHasKnobWithName(i, 'file')]
  92. proxyKnobNodes = [i for i in nodes if self.__NodeHasKnobWithName(i, 'proxy')]
  93. if not fileKnobNodes and not proxyKnobNodes: raise ValueError("No file nodes selected")
  94. nodeMatches = []
  95. knobMatches = []
  96. for i in fileKnobNodes:
  97. if self.__findNode(searchstr, i['file'] ):
  98. nodeMatches.append( i )
  99. knobMatches.append( i['file'] )
  100. for i in proxyKnobNodes:
  101. if self.__findNode(searchstr, i['proxy'] ):
  102. nodeMatches.append( i )
  103. knobMatches.append( i['proxy'] )
  104. return nodeMatches, knobMatches
  105. def getSearchResults( self, nodes ):
  106. # PERFORM SEARCH AND UPDATE INFO KNOB
  107. nodeMatches, knobMatches = self.search( self.searchStr.value(), nodes )
  108. nodes = [n.name() for n in nodeMatches]
  109. infoStr1 = '%s node(s) found:\n\t%s' % ( len(nodes), ', '.join( nodes ) )
  110. infoStr2 = ''
  111. for k in knobMatches:
  112. newStr = nukescripts.replaceHashes( self.__doReplace( k ) )
  113. # CHECK IF PATH IS VALID FOR CURRENT FRAME
  114. curFrame = int( nuke.Root()['frame'].value() ) # there is a bug which prevents nuke.frame() to work properly inside of python panels (6.3v5)
  115. try:
  116. curPath = newStr % curFrame
  117. except:
  118. curPath = newStr
  119. exists = {True:' VALID PATH AT FRAME %s' % curFrame, False:' !!! PATH IS INVALID AT CURRENT FRAME (%s)!!!' % curFrame}[os.path.exists( curPath )]
  120. # BUILD INFO STRING
  121. infoStr2 += '%s.%s:\n\tbefore\t%s\n\tafter\t%s %s\n' % ( k.node().name(), k.name(), k.value(), newStr, exists )
  122. self.info.setValue( '\n'.join( [infoStr1, infoStr2] ) )
  123. return knobMatches
  124. def __NodeHasKnobWithName( self, node, name):
  125. try:
  126. node[name]
  127. except NameError:
  128. return False
  129. return True
  130. def __findNode( self, searchstr, knob ):
  131. v = knob.value()
  132. if not self.case.value():
  133. v = v.lower()
  134. searchstr = searchstr.lower()
  135. if v and searchstr and searchstr in v:
  136. return True
  137. def __doSearch( self ):
  138. # LAUNCH SEARCH
  139. srcNodes = { 'all': nuke.allNodes(), 'selected': nuke.selectedNodes() }
  140. self.matches = self.getSearchResults( srcNodes[self.nodesChoice.value()] )
  141. def __doReplace( self, knob ):
  142. # PERFORM REPLACE
  143. if self.case.value():
  144. # CASE SENSITIVE
  145. newStr = re.sub( self.searchStr.value(), self.replaceStr.value(), knob.value() )
  146. else:
  147. # IGNORE CASE
  148. newStr = knob.value()
  149. for m in re.findall( self.searchStr.value(), knob.value(), re.IGNORECASE ):
  150. newStr = re.sub( m, self.replaceStr.value(), newStr )
  151. return newStr
  152. def knobChanged( self, knob ):
  153. if knob in ( self.searchStr, self.replaceStr, self.update, self.nodesChoice, self.case ):
  154. # PERFORM SEARCH
  155. self.__doSearch()
  156. elif knob is self.replace and self.matches is not None:
  157. # PERFORM REPLACE AND UPDATE HISTORY
  158. print("replacing")
  159. for k in self.matches:
  160. k.setValue( self.__doReplace( k ) )
  161. self.updateHistory( self.searchStr.value(), self.replaceStr.value() )
  162. self.history.setValues( self.loadHistory() )
  163. self.history.setValue( 0 )
  164. self.__doSearch()
  165. elif knob is self.history:
  166. search, replace = knob.value().split( self.delimiter )
  167. self.searchStr.setValue( search )
  168. self.replaceStr.setValue( replace )
  169. self.__doSearch()