def addChild(self, branch: TreeBase, index=None, force=False, emitDelta=None) -> TreeBase: """check if a delta is necessary""" #print("addChild") emitDelta = emitDelta if emitDelta is not None else self.trackingDeltas #print("emitStackDelta", emitStackDelta, self.trackingDeltas) if emitDelta: # check if tree is "known" to this delta tracking scope try: relPath = branch.relAddress(fromBranch=self.deltaTrackingRoot) print("existing branch delta") structDelta = TreeStructureDelta( branchRef=TreeReference(branch, relParent=self.deltaTrackingRoot), oldParentRef=TreeReference( branch.parent, relParent=self.deltaTrackingRoot), parentRef=TreeReference( branch=self, relParent=self.deltaTrackingRoot, ), oldIndex=branch.index(), newIndex=index or -1, eventCode=self.StructureEvents.branchAdded) self.emitStackDelta(structDelta) except LookupError: print("new branch delta") # branch not known to delta root, make creation delta createDelta = TreeCreationDelta( TreeReference(branch, relParent=None, mode=TreeReference.Mode.Uid), parentRef=TreeReference(self, relParent=self.deltaTrackingRoot), name=branch.name, value=branch.value, index=index or -1, treeUid=branch.uid, treeCls=Tree) self.emitStackDelta(createDelta) # print("readonly", self.readOnly) if self.readOnly: print("read only returning") return None # check for read only if self.readOnly: raise PermissionError( f"tree {self.address()} is read only, cannot set value") return super(Tree, self).addChild(branch, index=index)
def __init__(self, name="transform", uid=""): """TREES ALL THE WAY DOWN""" super(Transformation, self).__init__(uid) self.name = name # from tree.main import Tree self.settings = TreeBase(name + "Settings")
def __copy__(self): from tree.core import TreeBase newRef = TreeReference(TreeBase("temp"), relParent=None, mode=self.mode) newRef.uid = self.uid newRef.address = self.address return newRef
def __init__(self, branch: TreeBase = None, relParent=None, mode=Mode.Uid): self.mode = mode self.uid = branch.uid relParent = relParent or branch.root try: self.address = branch.relAddress(fromBranch=relParent) except: self.address = []
def __get__(self, instance: TreeBase, owner): """look up property on given tree """ if self.inherited: if self.breakTags: result = instance.getInherited(self.key, self.breakTags, returnBranch=self.returnBranch, default=FailToFind) else: result = instance.getInherited(self.key, returnBranch=self.returnBranch, default=FailToFind) else: result = instance.getProperty(self.key, default=FailToFind) if result is FailToFind: result = self._evalDefault(instance, self.key) # optionally set the value on this branch to the default result if self.defaultSetsValue: instance.setProperty(self.key, result) return result
class Tree(TreeBase): """implement more complex systems beyond basic structure""" # system for finding required trees from a global scope globalTree = TreeBase("globalRoots") #globalTree.default = lambda branch: {} # each namespace a dict # import reference modes for TreeReference RefMode = TreeReference.Mode # optionally set up a parametre object for UI widgets in this tree uiParams = TreeBase.TreePropertyDescriptor( UI_PROPERTY_KEY, inherited=False, desc="Optional parametre object to inform UI elements generated from this branch" ) @classmethod def defaultBranchCls(cls): return Tree @classmethod def proxyForBranch(cls, branch:Tree, mode=RefMode.Uid): """return a TreeProxy for the given branch""" return TreeProxy.getProxy(branch, shared=True, refMode=mode) def __init__(self, name: str = None, val: T = None, default=None, treeUid=None, properties: dict = None ): # for deltas signature: branch, delta, deltaTracker self.deltasChanged = Signal() super(Tree, self).__init__(name=name, val=val, default=default, treeUid=treeUid, properties=properties) @classmethod def addGlobalRoot(cls, branch:TreeBase, namespace=("main",)): """add the given branch as a global tree root - in the given namespace address (which of course also corresponds to a tree branch branch is added by the given key """ globalBranch = cls.globalTree(namespace, create=True) #print(globalBranch) globalBranch.value = branch #print(globalBranch) @property def readOnly(self)->bool: """read-only if explicitly set, OR if tree is reference and deltas are not tracked""" return self.getInherited("readOnly") @readOnly.setter def readOnly(self, val): self.setProperty("readOnly", val) # def _branchFromToken(self, token): # return super(Tree, self)._branchFromToken(token) def uidTie(self): return (self.uidRepr(), self._coreDataUid[:4] + "-", self.coreData().uidRepr()) def uidView(self): return pprint.pformat([i.uidTie() for i in self.allBranches()]) @classmethod def _copyAndXorUids(cls, targetTree:Tree, saltUid:str, andCoreData=True, sourceUidPropKey="_sourceUid")->Tree: """copies given branch, replaces each uid with xor( base branch uid, salt uid ) and adds original uid as property with sourceUidPropKey key""" copyTree = targetTree.copy(deep=True) for targetBranch, copyBranch in zip( targetTree.allBranches(includeSelf=True), copyTree.allBranches(includeSelf=True), ): baseUid = targetBranch.uid newUid = cls.xorUids(baseUid, saltUid) if andCoreData: copyBranch.coreData().setUid(newUid) copyBranch._coreDataUid = newUid copyBranch.setUid(newUid, replace=False) newBranch = cls.byUid(newUid) newBranch.setProperty(sourceUidPropKey, baseUid) return copyTree # region ui integration def setUiData(self, widgetType:AtomicWidgetType, widgetMin=None, widgetMax=None, defaultPath:T.Union[str, PurePath]=None): uiData = BranchUiData(widgetType, widgetMin, widgetMax, defaultPath) self.setProperty("uiData", uiData)
def __set__(self, instance: TreeBase, value): # do not set value if equal to default if value == self._evalDefault(instance, self.key): return instance.setProperty(self.key, value)
class Tree(TreeBase): """implement more complex systems beyond basic structure""" # system for finding required trees from a global scope globalTree = TreeBase("globalRoots") #globalTree.default = lambda branch: {} # each namespace a dict # import reference modes for TreeReference RefMode = TreeReference.Mode # what kind of UI widget should this branch show uiType = TreeBase.TreePropertyDescriptor("uiType") @classmethod def defaultBranchCls(cls): return Tree @classmethod def proxyForBranch(cls, branch: Tree, mode=RefMode.Uid): """return a TreeProxy for the given branch""" return TreeProxy.getProxy(branch, shared=True, refMode=mode) def __init__(self, name: str = None, val: T = None, default=None, treeUid=None, properties: dict = None): #print("_init tree") self.liveInput: TransformChain = None self._liveInputChanged = False self._directLiveInputRef = None # for direct access without hierarchy self._liveInputRoot = None # for direct access without hierarchy # for deltas signature: branch, delta, deltaTracker self.deltasChanged = Signal() self._deltaTracker: TreeDeltaTracker = None self._deltaTrackingActive = False super(Tree, self).__init__(name=name, val=val, default=default, treeUid=treeUid, properties=properties) self.deltasChanged.connect(self.treeChanged) @classmethod def addGlobalRoot(cls, branch: TreeBase, namespace=("main", )): """add the given branch as a global tree root - in the given namespace address (which of course also corresponds to a tree branch branch is added by the given key """ globalBranch = cls.globalTree(namespace, create=True) #print(globalBranch) globalBranch.value = branch #print(globalBranch) @property def liveInputRoot(self): """return the closest direct reference above this branch or None if there aren't any references""" if self.liveInput: return self if self.parent: return self.parent.liveInputRoot return None @property def readOnly(self) -> bool: """read-only if explicitly set, OR if tree is reference and deltas are not tracked""" return self.getInherited("readOnly") @readOnly.setter def readOnly(self, val): self.setProperty("readOnly", val) # def _branchFromToken(self, token): # return super(Tree, self)._branchFromToken(token) def signals(self): return (*super(Tree, self).signals(), self.deltasChanged) def setLiveInput(self, liveInput: T.Union[Tree, TransformChain]): """set this tree to reflect the result of a transform chain, drawing from another branch""" if isinstance(liveInput, TreeBase): liveInput = TransformChain(liveInput) print("set live input") self.liveInput = liveInput self.readOnly = True self.onLiveInputChanged() def coreData(self) -> TreeCoreData: """check if live input is dirty""" if self._liveInputRoot: # if self._liveInputRoot._liveInputChanged: if self._liveInputRoot.liveInput.dirty: self._liveInputRoot._liveInputChanged = False self._liveInputRoot.onLiveInputChanged() return super(Tree, self).coreData() def uidTie(self): return (self.uidRepr(), self._coreDataUid[:4] + "-", self.coreData().uidRepr()) def uidView(self): return pprint.pformat([i.uidTie() for i in self.allBranches()]) @classmethod def _copyAndXorUids(cls, targetTree: Tree, saltUid: str, andCoreData=True, sourceUidPropKey="_sourceUid") -> Tree: """copies given branch, replaces each uid with xor( base branch uid, salt uid ) and adds original uid as property with sourceUidPropKey key""" copyTree = targetTree.copy(deep=True) for targetBranch, copyBranch in zip( targetTree.allBranches(includeSelf=True), copyTree.allBranches(includeSelf=True), ): baseUid = targetBranch.uid newUid = cls.xorUids(baseUid, saltUid) if andCoreData: copyBranch.coreData().setUid(newUid) copyBranch._coreDataUid = newUid copyBranch.setUid(newUid, replace=False) newBranch = cls.byUid(newUid) newBranch.setProperty(sourceUidPropKey, baseUid) return copyTree def onLiveInputChanged(self, *args, **kwargs): """simplest, slowest solution - match this tree and its branches to result on change sourceUid is vital, used for delta retargeting back to transform result tree """ self.liveInput.resultTree() baseReadOnly = self.readOnly self.setProperty("_sourceUid", self.liveInput.resultTree().uid) # copy result tree resultTree = self._copyAndXorUids(self.liveInput.resultTree(), saltUid=self.uid, andCoreData=True) #print("result branches", self.liveInput.resultTree().branches) baseCoreData = self.coreData() self.setCoreData(resultTree.coreData()) self.coreData().setUid(self.uid) self._coreDataUid = self.uid # set direct reference to this branch on all children for i in self.allBranches(includeSelf=True): i._liveInputRoot = self self.coreDataChanged.emit(self, baseCoreData, self.coreData()) self.readOnly = baseReadOnly print("after input change") print(self.displayStr()) print(self.deltaData(), self.deltaBaseName()) def addReferenceBranch(self, otherBranch: Tree) -> Tree: """test without any other objects connect all signals of other tree's branches this one's sync functions """ newBranch = otherBranch.copy(deep=True) self.addChild(newBranch) newBranch.setLiveInput(otherBranch) return newBranch def setName( self, name, allowMerging=False, emitDelta=None, ): """ if name is current name, do nothing if emit delta, emit a delta if this is read only, then return if read-only, raise a permission error if read-only overridden to False AND live input AND no deltas just do nothing, this is super edge case """ oldName = self.name if name == oldName: return name if self.parent: if not allowMerging: name = self.parent.getUniqueBranchName(name) emitDelta = emitDelta if emitDelta is not None else self.trackingDeltas if emitDelta: delta = TreeNameDelta( TreeReference(self, relParent=self.deltaTrackingRoot), oldName, name) self.emitStackDelta(delta) if self.readOnly: # print("post emit read only, returning") return oldName # check for read only if self.readOnly: raise PermissionError( f"tree {self.address()} is read only, cannot set name") super(Tree, self).setName(name, allowMerging=allowMerging) return name def setValue(self, val, emitDelta=None): """set this tree's value - optionally emitting a delta object""" oldValue = self.value # if value hasn't actually changed, ignore # this often happens when linked to uis - # less elegant, but way less cde if val == oldValue: #print("new value is old") return emitDelta = emitDelta if emitDelta is not None else self.trackingDeltas if emitDelta: delta = TreeValueDelta( TreeReference(self, relParent=self.deltaTrackingRoot), oldValue, val) self.emitStackDelta(delta) if self.readOnly: return # check for read only if self.readOnly: raise PermissionError( f"tree {self.address()} is read only, cannot set value") return super(Tree, self).setValue(val) def addChild(self, branch: TreeBase, index=None, force=False, emitDelta=None) -> TreeBase: """check if a delta is necessary""" #print("addChild") emitDelta = emitDelta if emitDelta is not None else self.trackingDeltas #print("emitStackDelta", emitStackDelta, self.trackingDeltas) if emitDelta: # check if tree is "known" to this delta tracking scope try: relPath = branch.relAddress(fromBranch=self.deltaTrackingRoot) print("existing branch delta") structDelta = TreeStructureDelta( branchRef=TreeReference(branch, relParent=self.deltaTrackingRoot), oldParentRef=TreeReference( branch.parent, relParent=self.deltaTrackingRoot), parentRef=TreeReference( branch=self, relParent=self.deltaTrackingRoot, ), oldIndex=branch.index(), newIndex=index or -1, eventCode=self.StructureEvents.branchAdded) self.emitStackDelta(structDelta) except LookupError: print("new branch delta") # branch not known to delta root, make creation delta createDelta = TreeCreationDelta( TreeReference(branch, relParent=None, mode=TreeReference.Mode.Uid), parentRef=TreeReference(self, relParent=self.deltaTrackingRoot), name=branch.name, value=branch.value, index=index or -1, treeUid=branch.uid, treeCls=Tree) self.emitStackDelta(createDelta) # print("readonly", self.readOnly) if self.readOnly: print("read only returning") return None # check for read only if self.readOnly: raise PermissionError( f"tree {self.address()} is read only, cannot set value") return super(Tree, self).addChild(branch, index=index) def remove(self, address: T.Union[TreeBase.ADDRESS_TYPES, "TreeBase", None] = None, delete=False, emitDelta=None): # print(address, type(address)) parent, child = self._getRemoveTarget(address) # print(address, parent, child) if parent is None: return if parent is not self: return parent.remove(child, delete=delete, emitDelta=emitDelta) emitDelta = emitDelta if emitDelta is not None else self.trackingDeltas if emitDelta: deletionDelta = TreeDeletionDelta( TreeReference(child, relParent=self.deltaTrackingRoot, mode=TreeReference.Mode.Uid), parentRef=TreeReference(self, relParent=self.deltaTrackingRoot, mode=TreeReference.Mode.Uid), name=child.name, value=child.value, treeUid=child.uid, index=child.index(), treeCls=Tree) self.emitStackDelta(deletionDelta) if self.readOnly: #print("read only returning") return None # check for read only if self.readOnly: raise PermissionError( f"tree {self.address()} is read only, cannot set value") return super(Tree, self).remove(address, delete=delete) #region deltas def deltaTracker(self): if self.liveInputRoot: return self.liveInputRoot.liveInput.getDeltaTransform() return self._deltaTracker def setTrackingDeltas(self, state: bool): if state: self._deltaTracker = TreeDeltaTracker() self._deltaTrackingActive = True else: self._deltaTracker = None self._deltaTrackingActive = False self.deltasChanged(self, None, self._deltaTracker) def clearDeltas(self): self.deltaTracker().clearDeltas() self.deltasChanged(self, None, self.deltaTracker()) @property def deltaTrackingRoot(self): if self._deltaTracker: return self if self.parent: return self.parent.deltaTrackingRoot return None @property def trackingDeltas(self): if not self.deltaTrackingRoot: return False return self.deltaTrackingRoot._deltaTrackingActive def undo(self): """rolls back a single delta""" self.deltaTrackingRoot.deltaTracker().undo(self) def redo(self): """redo a single delta""" self.deltaTrackingRoot.deltaTracker().redo(self) def deltaAddress(self): """return relative address from delta tracking root""" return self.relAddress(self.deltaTrackingRoot) def emitStackDelta(self, delta: TreeDeltaAtom): print("emit delta", delta) if not self.deltaTrackingRoot: print("tree ", self, "is not tracking deltas") return # patch any tree references in this delta to input tree if self.deltaTrackingRoot.liveInputRoot: for ref in delta.references(): if ref.resolve() is self.deltaTrackingRoot: ref.uid = self.deltaTrackingRoot.liveInput.resultTree().uid elif ref.resolve().getProperty("_sourceUid"): # reverse xor operation to recover original tree uid reverseUid = bitwiseXor(ref.uid, self.deltaTrackingRoot.uid) ref.uid = reverseUid self.deltaTrackingRoot.deltaTracker().addDelta(delta) self.deltasChanged(self, delta, self.deltaTrackingRoot.deltaTracker()) def deltaData(self) -> dict: """return any record of what has been done to this tree by a delta""" if self.getProperty(self.deltaPropKey) is None: self.setProperty(self.deltaPropKey, {}) return self.getProperty(self.deltaPropKey) def deltaBaseName(self): return self.deltaData().get("name") def deltaBaseValue(self): return self.deltaData().get("value") def deltaBaseBranchNames(self): return self.deltaData().get("branchNames") def isDeltaCreated(self): """check if this branch is created by a delta""" return self.deltaData().get("created") def isDeltaCreatedChild(self): """check if this branch has a parent which is created by a delta""" state = self.isDeltaCreated() if self.parent: state = state or self.parent.isDeltaCreatedChild() return state #endregion # region ui integration def setUiData(self, widgetType: AtomicWidgetType, widgetMin=None, widgetMax=None, defaultPath: T.Union[str, PurePath] = None): uiData = BranchUiData(widgetType, widgetMin, widgetMax, defaultPath) self.setProperty("uiData", uiData)