Source code for hiero.core.FnExportStructure

# Copyright (c) 2011 The Foundry Visionmongers Ltd.  All Rights Reserved.
import xml.etree.ElementTree as etree
from hiero.core.FnExporterBase import classBasename
from hiero.core import (IExportStructure,
                        IExportStructureElement,
                        TaskPresetBase,
                        TaskBase,
                        FolderTaskPreset,
                        taskRegistry,
                        log)
import os.path

[docs]class ExportStructure2 (IExportStructure): """ ExportStructure2 is the implementation of the datastructure used to represent the export tree, each node within the tree is represented by an ExportStructureElement. Although this matches how the export presets are viewed in the UI, when it comes running an export, or persisting the structure, it is flattened into a list of paths and task presets. """ def __init__(self): IExportStructure.__init__ (self) self._rootElement = ExportStructureElement("root", True) self._exportRootPath = "{projectroot}"
[docs] def rootElement ( self ): """Return the root element in this hierarchy. The root element is not included in the path generation""" return self._rootElement
[docs] def findElementsByPath(self, path): """ Find the elements matching path. Returns a list. """ return self._rootElement.findElementsByPath(path)
[docs] def exportRootPath ( self ): """Returns the exportRootPath, the root of the export into which the export structure is built""" return self._exportRootPath
[docs] def setExportRootPath ( self, rootPath ): """Set the exportRootPath, the root of the export into which the export structure is built""" self._exportRootPath = rootPath
def _fromXml (self, element): self._rootElement._fromXml(element.find("root")) self._exportRootPath = taskRegistry._loadPresetElement(element.find("exportPath")) def _toXml (self, parent): rootElement = etree.Element( "root", valuetype=classBasename(ExportStructureElement) ) self._rootElement._toXml(rootElement) taskRegistry._savePresetElement( "exportPath", self._exportRootPath, parent)
[docs] def restore ( self, sequence ): """Restore the hierarchy from a list of (path, preset) tuples""" self._rootElement = ExportStructureElement("root", True) for path, preset in sequence: parent = self.rootElement() child = None path.replace('\\', '/') child = self.rootElement().createChildFromPreset(path, preset)
def _traverse ( self, element ): """ Helper for flatten. Recursively traverse the structure, building a list of (path, preset) tuples for each element with no children. """ leafElements = [] for child in element.children(): if child.childCount() == 0: path = child.path() if not child.isLeaf(): path += '/' leafElements.append((path, child.preset())) else: leafElements += self._traverse(child) return leafElements
[docs] def flatten ( self ): """Return the hierarchy as a list of (path, preset) tuples""" flattened = self._traverse(self.rootElement()) return flattened
def __repr__ (self): return str(self.rootElement())
[docs]class ExportStructureElement (IExportStructureElement): """ExportStructureElement represents a node within the export structure""" def __init__ ( self, name, isFolder ): IExportStructureElement.__init__(self) if '/' in name: raise RuntimeError("Cannot use '/' in element name.") self._name = name self._parent = None self._children = [] if isFolder: self.setPreset( FolderTaskPreset("folder", {}) ) else: self.setPreset( TaskPresetBase(TaskBase, "empty") ) # Clients can register callbacks to be notified when path changes. self._callbacks = []
[docs] def preset ( self ): """Return the preset assigned to this Element. May be None""" return self._preset
[docs] def setPreset ( self, preset ): """Set the preset assigned to this Element. May be None""" assert (preset is None or isinstance(preset, TaskPresetBase)), "unexpected type %s" % type(preset) self._preset = preset
[docs] def setPresetType ( self, identifier ): """setPresetType(self, identifier) @param identifier: Unique identifier from the Task which is used to associate the preset type""" newPreset = None data = self._preset.properties() if self._preset else {} if identifier: presetType = taskRegistry.getPresetType(identifier) if presetType: newPreset = presetType(identifier, data) # If the preset type wasn't found in the registry, create a default one. # Not sure this is the correct behaviour. if not newPreset: newPreset = TaskPresetBase(TaskBase, "empty") newPreset._properties.update(data) self.setPreset(newPreset)
[docs] def path ( self ): """Return the path of this Element""" if self.parent() is not None: return os.path.join(self.parent().path(), self.name()).replace('\\', '/') return ""
[docs] def name ( self ): """Return the name of this Element""" return self._name
[docs] def setName ( self, name ): """ Set the name of this element. """ if '/' in name: raise RuntimeError("Cannot use '/' in element name.") oldPath = self.path() self._name = name self.pathChanged(oldPath)
[docs] def pathChanged(self, oldPath): """ Notify children and any observers that the element's path has changed. """ newPath = self.path() # Notify callbacks for callback in self._callbacks: callback(oldPath, newPath) # Notify children for child in self.children(): child.pathChanged(os.path.join(oldPath, child.name()).replace('\\', '/'))
[docs] def addPathChangedCallback( self, callback): """ Add a callback to be notified when the path of this element has changed, callbacks should take the arguments (oldPath, newPath). """ if callable(callback): if callback not in self._callbacks: self._callbacks.append(callback) else: raise TypeError("callback must be callable")
[docs] def isLeaf ( self ): """Returns True if node is flagged as leaf and may not accept children""" return not isinstance(self._preset, FolderTaskPreset)
[docs] def parent ( self ): """Return the parent element of this Element""" return self._parent
def _setParent ( self, parent ): """Set the parent element of this Element. Note: this is an internal method and should not be called directly. Elements should be added to a parent by calling parent.addChild() """ oldPath = self.path() # Get the path based on the old parent self._parent = parent self.pathChanged(oldPath) # Notify observers the path has changed
[docs] def childIndex ( self, element ): """Given a child element, identify and return the index of the child within the children array. Returns -1 if child not found. """ try: return self.children().index(element) except ValueError: return -1
[docs] def child ( self, index ): """Return a child by index""" return self._children[index]
[docs] def children ( self ): """Return a list of children""" return self._children
[docs] def childCount ( self ): """Return the number of children""" return len(self._children)
def __bool__(self): """ Implemented because otherwise the __len__ method is used for evaluation in boolean contexts. 'if element' should always succeed. """ return True def __len__ ( self ): return self.childCount() def __contains__ ( self, name ): for child in self._children: if child.name() == name: return True return False def __getitem__(self, index): return self.child(index) def __repr__ ( self ): strn = str(self.name()) + "\r" for child in self.children(): strn += "-" + str(child) strn += "\r" return strn
[docs] def findElementsByPath(self, path): """ Search recursively through the element tree finding elements which match path. Returns a list. """ if '/' in path: # Split the first part of the path and try to find a child matching than, # then call findElementsByPath() on it with the remainder of the path childname, remaining = path.split('/', 1) for child in self.children(): if child.name() == childname: return child.findElementsByPath(remaining) return [] else: # Return children matching the name return [ c for c in self.children() if c.name() == path ]
[docs] def addChild ( self, child ): """Add a child to this Element""" assert child.parent() is None, "Cannot add a child which already has a parent" child._setParent( self ) self._children.append(child)
def _createChildren(self, path, isFolder, preset=None): """ Create and add a child element. If path has / separators, recursively adds children. Returns the final created element. """ # Split the path into elements elements = [ elem for elem in path.split('/') if elem ] if not elements: return None # Loop over the elements, building the folder hierarchy as needed parent = self while elements: childName = elements.pop(0) childIsFolder = bool(isFolder or elements) child = None # If creating a folder, look for an existing one with this name. Duplicate # task names are allowed if childIsFolder: child = next( (c for c in parent.children() if c.name() == childName), None ) if not child: child = ExportStructureElement(childName, childIsFolder) parent.addChild(child) parent = child # If an existing preset was given, set it on the child if preset: child.setPreset(preset) return parent
[docs] def createChildFolder(self, path): return self._createChildren(path, True)
[docs] def createChildTask(self, path): return self._createChildren(path, False)
[docs] def createChildFromPreset(self, path, preset): """ Create a child element from a path and existing preset """ return self._createChildren(path, False, preset)
[docs] def removeChild ( self, child ): """Remove a child from this Element""" assert child.parent() is self assert child in self._children child._setParent(None) self._children.remove(child)
[docs] def clearChildren ( self ): """Clear all the children""" self._children = []
[docs] def toXml(self): """Serialize Element and children to XML""" # Create root node root = etree.Element("ExportStructureElement") # call internal function to populate root and convert the result to a unicode string. xml = etree.tostring(self._toXml(root), encoding='unicode') return xml
def _toXml(self, parent): # Add properties as Elements to parent XmlElement taskRegistry._savePresetElement( "name", self.name(), parent) # This call will serialize the whole preset object to xml taskRegistry._savePresetElement( "preset", self.preset(), parent) # Create 'children' XmlElement as container for child XmlElements children = etree.Element( "children", valuetype=classBasename(tuple) ) parent.append(children) # For each child, recurse for child in self._children: childElement = etree.Element("ExportStructureElement") children.append(childElement) child._toXml(childElement) return parent
[docs] def fromXml (self, xml): """Build Child Elements from XML data""" # Parse XML try: log.debug( "fromXml(%s)" % str(xml) ) root = etree.XML(xml) self._fromXml(root) except: log.exception( "Failed to parse Xml for ExportStructureElement" )
def _fromXml (self, element): # Restore name and TaskPresetObject from XML data name = taskRegistry._loadPresetElement(element.find("name")) preset = taskRegistry._loadPresetElement(element.find("preset")) # User properties to create child child = self.createChildFromPreset(name, preset) # for each child create grand child from xml for grandChildElement in element.find("children"): child._fromXml(grandChildElement)