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)
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, 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()
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()
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
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
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()
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