Example #1
0
    def load(
        self,
        bl_collection: bpy.types.bpy_prop_collection,
        attr_name: str,
        attr_property: T.Property,
        context: Context,
    ):
        """
        - bl_collection: a collection of structure, e.g. a SplineBezierPoints instance
        - attr_name: the name of the member to load, such as "handle_left_type"
        """

        for index, item in enumerate(bl_collection):
            self._data[index] = read_attribute(getattr(item, attr_name), index,
                                               attr_property, bl_collection,
                                               context)

        try:
            if not isinstance(self._data[0], str):
                logger.error(
                    f"unsupported type for {bl_collection}[{attr_name}]: {type(self._data[0])}"
                )
        except KeyError:
            pass

        return self
Example #2
0
    def load(
        self,
        datablock: T.ID,
        context: Context,
    ) -> DatablockProxy:
        """Load a datablock into this proxy.

        Args:
            datablock: the embedded datablock to load into this proxy
            context: visit and proxy state

        Returns:
            this DatablockProxy
        """

        self.clear_data()
        self._has_datablock = True
        properties = context.synchronized_properties.properties(datablock)
        # this assumes that specifics.py apply only to ID, not Struct
        properties = specifics.conditional_properties(datablock, properties)
        with context.visit_state.enter_datablock(self, datablock):
            for name, bl_rna_property in properties:
                attr = getattr(datablock, name)
                attr_value = read_attribute(attr, name, bl_rna_property,
                                            context)
                # Also write None values to reset attributes like Camera.dof.focus_object
                # TODO for scene, test difference, only send update if dirty as continuous updates to scene
                # master collection will conflicting writes with Master Collection
                self._data[name] = attr_value

        self.attach_filepath_raw(datablock)
        self.attach_media_descriptor(datablock, context)
        self._custom_properties.load(datablock)
        return self
Example #3
0
    def load(self, attribute: T.bpy_struct, key: Union[int, str],
             context: Context) -> StructProxy:
        """
        Load the attribute Blender struct into this proxy

        Args:
            attribute: the Blender struct to load into this proxy, (e.g an ObjectDisplay instance)
            key: the identifier of attribute in its parent (e.g. "display")
            context: the proxy and visit state
        """
        self.clear_data()
        properties = context.synchronized_properties.properties(attribute)
        # includes properties from the bl_rna only, not the "view like" properties like MeshPolygon.edge_keys
        # that we do not want to load anyway
        properties = specifics.conditional_properties(attribute, properties)
        context.visit_state.path.append(key)
        try:
            for name, bl_rna_property in properties:
                attr = getattr(attribute, name)
                attr_value = read_attribute(attr, name, bl_rna_property,
                                            context)
                self._data[name] = attr_value
        finally:
            context.visit_state.path.pop()

        return self
Example #4
0
    def load(
        self,
        bl_instance: T.ID,
        key: str,
        context: Context,
        bpy_data_collection_name: str = None,
    ):
        """
        Load a datablock into this proxy
        """
        if bl_instance.is_embedded_data and bpy_data_collection_name is not None:
            logger.error(
                f"DatablockProxy.load() for {bl_instance} : is_embedded_data is True and bpy_prop_collection is {bpy_data_collection_name}. Item ignored"
            )
            return

        if bl_instance.is_embedded_data:
            self._bpy_data_collection = None

        if bpy_data_collection_name is not None:
            self._bpy_data_collection = bpy_data_collection_name

        self._class_name = bl_instance.__class__.__name__
        self._data.clear()
        properties = context.synchronized_properties.properties(bl_instance)
        # this assumes that specifics.py apply only to ID, not Struct
        properties = specifics.conditional_properties(bl_instance, properties)
        try:
            context.visit_state.datablock_proxy = self
            for name, bl_rna_property in properties:
                attr = getattr(bl_instance, name)
                attr_value = read_attribute(attr, name, bl_rna_property,
                                            context)
                # Also write None values to reset attributes like Camera.dof.focus_object
                # TODO for scene, test difference, only send update if dirty as continuous updates to scene
                # master collection will conflicting writes with Master Collection
                self._data[name] = attr_value
        finally:
            context.visit_state.datablock_proxy = None

        specifics.post_save_id(self, bl_instance)

        uuid = bl_instance.get("mixer_uuid")
        if uuid:
            # It is a bpy.data ID, not an ID "embedded" inside another ID, like scene.collection
            id_ = context.proxy_state.datablocks.get(uuid)
            if id_ is not bl_instance:
                # this occurs when
                # - when we find a reference to a BlendData ID that was not loaded
                # - the ID are not properly ordred at creation time, for instance (objects, meshes)
                # instead of (meshes, objects) : a bug
                logger.debug(
                    "DatablockProxy.load(): %s not in context.proxy_state.datablocks[uuid]",
                    bl_instance)
            self._datablock_uuid = bl_instance.mixer_uuid
            context.proxy_state.proxies[uuid] = self

        self.attach_media_descriptor(bl_instance)
        return self
Example #5
0
    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
Example #7
0
 def diff(
     self,
     container: Union[T.bpy_prop_collection, T.Struct],
     key: Union[str, int],
     prop: T.Property,
     context: Context,
 ) -> Optional[DeltaUpdate]:
     attr = read_attribute(container, key, prop, None, context)
     if isinstance(attr, NonePtrProxy):
         return None
     return DeltaReplace(attr)
Example #8
0
    def diff(self, datablock: T.ID, key: Union[int, str],
             datablock_property: T.Property,
             context: Context) -> Optional[DeltaReplace]:
        """
        Computes the difference between this proxy and its Blender state.
        """

        if datablock is None:
            return DeltaReplace(DatablockRefProxy())

        value = read_attribute(datablock, key, datablock_property, None,
                               context)
        assert isinstance(value, DatablockRefProxy)
        if value._datablock_uuid != self._datablock_uuid:
            return DeltaReplace(value)
        else:
            return None
Example #9
0
    def load(
        self,
        bl_collection: bpy.types.bpy_prop_collection,
        attr_name: str,
        attr_property: T.Property,
        context: Context,
    ):
        """
        - bl_collection: a collection of structure, e.g. T.Mesh.vertices
        - item_bl_rna: the bl_rna if the structure contained in the collection, e.g. T.MeshVertices.bl_rna
        - attr_name: a member if the structure to be loaded as a sequence, e.g. "groups"
        """

        for index, item in enumerate(bl_collection):
            self._data[index] = read_attribute(getattr(item, attr_name), index, attr_property, context)

        return self
Example #10
0
    def load(self, bl_instance: Any, parent_key: Union[int, str],
             context: Context):
        """
        Load a Blender object into this proxy
        """
        self._data.clear()
        properties = context.synchronized_properties.properties(bl_instance)
        # includes properties from the bl_rna only, not the "view like" properties like MeshPolygon.edge_keys
        # that we do not want to load anyway
        properties = specifics.conditional_properties(bl_instance, properties)
        try:
            context.visit_state.path.append(parent_key)
            for name, bl_rna_property in properties:
                attr = getattr(bl_instance, name)
                attr_value = read_attribute(attr, name, bl_rna_property,
                                            context)

                # Also write None values. We use them to reset attributes like Camera.dof.focus_object
                self._data[name] = attr_value
        finally:
            context.visit_state.path.pop()

        return self
Example #11
0
 def _read_attribute():
     self._data["edit_bones"] = read_attribute(
         armature_data.edit_bones, "edit_bones",
         self._edit_bones_property, armature_data, context)
Example #12
0
    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