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)
Ejemplo n.º 2
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()
Ejemplo n.º 3
0
    def save(self, bl_instance: T.bpy_struct, attr_name: str,
             context: Context):
        """
        Save this proxy the Blender property
        """

        if self.length == 0 and len(self._data) != 0:
            logger.error(
                f"save(): length is {self.length} but _data is {self._data.keys()}"
            )
            # return

        target = getattr(bl_instance, attr_name, None)
        if target is None:
            return

        specifics.truncate_collection(target, self, context)
        # nothing to do save here. The buffers that contains vertices and co are serialized apart from the json
        # that contains the Mesh members. The children of this are SoaElement and have no child.
        # They are updated directly bu SoaElement.save_array()

        try:
            context.visit_state.path.append(attr_name)
            for k, v in self._data.items():
                write_attribute(target, k, v, context)
        finally:
            context.visit_state.path.pop()
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
    def apply(self,
              parent: T.bpy_struct,
              key: str,
              delta: Optional[DeltaUpdate],
              context: Context,
              to_blender=True) -> Optional[AosProxy]:
        if delta is None:
            return
        struct_update = delta.value

        aos = getattr(parent, key)

        try:
            context.visit_state.path.append(key)
            self._aos_length = struct_update._aos_length
            specifics.truncate_collection(aos, self, context)
            for k, member_delta in struct_update._data.items():
                current_value = self.data(k)
                if current_value is not None:
                    self._data[k] = current_value.apply(
                        aos, k, member_delta, to_blender)
        finally:
            context.visit_state.path.pop()
        return self
Ejemplo n.º 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
Ejemplo n.º 7
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
        try:
            context.visit_state.path.append(attr_name)
            sequence = self._data.get(MIXER_SEQUENCE)
            if sequence:
                srna = bl_instance.bl_rna.properties[attr_name].srna
                if srna:
                    # TODO move to specifics
                    if srna.bl_rna is bpy.types.CurveMapPoints.bl_rna:
                        write_curvemappoints(target, sequence, context)
                    elif srna.bl_rna is bpy.types.MetaBallElements.bl_rna:
                        write_metaballelements(target, sequence, context)
                    elif srna.bl_rna is bpy.types.MeshPolygons.bl_rna:
                        # see soable_collection_properties
                        target.add(len(sequence))
                        for i, proxy in enumerate(sequence):
                            write_attribute(target, i, proxy, context)
                    elif srna.bl_rna is bpy.types.GPencilFrames.bl_rna:
                        for i, proxy in enumerate(sequence):
                            frame_number = proxy.data("frame_number")
                            target.new(frame_number)
                            write_attribute(target, i, proxy, context)
                    elif srna.bl_rna is bpy.types.GPencilStrokes.bl_rna:
                        for i, proxy in enumerate(sequence):
                            target.new()
                            write_attribute(target, i, proxy, context)
                    elif srna.bl_rna is bpy.types.GPencilStrokePoints.bl_rna:
                        target.new(len(sequence))
                        for i, proxy in enumerate(sequence):
                            write_attribute(target, i, proxy, context)
                    else:
                        logger.error(f"unsupported sequence type {srna}")
                        pass

                elif len(target) == len(sequence):
                    for i, v in enumerate(sequence):
                        # TODO this way can only save items at pre-existing slots. The bpy_prop_collection API
                        # uses struct specific API and ctors:
                        # - CurveMapPoints uses: .new(x, y) and .remove(point), no .clear(). new() inserts in head !
                        #   Must have at least 2 points left !
                        # - NodeTreeOutputs uses: .new(type, name), .remove(socket), has .clear()
                        # - ActionFCurves uses: .new(data_path, index=0, action_group=""), .remove(fcurve)
                        # - GPencilStrokePoints: .add(count), .pop()
                        write_attribute(target, i, v, context)
                else:
                    logger.warning(
                        f"Not implemented: write sequence of different length (incoming: {len(sequence)}, existing: {len(target)})for {bl_instance}.{attr_name}"
                    )
            else:
                # dictionary
                specifics.truncate_collection(target, self, context)
                for k, v in self._data.items():
                    write_attribute(target, k, v, context)
        finally:
            context.visit_state.path.pop()
Ejemplo n.º 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