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
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
def _diff(self, attribute: T.bpy_struct, key: Union[int, str], prop: T.Property, context: Context, diff: StructProxy) -> Optional[Delta]: """ Computes the difference between the state of an item tracked by this proxy and its Blender state and attached the difference to diff. See diff() Args: attribute: the struct to update (e.g. a Material instance) key: the key that identifies attribute in parent (e.g "Material") prop: the Property of struct as found in its enclosing object context: proxy and visit state diff: the proxy that holds the difference and will be transmitted in a Delta Returns: a delta if any difference is found, None otherwise """ # PERF accessing the properties from the synchronized_properties is **far** cheaper that iterating over # _data and the getting the properties with # member_property = struct.bl_rna.properties[k] # line to which py-spy attributes 20% of the total diff ! if prop is not None: context.visit_state.path.append(key) try: properties = context.synchronized_properties.properties(attribute) properties = specifics.conditional_properties( attribute, properties) for k, member_property in properties: # TODO in test_differential.StructDatablockRef.test_remove # target et a scene, k is world and v (current world value) is None # so diff fails. v should be a BpyIDRefNoneProxy # make a difference between None value and no member try: member = getattr(attribute, k) except AttributeError: logger.info(f"diff: unknown attribute {k} in {attribute}") continue proxy_data = self._data.get(k) delta = diff_attribute(member, k, member_property, proxy_data, context) if delta is not None: diff._data[k] = delta finally: if prop is not None: context.visit_state.path.pop() # TODO detect media updates (reload(), and attach a media descriptor to diff) # difficult ? # if anything has changed, wrap the hollow proxy in a DeltaUpdate. This may be superfluous but # it is homogenous with additions and deletions if len(diff._data): return DeltaUpdate(diff) return None
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
def _diff(self, struct: T.Mesh, key: str, prop: T.Property, context: Context, diff: MeshProxy) -> Optional[Union[DeltaUpdate, DeltaReplace]]: if self.requires_clear_geometry(struct): # If any mesh buffer changes requires a clear geometry on the receiver, the receiver will clear all # buffers, including uv_layers and vertex_colors. # Resend everything diff.load(struct, context) # force ObjectProxy._diff to resend the Vertex groups context.visit_state.dirty_vertex_groups.add(struct.mixer_uuid) return DeltaReplace(diff) else: if prop is not None: context.visit_state.path.append(key) try: # vertex groups are always replaced as a whole mesh_vertex_groups = VertexGroups.from_mesh( struct).to_array_sequence() proxy_vertex_groups: ArrayGroup = self._arrays.get( "vertex_groups", []) if mesh_vertex_groups != proxy_vertex_groups: diff._arrays["vertex_groups"] = mesh_vertex_groups # force Object update. This requires that Object updates are processed later, which seems to be # the order they are listed in Depsgraph.updates context.visit_state.dirty_vertex_groups.add( struct.mixer_uuid) properties = context.synchronized_properties.properties(struct) properties = specifics.conditional_properties( struct, properties) for k, member_property in properties: try: member = getattr(struct, k) except AttributeError: logger.warning("diff: unknown attribute ...") logger.warning( f"... {context.visit_state.display_path()}.{k}") continue proxy_data = self._data.get(k) delta = diff_attribute(member, k, member_property, proxy_data, context) if delta is not None: diff._data[k] = delta finally: if prop is not None: context.visit_state.path.pop() if len(diff._data) or len(diff._arrays): return DeltaUpdate(diff) return None
def _diff(self, struct: T.Struct, key: str, prop: T.Property, context: Context, diff: MeshProxy) -> Optional[DeltaUpdate]: try: # If any mesh buffer change requires a clear geometry on the receiver, send all buffers # This is the case if a face is separated from a cube. The number of vertices is unchanged # but the number of faces changes, which requires the receiver to call Mesh.clear_geometry(), # hence to reload tall the geometry, including parts that were unchanged. # As an optimized alternative, it should be possible not to send the unchanged arrays # but have MeshProxy.apply() to reload unchanged buffers from in-memory copies. force_send_all = proxy_requires_clear_geometry(self, struct) if force_send_all: logger.debug("requires_clear for %s", struct) if prop is not None: context.visit_state.path.append(key) properties = context.synchronized_properties.properties(struct) properties = specifics.conditional_properties(struct, properties) for k, member_property in properties: try: member = getattr(struct, k) except AttributeError: logger.warning(f"diff: unknown attribute {k} in {struct}") continue proxy_data = self._data.get(k) force_diff = force_send_all and k in mesh_resend_on_clear try: if force_diff: context.visit_state.scratchpad["force_soa_diff"] = True delta = diff_attribute(member, k, member_property, proxy_data, context) if delta is not None: diff._data[k] = delta elif force_send_all and k in mesh_resend_on_clear: diff._data[k] = DeltaUpdate.deep_wrap(proxy_data) finally: if force_diff: del context.visit_state.scratchpad["force_soa_diff"] finally: if prop is not None: context.visit_state.path.pop() if len(diff._data): return DeltaUpdate(diff) return None
def _diff(self, struct: T.Struct, key: str, prop: T.Property, context: Context, diff: StructProxy) -> Optional[DeltaUpdate]: # PERF accessing the properties from the synchronized_properties is **far** cheaper that iterating over # _data and the getting the properties with # member_property = struct.bl_rna.properties[k] # line to which py-spy attributes 20% of the total diff ! try: if prop is not None: context.visit_state.path.append(key) properties = context.synchronized_properties.properties(struct) properties = specifics.conditional_properties(struct, properties) for k, member_property in properties: # TODO in test_differential.StructDatablockRef.test_remove # target et a scene, k is world and v (current world value) is None # so diff fails. v should be a BpyIDRefNoneProxy # make a difference between None value and no member try: member = getattr(struct, k) except AttributeError: logger.warning(f"diff: unknown attribute {k} in {struct}") continue proxy_data = self._data.get(k) delta = diff_attribute(member, k, member_property, proxy_data, context) if delta is not None: diff._data[k] = delta finally: if prop is not None: context.visit_state.path.pop() # TODO detect media updates (reload(), and attach a media descriptor to diff) # difficult ? # if anything has changed, wrap the hollow proxy in a DeltaUpdate. This may be superfluous but # it is homogenous with additions and deletions if len(diff._data): return DeltaUpdate(diff) return None
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