def diff(self, collection: T.bpy_prop_collection, key: str, collection_property: T.Property, context: Context) -> Optional[DeltaUpdate]: """ Computes the difference between the state of an item tracked by this proxy and its Blender state. As this proxy tracks a collection, the result will be a DeltaUpdate that contains a DatablockCollectionProxy with an Delta item per added, deleted or update item Args: collection: the collection diff against this proxy collection_property: the property of collection in its enclosing object """ # This method is called from the depsgraph handler. The proxy holds a representation of the Blender state # before the modification being processed. So the changeset is (Blender state - proxy state) # TODO how can this replace BpyBlendDiff ? diff = self.__class__() item_property = collection_property.fixed_type # keys are uuids # BpyDataCollectionDiff.diff() for why proxies without datablocks are ignores proxy_keys = {k for k, v in self._data.items() if v.target(context)} blender_items = { datablock.mixer_uuid: datablock for datablock in collection.values() } blender_keys = blender_items.keys() added_keys = blender_keys - proxy_keys deleted_keys = proxy_keys - blender_keys maybe_updated_keys = proxy_keys & blender_keys for k in added_keys: value = read_attribute(blender_items[k], k, item_property, collection, context) assert isinstance(value, (DatablockProxy, DatablockRefProxy)) diff._data[k] = DeltaAddition(value) for k in deleted_keys: diff._data[k] = DeltaDeletion(self._data[k]) for k in maybe_updated_keys: delta = diff_attribute(blender_items[k], k, item_property, self.data(k), context) if delta is not None: assert isinstance(delta, DeltaUpdate) diff._data[k] = delta if len(diff._data): return DeltaUpdate(diff) return None
def diff( self, collection: T.bpy_prop_collection, key: Union[int, str], collection_property: T.Property, context: Context ) -> Optional[Union[DeltaUpdate, DeltaReplace]]: """ Computes the difference between the state of an item tracked by this proxy and its Blender state. This proxy tracks a collection of items indexed by string (e.g Scene.render.views) or int. The result will be a ProxyDiff that contains a Delta item per added, deleted or updated item Args: collection; the collection that must be diffed agains this proxy key: the name of the collection, to record in the visit path collection_property; the property os collection as found in its enclosing object """ sequence = self._sequence if len(sequence) == 0 and len(collection) == 0: return None if specifics.diff_must_replace(collection, sequence, collection_property): # A collection cannot be updated because either: # - some of its members cannot be updated : # SplineBezierPoints has no API to remove points, so Curve.splines cannot be update and must be replaced # - updating the name of members will cause unsolicited renames. # When swapping layers A and B in a GreasePencilLayers, renaming layer 0 into B cause an unsolicited # rename of layer 0 into B.001 # Send a replacement for the whole collection self.load(collection, context) return DeltaReplace(self) else: item_property = collection_property.fixed_type diff = self.__class__() # items from clear_from index cannot be updated, most often because eir type has changed (e.g # ObjectModifier) clear_from = specifics.clear_from(collection, sequence, context) # run a diff for the head, that can be updated in-place for i in range(clear_from): delta = diff_attribute(collection[i], i, item_property, sequence[i], context) if delta is not None: diff._diff_updates.append((i, delta)) if specifics.can_resize(collection, context): # delete the existing tail that cannot be modified diff._diff_deletions = len(sequence) - clear_from # add the new tail for i, item in enumerate(collection[clear_from:], clear_from): value = read_attribute(item, i, item_property, collection, context) diff._diff_additions.append(DeltaAddition(value)) if diff._diff_updates or diff._diff_deletions or diff._diff_additions: return DeltaUpdate(diff) return None
def diff( self, collection: T.bpy_prop_collection, key: Union[int, str], collection_property: T.Property, context: Context ) -> Optional[DeltaUpdate]: """ Computes the difference between the state of an item tracked by this proxy and its Blender state. This proxy tracks a collection of items indexed by string (e.g Scene.render.views) or int. The result will be a ProxyDiff that contains a Delta item per added, deleted or updated item Args: collection; the collection that must be diffed agains this proxy collection_property; the property os collection as found in its enclosing object """ diff = self.__class__() item_property = collection_property.fixed_type try: context.visit_state.path.append(key) sequence = self._data.get(MIXER_SEQUENCE) if sequence: # indexed by int # TODO This produces one DeltaDeletion by removed item. Produce a range in case may items are # deleted # since the diff sequence is hollow, we cannot store it in a list. Use a dict with int keys instead for i, (proxy_value, blender_value) in enumerate(itertools.zip_longest(sequence, collection)): if proxy_value is None: value = read_attribute(collection[i], i, item_property, context) diff._data[i] = DeltaAddition(value) elif blender_value is None: diff._data[i] = DeltaDeletion(self.data(i)) else: delta = diff_attribute(collection[i], i, item_property, proxy_value, context) if delta is not None: diff._data[i] = delta else: # index by string. This is similar to DatablockCollectionProxy.diff # Renames are detected as Deletion + Addition # This assumes that keys ordring is the same in the proxy and in blender, which is # guaranteed by the fact that proxy load uses SynchronizedProperties.properties() bl_rna = getattr(collection, "bl_rna", None) if bl_rna is not None and isinstance( bl_rna, (type(T.ObjectModifiers.bl_rna), type(T.ObjectGpencilModifiers)) ): # TODO move this into specifics.py # order-dependant collections with different types like Modifiers proxy_names = list(self._data.keys()) blender_names = collection.keys() proxy_types = [self.data(name).data("type") for name in proxy_names] blender_types = [collection[name].type for name in blender_names] if proxy_types == blender_types and proxy_names == blender_names: # Same types and names : do sparse modification for name in proxy_names: delta = diff_attribute(collection[name], name, item_property, self.data(name), context) if delta is not None: diff._data[name] = delta else: # names or types do not match, rebuild all # There are name collisions during Modifier order change for instance, so prefix # the names to avoid them (using a tuple fails in the json encoder) for name in proxy_names: diff._data["D" + name] = DeltaDeletion(self.data(name)) for name in blender_names: value = read_attribute(collection[name], name, item_property, context) diff._data["A" + name] = DeltaAddition(value) else: # non order dependant, uniform types proxy_keys = self._data.keys() blender_keys = collection.keys() added_keys = blender_keys - proxy_keys for k in added_keys: value = read_attribute(collection[k], k, item_property, context) diff._data["A" + k] = DeltaAddition(value) deleted_keys = proxy_keys - blender_keys for k in deleted_keys: diff._data["D" + k] = DeltaDeletion(self.data(k)) maybe_updated_keys = proxy_keys & blender_keys for k in maybe_updated_keys: delta = diff_attribute(collection[k], k, item_property, self.data(k), context) if delta is not None: diff._data[k] = delta finally: context.visit_state.path.pop() if len(diff._data): return DeltaUpdate(diff) return None