Пример #1
0
    def save(self, collection: T.bpy_prop_collection, parent: T.bpy_struct,
             key: str, context: Context):
        """
        Save this proxy into collection

        Args:
            collection: the collection into which this proxy is saved
            parent: the attribute that contains collection (e.g. a Scene instance)
            key: the name of the collection in parent (e.g "background_images")
            context: the proxy and visit state
        """
        context.visit_state.path.append(key)
        try:
            sequence = self._sequence

            # Using clear_from ensures that sequence data is compatible with remaining elements after
            # truncate_collection. This addresses an issue with Nodes, for which the order of default nodes (material
            # output and principled in collection) may not match the order of incoming nodes. Saving node data into a
            # node of the wrong type can lead to a crash.
            clear_from = specifics.clear_from(collection, sequence)
            specifics.truncate_collection(collection, clear_from)

            # For collections like `IDMaterials`, the creation API (`.new(datablock_ref)`) also writes the value.
            # For collections like `Nodes`, the creation API (`.new(name)`) does not write the item value.
            # So the value must always be written for all collection types.
            for i in range(len(collection), len(sequence)):
                item_proxy = sequence[i]
                specifics.add_element(collection, item_proxy, context)
            for i, v in enumerate(sequence):
                write_attribute(collection, i, v, context)
        finally:
            context.visit_state.path.pop()
    def save(self, collection: T.bpy_prop_collection, parent: T.bpy_struct, key: str, context: Context):
        """
        Save this proxy into collection

        Args:
            collection: the collection into which this proxy is saved
            parent: the attribute that contains collection (e.g. a Scene instance)
            key: the name of the collection in parent (e.g "background_images")
            context: the proxy and visit state
        """
        sequence = self._sequence

        # Using clear_from ensures that sequence data is compatible with remaining elements after
        # truncate_collection. This addresses an issue with Nodes, for which the order of default nodes (material
        # output and principled in collection) may not match the order of incoming nodes. Saving node data into a
        # node of the wrong type can lead to a crash.
        clear_from = specifics.clear_from(collection, sequence, context)
        specifics.truncate_collection(collection, clear_from)

        # For collections like `IDMaterials`, the creation API (`.new(datablock_ref)`) also writes the value.
        # For collections like `Nodes`, the creation API (`.new(name)`) does not write the item value.
        # So the value must always be written for all collection types.
        collection_length = len(collection)
        for i, item_proxy in enumerate(sequence[:collection_length]):
            write_attribute(collection, i, item_proxy, context)
        for i, item_proxy in enumerate(sequence[collection_length:], collection_length):
            try:
                specifics.add_element(collection, item_proxy, i, context)
                if self._resolver:
                    self._resolver.resolve(i)
            except AddElementFailed:
                break
            # Must write at once, otherwise the default item name might conflit with a later item name
            write_attribute(collection, i, item_proxy, context)
Пример #3
0
 def save(self, bl_instance: Any, attr_name: str, context: Context):
     """
     Save this proxy the Blender property
     """
     target = getattr(bl_instance, attr_name, None)
     if target is None:
         # # Don't log this, too many messages
         # f"Saving {self} into non existent attribute {bl_instance}.{attr_name} : ignored"
         return
     context.visit_state.path.append(attr_name)
     try:
         sequence = self._sequence
         specifics.truncate_collection(target, len(self._sequence))
         for i in range(len(target), len(sequence)):
             item_proxy = sequence[i]
             specifics.add_element(target, item_proxy, context)
         for i, v in enumerate(sequence):
             write_attribute(target, i, v, context)
     finally:
         context.visit_state.path.pop()
Пример #4
0
    def save(self, bl_instance: Any, key: Union[int, str], context: Context):
        """
        Save this proxy into a Blender attribute
        """
        assert isinstance(key, (int, str))

        if isinstance(key, int):
            target = bl_instance[key]
        elif isinstance(bl_instance, T.bpy_prop_collection):
            # TODO append an element :
            # https://blenderartists.org/t/how-delete-a-bpy-prop-collection-element/642185/4
            target = bl_instance.get(key)
            if target is None:
                target = specifics.add_element(self, bl_instance, key, context)
        else:
            target = getattr(bl_instance, key, None)
            if target is not None:
                self._pre_save(target, context)

        if target is None:
            if isinstance(bl_instance, T.bpy_prop_collection):
                logger.warning(
                    f"Cannot write to '{bl_instance}', attribute '{key}' because it does not exist."
                )
            else:
                # Don't log this because it produces too many log messages when participants have plugins
                # f"Note: May be due to a plugin used by the sender and not on this Blender"
                # f"Note: May be due to unimplemented 'use_{key}' implementation for type {type(bl_instance)}"
                # f"Note: May be {bl_instance}.{key} should not have been saved"
                pass

            return

        try:
            context.visit_state.path.append(key)
            for k, v in self._data.items():
                write_attribute(target, k, v, context)
        finally:
            context.visit_state.path.pop()
