/share/nuke/ocionuke/cdl.py

http://github.com/imageworks/OpenColorIO · Python · 290 lines · 165 code · 62 blank · 63 comment · 37 complexity · 66719c0cba900216e65452a133ac48d4 MD5 · raw file

  1. # SPDX-License-Identifier: BSD-3-Clause
  2. # Copyright Contributors to the OpenColorIO Project.
  3. """Various utilities relating to the OCIOCDLTransform node
  4. """
  5. import nuke
  6. import PyOpenColorIO as OCIO
  7. import xml.etree.ElementTree as ET
  8. def _node_to_cdltransform(node):
  9. """From an OCIOCDLTransform node, returns a PyOpenColorIO
  10. CDLTransform object, which could be used to write XML
  11. """
  12. # Color_Knob.value returns single float if control is not
  13. # expanded, so use value(index=...) to always get three values
  14. slope = [node['slope'].value(x) for x in range(3)]
  15. offset = [node['offset'].value(x) for x in range(3)]
  16. power = [node['power'].value(x) for x in range(3)]
  17. sat = node['saturation'].value()
  18. cccid = node['cccid'].value()
  19. cdl = OCIO.CDLTransform()
  20. cdl.setSlope(slope)
  21. cdl.setOffset(offset)
  22. cdl.setPower(power)
  23. cdl.setSat(sat)
  24. cdl.setID(cccid)
  25. return cdl
  26. def _cdltransform_to_node(cdl, node):
  27. """From an XML string, populates the parameters on an
  28. OCIOCDLTransform node
  29. """
  30. # Treat "node" as a dictionary of knobs, as the "node" argument could be
  31. # a the return value of PythonPanel.knob(), as in SelectCCCIDPanel
  32. node['slope'].setValue(cdl.getSlope())
  33. node['offset'].setValue(cdl.getOffset())
  34. node['power'].setValue(cdl.getPower())
  35. node['saturation'].setValue(cdl.getSat())
  36. node['cccid'].setValue(cdl.getID())
  37. def _xml_colorcorrection_to_cdltransform(xml, cccposition):
  38. """From an XML string, return a CDLTransform object, if the cccid
  39. is absent from the XML the given cccposition will be used instead
  40. """
  41. ccxml = ET.tostring(xml)
  42. cdl = OCIO.CDLTransform()
  43. cdl.setXML(ccxml)
  44. if "cccid" not in xml.getchildren():
  45. cdl.setID(str(cccposition))
  46. return cdl
  47. def _xml_to_cdltransforms(xml):
  48. """Given some XML as a string, returns a list of CDLTransform
  49. objects for each ColorCorrection (returns a one-item list for a
  50. .cc file)
  51. """
  52. tree = ET.fromstring(xml)
  53. # Strip away xmlns
  54. for elem in tree.getiterator():
  55. if elem.tag.startswith("{"):
  56. elem.tag = elem.tag.partition("}")[2]
  57. filetype = tree.tag
  58. cccposition = 0
  59. if filetype == "ColorCorrection":
  60. return [_xml_colorcorrection_to_cdltransform(tree, cccposition)]
  61. elif filetype in ["ColorCorrectionCollection", "ColorDecisionList"]:
  62. allcdl = []
  63. for cc in tree.getchildren():
  64. if cc.tag == "ColorDecision":
  65. cc = cc.getchildren()[0] # TODO: something better here
  66. if cc.tag != "ColorCorrection": continue
  67. allcdl.append(_xml_colorcorrection_to_cdltransform(cc, cccposition))
  68. cccposition += 1
  69. return allcdl
  70. else:
  71. raise RuntimeError(
  72. "The supplied file did not have the correct root element, expected"
  73. " 'ColorCorrection' or 'ColorCorrectionCollection' or 'ColorDecisionList', got %r" % (filetype))
  74. def _cdltransforms_to_xml(allcc):
  75. """Given a list of CDLTransform objects, returns an XML string
  76. """
  77. root = ET.Element("ColorCorrectionCollection")
  78. root.attrib['xmlns'] = 'urn:ASC:CDL:v1.2'
  79. for cc in allcc:
  80. cur = ET.fromstring(cc.getXML())
  81. # Strip away xmlns
  82. for elem in cur.getiterator():
  83. if elem.tag.startswith("{"):
  84. elem.tag = elem.tag.partition("}")[2]
  85. root.append(cur)
  86. return ET.tostring(root)
  87. def SelectCCCIDPanel(*args, **kwargs):
  88. # Wrap class definition in a function, so nukescripts.PythonPanel
  89. # is only accessed when ``SelectCCCIDPanel()`` is called,
  90. # https://github.com/AcademySoftwareFoundation/OpenColorIO/issues/277
  91. import nukescripts
  92. class _SelectCCCIDPanel(nukescripts.PythonPanel):
  93. """Allows the user to select from a list of CDLTransform
  94. objects
  95. """
  96. def __init__(self, allcdl):
  97. super(_SelectCCCIDPanel, self).__init__()
  98. self.available = {}
  99. for cur in allcdl:
  100. self.available[cur.getID()] = cur
  101. self.addKnob(nuke.Enumeration_Knob("cccid", "cccid", self.available.keys()))
  102. self.addKnob(nuke.Text_Knob("divider"))
  103. self.addKnob(nuke.Color_Knob("slope"))
  104. self.addKnob(nuke.Color_Knob("offset"))
  105. self.addKnob(nuke.Color_Knob("power"))
  106. self.addKnob(nuke.Double_Knob("saturation"))
  107. def selected(self):
  108. return self.available[self.knobs()['cccid'].value()]
  109. def knobChanged(self, knob):
  110. """When the user selects a cccid, a grade-preview knobs are set.
  111. This method is triggered when any knob is changed, which has the
  112. useful side-effect of preventing changing the preview values, while
  113. keeping them selectable for copy-and-paste.
  114. """
  115. _cdltransform_to_node(self.selected(), self.knobs())
  116. return _SelectCCCIDPanel(*args, **kwargs)
  117. def export_as_cc(node = None, filename = None):
  118. """Export a OCIOCDLTransform node as a ColorCorrection XML file
  119. (.cc)
  120. If node is None, "nuke.thisNode()" will be used. If filename is
  121. not specified, the user will be prompted.
  122. """
  123. if node is None:
  124. node = nuke.thisNode()
  125. cdl = _node_to_cdltransform(node)
  126. if filename is None:
  127. ccfilename = nuke.getFilename("Color Correction filename", pattern = "*.cc")
  128. if ccfilename is None:
  129. # User clicked cancel
  130. return
  131. xml = cdl.getXML()
  132. print "Writing to %s - contents:\n%s" % (ccfilename, xml)
  133. open(ccfilename, "w").write(xml)
  134. def import_cc_from_xml(node = None, filename = None):
  135. """Import a ColorCorrection XML (.cc) into a OCIOCDLTransform node.
  136. If node is None, "nuke.thisNode()" will be used. If filename is
  137. not specified, the user will be prompted.
  138. """
  139. if node is None:
  140. node = nuke.thisNode()
  141. if filename is None:
  142. ccfilename = nuke.getFilename("Color Correction filename", pattern = "*.cc *.ccc *.cdl")
  143. if ccfilename is None:
  144. # User clicked cancel
  145. return
  146. xml = open(ccfilename).read()
  147. allcc = _xml_to_cdltransforms(xml)
  148. if len(allcc) == 1:
  149. _cdltransform_to_node(allcc[0], node)
  150. elif len(allcc) > 1:
  151. do_selectcccid = nuke.ask(
  152. "Selected a ColorCorrectionCollection, do you wish to select a ColorCorrection from this file?")
  153. if do_selectcccid:
  154. sel = SelectCCCIDPanel(allcc)
  155. okayed = sel.showModalDialog()
  156. if okayed:
  157. cc = sel.selected()
  158. _cdltransform_to_node(cc, node)
  159. else:
  160. return
  161. else:
  162. nuke.message("The supplied file (%r) contained no ColorCorrection's" % ccfilename)
  163. return
  164. def export_multiple_to_ccc(filename = None):
  165. """Exported all selected OCIOCDLTransform nodes to a
  166. ColorCorrectionCollection XML file (.ccc)
  167. """
  168. if filename is None:
  169. filename = nuke.getFilename("Color Correction XML file", pattern = "*.cc *.ccc")
  170. if filename is None:
  171. # User clicked cancel
  172. return
  173. allcc = []
  174. for node in nuke.selectedNodes("OCIOCDLTransform"):
  175. allcc.append(_node_to_cdltransform(node))
  176. xml = _cdltransforms_to_xml(allcc)
  177. print "Writing %r, contents:\n%s" % (filename, xml)
  178. open(filename, "w").write(xml)
  179. def import_multiple_from_ccc(filename = None):
  180. """Import a ColorCorrectionCollection file (.ccc) into multiple
  181. OCIOCDLTransform nodes. Also creates a single node for a .cc file
  182. """
  183. if filename is None:
  184. filename = nuke.getFilename("Color Correction XML file", pattern = "*.cc *.ccc *.cdl")
  185. if filename is None:
  186. # User clicked cancel
  187. return
  188. xml = open(filename).read()
  189. allcc = _xml_to_cdltransforms(xml)
  190. def _make_node(cdl):
  191. newnode = nuke.nodes.OCIOCDLTransform(inputs = nuke.selectedNodes()[:1])
  192. _cdltransform_to_node(cdl, newnode)
  193. newnode['label'].setValue("id: [value cccid]")
  194. if len(allcc) > 0:
  195. for cc in allcc:
  196. _make_node(cc)
  197. else:
  198. nuke.message("The supplied file (%r) contained no ColorCorrection's" % filename)
  199. def select_cccid_for_filetransform(node = None, fileknob = 'file', cccidknob = 'cccid'):
  200. """Select cccid button for the OCIOFileTransform node, also used
  201. in OCIOCDLTransform. Presents user with list of cccid's within the
  202. specified .ccc file, and sets the cccid knob to the selected ID.
  203. """
  204. if node is None:
  205. node = nuke.thisNode()
  206. filename = node[fileknob].value()
  207. try:
  208. xml = open(filename).read()
  209. except IOError, e:
  210. nuke.message("Error opening src file: %s" % e)
  211. raise
  212. allcc = _xml_to_cdltransforms(xml)
  213. if len(allcc) == 0:
  214. nuke.message("The file (%r) contains no ColorCorrection's")
  215. return
  216. sel = SelectCCCIDPanel(allcc)
  217. okayed = sel.showModalDialog()
  218. if okayed:
  219. node[cccidknob].setValue(sel.selected().getID())