def _diff(self, struct: T.Object, key: str, prop: T.Property, context: Context, diff: StructProxy) -> Optional[Delta]: from mixer.blender_data.attributes import diff_attribute must_replace = False data_datablock = struct.data if data_datablock is not None: dirty_vertex_groups = data_datablock.mixer_uuid in context.visit_state.dirty_vertex_groups # Replace the whole Object. Otherwise we would have to merge a DeltaReplace for vertex_groups # and a DeltaUpdate for the remaining items logger.debug(f"_diff: {struct} dirty vertex group: replace") must_replace |= dirty_vertex_groups if not must_replace: # Parenting with ctrl-P generates a Delta with parent, local_matrix and matrix_parent_inverse. # Applying this delta causes a position shift in the parented object. A full replace fixes the problem. # Not that parenting with just updating the parent property in the property panel does not cause # the same problem parent_property = struct.bl_rna.properties["parent"] parent_delta = diff_attribute(struct.parent, "parent", parent_property, self._data["parent"], context) must_replace |= parent_delta is not None if must_replace: diff.load(struct, context) return DeltaReplace(diff) else: return super()._diff(struct, key, prop, context, diff)
def load( self, bl_collection: T.bpy_prop_collection, key: Union[int, str], bl_collection_property: T.Property, context: Context, ): if len(bl_collection) == 0: self._data.clear() return self try: context.visit_state.path.append(key) # no keys means it is a sequence. However bl_collection.items() returns [(index, item)...] is_sequence = not bl_collection.keys() if is_sequence: # easier for the encoder to always have a dict self._data = { MIXER_SEQUENCE: [ StructProxy.make(v).load(v, i, context) for i, v in enumerate(bl_collection.values()) ] } else: self._data = {k: StructProxy().load(v, k, context) for k, v in bl_collection.items()} finally: context.visit_state.path.pop() return self
def pre_save_struct(proxy: StructProxy, target: T.bpy_struct, context: Context) -> T.bpy_struct: """Process attributes that must be saved first""" if isinstance(target, T.ColorManagedViewSettings): use_curve_mapping = proxy.data("use_curve_mapping") if use_curve_mapping: target.use_curve_mapping = True return target
def _proxy_factory(attr): if isinstance(attr, T.ID) and not attr.is_embedded_data: from mixer.blender_data.datablock_ref_proxy import DatablockRefProxy return DatablockRefProxy() elif attr is None: from mixer.blender_data.misc_proxies import NonePtrProxy return NonePtrProxy() else: return StructProxy()
def load( self, bl_collection: T.bpy_prop_collection, key: Union[int, str], bl_collection_property: T.Property, context: Context, ): context.visit_state.path.append(key) try: self._sequence = [ StructProxy.make(v).load(v, i, context) for i, v in enumerate(bl_collection.values()) ] finally: context.visit_state.path.pop() return self
def test_non_existing(self): # test_end_to_end.TestWorld.test_non_existing world = bpy.data.worlds[0] self.diff.diff(self.bpy_data_proxy, safe_properties) sent_ids = {} sent_ids.update({("worlds", world.name): world}) changeset = self.bpy_data_proxy.update(self.diff, {}, False, safe_properties) creations = changeset.creations # avoid clash on restore world.name = world.name + "_bak" codec = Codec() for update in creations: key = (update.collection_name, update.data("name")) sent_id = sent_ids.get(key) if sent_id is None: continue # pretend it is a new one update._datablock_uuid += "_new" # create a property on the send proxy and test that is does not fail on the receiver # property on ID update._data["does_not_exist_property"] = "" update._data["does_not_exist_struct"] = StructProxy() update._data["does_not_exist_ID"] = DatablockProxy() encoded = codec.encode(update) # sender side ####################### # receiver side decoded = codec.decode(encoded) created, _ = self.bpy_data_proxy.create_datablock(decoded) self.assertEqual(created, sent_id)
def deep_wrap(cls, proxy: StructProxy) -> DeltaUpdate: """Recursively wraps proxy members in DeltaUpdate items.""" p = proxy.__class__() for k, v in proxy._data.items(): p.data[k] = DeltaUpdate.deep_wrap(v) return cls(p)
def read_attribute(attr: Any, key: Union[int, str], attr_property: T.Property, context: Context): """ Load a property into a python object of the appropriate type, be it a Proxy or a native python object """ attr_type = type(attr) if is_builtin(attr_type): return attr if is_vector(attr_type): return list(attr) if is_matrix(attr_type): return [list(col) for col in attr.col] # We have tested the types that are usefully reported by the python binding, now harder work. # These were implemented first and may be better implemented with the bl_rna property of the parent struct # TODO flatten if attr_type == T.bpy_prop_array: return list(attr) try: context.visit_state.recursion_guard.push(attr_property.identifier) if attr_type == T.bpy_prop_collection: if isinstance(attr_property.fixed_type, bpy.types.ID): from mixer.blender_data.datablock_collection_proxy import DatablockRefCollectionProxy return DatablockRefCollectionProxy().load(attr, key, context) elif is_soable_collection(attr_property): from mixer.blender_data.aos_proxy import AosProxy return AosProxy().load(attr, key, attr_property, context) else: from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy.make(attr_property).load( attr, key, attr_property, context) # TODO merge with previous case if isinstance(attr_property, T.CollectionProperty): from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy().load(attr, key, attr_property, context) bl_rna = attr_property.bl_rna if bl_rna is None: logger.warning("Not implemented: attribute %s", attr) return None if issubclass(attr_type, T.PropertyGroup): from mixer.blender_data.struct_proxy import StructProxy return StructProxy().load(attr, key, context) if issubclass(attr_type, T.ID): if attr.is_embedded_data: from mixer.blender_data.datablock_proxy import DatablockProxy return DatablockProxy.make(attr_property).load( attr, key, context) else: from mixer.blender_data.datablock_ref_proxy import DatablockRefProxy return DatablockRefProxy().load(attr, key, context) if issubclass(attr_type, T.bpy_struct): from mixer.blender_data.struct_proxy import StructProxy return StructProxy().load(attr, key, context) if attr is None and isinstance(attr_property, T.PointerProperty): from mixer.blender_data.misc_proxies import NonePtrProxy return NonePtrProxy() logger.error( f"Unsupported attribute {attr_type} {attr_property} {attr_property.fixed_type} at {context.visit_state.datablock_proxy._class_name}.{context.visit_state.path}.{attr_property.identifier}" ) finally: context.visit_state.recursion_guard.pop()
def read_attribute(attr: Any, key: Union[int, str], attr_property: T.Property, parent: T.bpy_struct, context: Context): """ Load a property into a python object of the appropriate type, be it a Proxy or a native python object """ try: return _read_builtin(attr) except _NotBuiltin: pass if isinstance(attr, set): from mixer.blender_data.misc_proxies import SetProxy return SetProxy().load(attr) context.visit_state.push(attr_property, key) try: from mixer.blender_data.misc_proxies import PtrToCollectionItemProxy attr_type = type(attr) if attr_type == T.bpy_prop_collection: if hasattr(attr, "bl_rna") and isinstance( attr.bl_rna, (type(T.CollectionObjects.bl_rna), type(T.CollectionChildren.bl_rna))): from mixer.blender_data.datablock_collection_proxy import DatablockRefCollectionProxy return DatablockRefCollectionProxy().load(attr, context) elif is_soable_collection(attr_property): from mixer.blender_data.aos_proxy import AosProxy return AosProxy().load(attr, attr_property, context) else: # This code path is taken for collections that have an rna and collections that do not # There should probably be different proxies for collection with and without rna. # See comment in add_element() from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy.make(attr_property).load( attr, context) # TODO merge with previous case if isinstance(attr_property, T.CollectionProperty): from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy().load(attr, context) bl_rna = attr_property.bl_rna if bl_rna is None: logger.error("read_attribute: no implementation for ...") logger.error( f"... {context.visit_state.display_path()}.{key} (type: {type(attr)})" ) return None if issubclass(attr_type, T.PropertyGroup): from mixer.blender_data.struct_proxy import StructProxy return StructProxy.make(attr).load(attr, context) if issubclass(attr_type, T.ID): if attr.is_embedded_data: # Embedded datablocks are loaded as StructProxy and DatablockProxy is reserved # for standalone datablocks from mixer.blender_data.struct_proxy import StructProxy return StructProxy.make(attr).load(attr, context) else: # Standalone databocks are loaded from DatablockCollectionProxy, so we can only encounter # datablock references here from mixer.blender_data.datablock_ref_proxy import DatablockRefProxy return DatablockRefProxy().load(attr, context) proxy = PtrToCollectionItemProxy.make(type(parent), key) if proxy is not None: return proxy.load(attr) if issubclass(attr_type, T.bpy_struct): from mixer.blender_data.struct_proxy import StructProxy return StructProxy.make(attr).load(attr, context) if attr is None: from mixer.blender_data.misc_proxies import NonePtrProxy return NonePtrProxy() logger.error("read_attribute: no implementation for ...") logger.error( f"... {context.visit_state.display_path()}.{key} (type: {type(attr)})" ) finally: context.visit_state.pop()
def read_attribute(attr: Any, key: Union[int, str], attr_property: T.Property, context: Context): """ Load a property into a python object of the appropriate type, be it a Proxy or a native python object """ if isinstance(attr, _builtin_types): return attr attr_type = type(attr) if is_vector(attr_type): return list(attr) if is_matrix(attr_type): return [list(col) for col in attr.col] if isinstance(attr, set): from mixer.blender_data.misc_proxies import SetProxy return SetProxy().load(attr) # We have tested the types that are usefully reported by the python binding, now harder work. # These were implemented first and may be better implemented with the bl_rna property of the parent struct # TODO flatten if attr_type == T.bpy_prop_array: return list(attr) context.visit_state.recursion_guard.push(attr_property.identifier) try: from mixer.blender_data.misc_proxies import PtrToCollectionItemProxy if attr_type == T.bpy_prop_collection: if hasattr(attr, "bl_rna") and isinstance( attr.bl_rna, (type(T.CollectionObjects.bl_rna), type(T.CollectionChildren.bl_rna))): from mixer.blender_data.datablock_collection_proxy import DatablockRefCollectionProxy return DatablockRefCollectionProxy().load(attr, key, context) elif is_soable_collection(attr_property): from mixer.blender_data.aos_proxy import AosProxy return AosProxy().load(attr, key, attr_property, context) else: from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy.make(attr_property).load( attr, key, attr_property, context) # TODO merge with previous case if isinstance(attr_property, T.CollectionProperty): from mixer.blender_data.struct_collection_proxy import StructCollectionProxy return StructCollectionProxy().load(attr, key, attr_property, context) bl_rna = attr_property.bl_rna if bl_rna is None: logger.error("read_attribute: no implementation for ...") logger.error( f"... {context.visit_state.display_path()}.{key} (type: {type(attr)})" ) return None if issubclass(attr_type, T.PropertyGroup): from mixer.blender_data.struct_proxy import StructProxy return StructProxy().load(attr, key, context) if issubclass(attr_type, T.ID): if attr.is_embedded_data: # Embedded datablocks are loaded as StructProxy and DatablockProxy is reserved # for standalone datablocks from mixer.blender_data.struct_proxy import StructProxy return StructProxy().load(attr, key, context) else: # Standalone databocks are loaded from DatablockCollectionProxy, so we can only encounter # datablock references here from mixer.blender_data.datablock_ref_proxy import DatablockRefProxy return DatablockRefProxy().load(attr, key, context) proxy = PtrToCollectionItemProxy.make(attr_type, key) if proxy: return proxy.load(attr) if issubclass(attr_type, T.bpy_struct): from mixer.blender_data.struct_proxy import StructProxy return StructProxy().load(attr, key, context) if attr is None: from mixer.blender_data.misc_proxies import NonePtrProxy return NonePtrProxy() logger.error("read_attribute: no implementation for ...") logger.error( f"... {context.visit_state.display_path()}.{key} (type: {type(attr)})" ) finally: context.visit_state.recursion_guard.pop()
def test_skip_ShaderNodeTree(self): # noqa N802 world = D.worlds["World"] proxy = StructProxy().load(world, self._context) self.assertTrue("color" in proxy._data)