Пример #5
0
    def apply(
        self,
        parent: Any,
        key: Union[int, str],
        struct_delta: Optional[DeltaUpdate],
        context: Context,
        to_blender: bool = True,
    ) -> StructProxy:
        """
        Apply diff to the Blender attribute at parent[key] or parent.key and update accordingly this proxy entry
        at key.

        Args:
            parent ([type]): [description]
            key ([type]): [description]
            delta ([type]): [description]
            context ([type]): [description]

        Returns:
            [type]: [description]
        """
        if struct_delta is None:
            return

        assert isinstance(key, (int, str))

        struct_update = struct_delta.value

        if isinstance(key, int):
            struct = parent[key]
        elif isinstance(parent, T.bpy_prop_collection):
            # TODO append an element :
            # https://blenderartists.org/t/how-delete-a-bpy-prop-collection-element/642185/4
            struct = parent.get(key)
            if struct is None and to_blender:
                struct = specifics.add_element(self, parent, key, context)
        else:
            struct = getattr(parent, key, None)
            if to_blender:
                struct = struct_update._pre_save(struct, context)

        assert type(struct_update) == type(self)

        try:
            context.visit_state.path.append(key)
            for k, member_delta in struct_update._data.items():
                current_value = self._data.get(k)
                try:
                    self._data[k] = apply_attribute(struct, k, current_value,
                                                    member_delta, context,
                                                    to_blender)
                except Exception as e:
                    logger.warning(
                        f"Struct.apply(). Processing {member_delta}")
                    logger.warning(f"... for {struct}.{k}")
                    logger.warning(f"... Exception: {e!r}")
                    logger.warning("... Update ignored")
                    continue
        finally:
            context.visit_state.path.pop()

        return self
Пример #6
0
    def apply(
        self,
        collection: T.bpy_prop_collection,
        parent: T.bpy_struct,
        key: Union[int, str],
        delta: Delta,
        context: Context,
        to_blender=True,
    ) -> StructCollectionProxy:
        """
        Apply delta to this proxy and optionally to the Blender attribute its manages.

        Args:
            attribute: the collection to update (e.g. a_mesh.material)
            parent: the attribute that contains attribute (e.g. a a Mesh instance)
            key: the key that identifies attribute in parent (e.g "materials")
            delta: the delta to apply
            context: proxy and visit state
            to_blender: update the managed Blender attribute in addition to this Proxy
        """
        assert isinstance(key, str)

        update = delta.value
        assert type(update) == type(self)

        if isinstance(delta, DeltaReplace):
            self._sequence = update._sequence
            if to_blender:
                specifics.truncate_collection(collection, 0)
                self.save(collection, parent, key, context)
        else:
            # a sparse update

            context.visit_state.path.append(key)
            try:
                sequence = self._sequence

                # Delete before update and process updates in reverse order to avoid spurious renames.
                # Starting with sequence A, B, C, D and delete B causes :
                # - an update for items 1 and 2 to be renamed into C and D
                # - one delete
                # If the update is processed first, Blender renames item 3 into D.001
                # If the deletes are processed first but the updates are processed in order, Blender renames item 1
                # into C.001

                delete_count = update._diff_deletions
                if delete_count > 0:
                    if to_blender:
                        specifics.truncate_collection(
                            collection,
                            len(collection) - delete_count)
                    del sequence[-delete_count:]

                for i, delta_update in reversed(update._diff_updates):
                    sequence[i] = apply_attribute(collection, i, sequence[i],
                                                  delta_update, context,
                                                  to_blender)

                for i, delta_addition in enumerate(update._diff_additions,
                                                   len(sequence)):
                    if to_blender:
                        item_proxy = delta_addition.value
                        specifics.add_element(collection, item_proxy, context)
                        write_attribute(collection, i, item_proxy, context)
                    sequence.append(delta_addition.value)

            except Exception as e:
                logger.warning(
                    "apply: Exception while processing attribute ...")
                logger.warning(
                    f"... {context.visit_state.display_path()}.{key}")
                logger.warning(f"... {e!r}")
            finally:
                context.visit_state.path.pop()

        return self
Пример #7
0
    def apply(
        self, parent: Any, key: Union[int, str], delta: Optional[DeltaUpdate], context: Context, to_blender=True
    ) -> StructProxy:

        assert isinstance(key, (int, str))

        # TODO factorize with save

        if isinstance(key, int):
            collection = parent[key]
        elif isinstance(parent, T.bpy_prop_collection):
            # TODO append an element :
            # https://blenderartists.org/t/how-delete-a-bpy-prop-collection-element/642185/4
            collection = parent.get(key)
            if collection is None:
                collection = specifics.add_element(self, parent, key, context)
        else:
            collection = getattr(parent, key, None)

        update = delta.value
        assert type(update) == type(self)

        try:
            context.visit_state.path.append(key)
            sequence = self._data.get(MIXER_SEQUENCE)
            if sequence:

                # input validity assertions
                add_indices = [i for i, delta in enumerate(update._data.values()) if isinstance(delta, DeltaAddition)]
                del_indices = [i for i, delta in enumerate(update._data.values()) if isinstance(delta, DeltaDeletion)]
                if add_indices or del_indices:
                    # Cannot have deletions and additions
                    assert not add_indices or not del_indices, "not add_indices or not del_indices"
                    indices = add_indices if add_indices else del_indices
                    # Check that adds and deleted are at the end
                    assert (
                        not indices or indices[-1] == len(update._data) - 1
                    ), "not indices or indices[-1] == len(update._data) - 1"
                    # check that adds and deletes are contiguous
                    assert all(
                        a + 1 == b for a, b in zip(indices, iter(indices[1:]))
                    ), "all(a + 1 == b for a, b in zip(indices, iter(indices[1:])))"

                for k, delta in update._data.items():
                    i = int(k)
                    try:
                        if isinstance(delta, DeltaUpdate):
                            sequence[i] = apply_attribute(collection, i, sequence[i], delta, context, to_blender)
                        elif isinstance(delta, DeltaDeletion):
                            if to_blender:
                                item = collection[i]
                                collection.remove(item)
                            del sequence[i]
                        else:  # DeltaAddition
                            # TODO pre save for use_curves
                            # since ordering does not include this requirement
                            if to_blender:
                                raise NotImplementedError("Not implemented: DeltaAddition for array")
                                write_attribute(collection, i, delta.value, context)
                            sequence.append(delta.value)

                    except Exception as e:
                        logger.warning(f"StructCollectionProxy.apply(). Processing {delta}")
                        logger.warning(f"... for {collection}[{i}]")
                        logger.warning(f"... Exception: {e!r}")
                        logger.warning("... Update ignored")
                        continue
            else:
                for k, delta in update._data.items():
                    try:
                        if isinstance(delta, DeltaDeletion):
                            # TODO do all collections have remove ?
                            # see "name collision" in diff()
                            k = k[1:]
                            if to_blender:
                                item = collection[k]
                                collection.remove(item)
                            del self._data[k]
                        elif isinstance(delta, DeltaAddition):
                            # TODO pre save for use_curves
                            # since ordering does not include this requirement

                            # see "name collision" in diff()
                            k = k[1:]
                            if to_blender:
                                write_attribute(collection, k, delta.value, context)
                            self._data[k] = delta.value
                        else:
                            self._data[k] = apply_attribute(collection, k, self._data[k], delta, context, to_blender)
                    except Exception as e:
                        logger.warning(f"StructCollectionProxy.apply(). Processing {delta}")
                        logger.warning(f"... for {collection}[{k}]")
                        logger.warning(f"... Exception: {e!r}")
                        logger.warning("... Update ignored")
                        continue
        finally:
            context.visit_state.path.pop()

        return self
Пример #8
0
    def apply(self,
              parent: Any,
              key: Union[int, str],
              delta: Optional[DeltaUpdate],
              context: Context,
              to_blender=True) -> StructCollectionProxy:

        assert isinstance(key, (int, str))

        update = delta.value
        assert type(update) == type(self)

        if isinstance(key, int):
            collection = parent[key]
        elif isinstance(parent, T.bpy_prop_collection):
            collection = parent.get(key)
        else:
            collection = getattr(parent, key, None)

        if isinstance(delta, DeltaReplace):
            self._sequence = update._sequence
            if to_blender:
                specifics.truncate_collection(collection, 0)
                self.save(parent, key, context)
        else:
            # a sparse update

            context.visit_state.path.append(key)
            try:
                sequence = self._sequence

                # Delete before update and process updates in reverse order to avoid spurious renames.
                # Starting with sequence A, B, C, D and delete B causes :
                # - an update for items 1 and 2 to be renamed into C and D
                # - one delete
                # If the update is processed first, Blender renames item 3 into D.001
                # If the deletes are processed first but the updates are processed in order, Blender renames item 1
                # into C.001

                for _ in range(update._diff_deletions):
                    if to_blender:
                        item = collection[-1]
                        collection.remove(item)
                    del sequence[-1]

                for i, delta_update in reversed(update._diff_updates):
                    sequence[i] = apply_attribute(collection, i, sequence[i],
                                                  delta_update, context,
                                                  to_blender)

                for i, delta_addition in enumerate(update._diff_additions,
                                                   len(sequence)):
                    if to_blender:
                        item_proxy = delta_addition.value
                        specifics.add_element(collection, item_proxy, context)
                        write_attribute(collection, i, item_proxy, context)
                    sequence.append(delta_addition.value)

            except Exception as e:
                logger.warning(
                    f"StructCollectionProxy.apply(). Processing {delta}")
                logger.warning(f"... for {collection}")
                logger.warning(f"... Exception: {e!r}")
                logger.warning("... Update ignored")

            finally:
                context.visit_state.path.pop()

        return self