Progress on deserialization
This commit is contained in:
parent
8f4d7d5684
commit
7af137867b
121
dumper.py
121
dumper.py
@ -43,9 +43,9 @@ def deserialize_nodes_into_node_tree(data: dict, node_tree: bpy.types.NodeTree):
|
|||||||
|
|
||||||
class Serializer(ABC):
|
class Serializer(ABC):
|
||||||
"""
|
"""
|
||||||
Base Serializer class, from which other Serializer are derived.
|
Base Serializer class.
|
||||||
`bl_pointers_ref` corresponds to a mutable dict passed through serialize/deserialize
|
`bl_pointers_ref` corresponds to a mutable dict passed through serialize/deserialize
|
||||||
functions, containing Blender object pointers from the current operation from relinking.
|
functions, containing a map of Blender pointers IDs and their corresponding objects.
|
||||||
"""
|
"""
|
||||||
# Whitelisted properties, applied after the blacklist
|
# Whitelisted properties, applied after the blacklist
|
||||||
prop_whitelist = None
|
prop_whitelist = None
|
||||||
@ -58,9 +58,9 @@ class Serializer(ABC):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_serializer_map(cls) -> dict[type[bpy.types.bpy_struct], type[Serializer]]:
|
def get_serializer_map(cls) -> dict[type[bpy.types.bpy_struct], type[Serializer]]:
|
||||||
"""Store the Serializer Map in a class variable for simple caching"""
|
"""Get the serializer map, stored in a class variable for simple caching"""
|
||||||
if not cls.serializer_map:
|
if not cls.serializer_map:
|
||||||
for subclass in utils.all_subclasses(Dumper):
|
for subclass in utils.all_subclasses(Serializer):
|
||||||
assert hasattr(subclass, "bl_type")
|
assert hasattr(subclass, "bl_type")
|
||||||
cls.serializer_map[subclass.bl_type] = subclass
|
cls.serializer_map[subclass.bl_type] = subclass
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class Serializer(ABC):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_serializer(cls, bl_object: bpy.types.bpy_struct) -> type[Serializer]:
|
def get_serializer(cls, bl_object: bpy.types.bpy_struct) -> type[Serializer]:
|
||||||
"""Get the closest corresponding dumper for a given Blender object using its MRO"""
|
"""Get the closest corresponding serializer for a given Blender object using its MRO"""
|
||||||
serializer_map = cls.get_serializer_map()
|
serializer_map = cls.get_serializer_map()
|
||||||
|
|
||||||
bl_type = type(bl_object.bl_rna.type_recast())
|
bl_type = type(bl_object.bl_rna.type_recast())
|
||||||
@ -104,7 +104,7 @@ class Serializer(ABC):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def serialize(cls, obj: bpy.types.bpy_struct | Any, bl_pointers_ref: dict) -> dict:
|
def serialize(cls, obj: bpy.types.bpy_struct | Any, bl_pointers_ref: dict) -> dict:
|
||||||
"""Base Serialize method, overridded by subclasses"""
|
"""Base serialization method, overridden by subclasses"""
|
||||||
|
|
||||||
# Early recursive return case
|
# Early recursive return case
|
||||||
# TODO: Ported as is, check the heuristics, (if there are more or attributes type to add)
|
# TODO: Ported as is, check the heuristics, (if there are more or attributes type to add)
|
||||||
@ -179,25 +179,16 @@ class Serializer(ABC):
|
|||||||
|
|
||||||
return serializer.serialize(obj, bl_pointers_ref)
|
return serializer.serialize(obj, bl_pointers_ref)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def construct_bl_object(cls, data: dict):
|
|
||||||
"""Abstract method for Serializer specificaiton to provide a way to construct their object"""
|
|
||||||
print("DEBUG: construct_bl_object called on Base Serializer, shouldn't happen")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# --- Deserialization ---
|
# --- Deserialization ---
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def deserialize(cls, data: dict, target_obj: bpy.types.bpy_struct, bl_pointers_ref: dict):
|
def deserialize(cls, data: dict, target_obj: bpy.types.bpy_struct, bl_pointers_ref: dict):
|
||||||
if target_obj is None:
|
"""
|
||||||
target_obj = cls.construct_bl_object(data)
|
Base deserialization method.
|
||||||
|
Deserialize data into a specific Blender object, creating sub-objects as needed.
|
||||||
if target_obj is None:
|
Partial data may be provided, in which case, fields not specified will be left to default.
|
||||||
print("DEBUG: No method to construct object")
|
"""
|
||||||
# Failed to construct object/no speciailization to construct object
|
|
||||||
return None
|
|
||||||
|
|
||||||
if (kit_ptr := data.get("_kit_ptr", None)):
|
if (kit_ptr := data.get("_kit_ptr", None)):
|
||||||
bl_pointers_ref[kit_ptr] = target_obj
|
bl_pointers_ref[kit_ptr] = target_obj
|
||||||
@ -211,13 +202,8 @@ class Serializer(ABC):
|
|||||||
target_bl_prop = BlenderProperty(rep=target_obj.bl_rna.properties[stored_key],
|
target_bl_prop = BlenderProperty(rep=target_obj.bl_rna.properties[stored_key],
|
||||||
attr=getattr(target_obj, stored_key))
|
attr=getattr(target_obj, stored_key))
|
||||||
|
|
||||||
# Skip the property if it's read-only
|
|
||||||
if target_bl_prop.rep.is_readonly:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Unlike serialization, there's no property array case, as they are just directly assigned
|
|
||||||
|
|
||||||
# Collection case
|
# Collection case
|
||||||
|
# Unlike serialization, there's no property array case, as they are just directly assigned
|
||||||
if isinstance(target_bl_prop.attr, bpy.types.bpy_prop_collection):
|
if isinstance(target_bl_prop.attr, bpy.types.bpy_prop_collection):
|
||||||
cls.deserialize_collection(stored_value, target_bl_prop.attr, bl_pointers_ref)
|
cls.deserialize_collection(stored_value, target_bl_prop.attr, bl_pointers_ref)
|
||||||
continue
|
continue
|
||||||
@ -225,9 +211,14 @@ class Serializer(ABC):
|
|||||||
value_to_set = stored_value
|
value_to_set = stored_value
|
||||||
|
|
||||||
# Pointer case
|
# Pointer case
|
||||||
|
# Dereference the value if its already present in the pointers_ref map
|
||||||
if target_bl_prop.rep.type == "POINTER":
|
if target_bl_prop.rep.type == "POINTER":
|
||||||
value_to_set = cls.deserialize_pointer(stored_value, target_bl_prop.attr, bl_pointers_ref)
|
value_to_set = cls.deserialize_pointer(stored_value, target_bl_prop.attr, bl_pointers_ref)
|
||||||
|
|
||||||
|
# Skip setting the property if it's read-only
|
||||||
|
if target_bl_prop.rep.is_readonly:
|
||||||
|
continue
|
||||||
|
|
||||||
# Assign the property
|
# Assign the property
|
||||||
setattr(target_obj, stored_key, value_to_set)
|
setattr(target_obj, stored_key, value_to_set)
|
||||||
|
|
||||||
@ -235,6 +226,13 @@ class Serializer(ABC):
|
|||||||
if hasattr(target_bl_prop.attr, "update"):
|
if hasattr(target_bl_prop.attr, "update"):
|
||||||
target_bl_prop.attr.update()
|
target_bl_prop.attr.update()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def construct_bl_object(cls, data: dict):
|
||||||
|
"""Abstract method to construct a Serializer's specific Blender Object"""
|
||||||
|
print("DEBUG: construct_bl_object called on Base Serializer, shouldn't happen")
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialize_collection(cls, stored_value: Any, bl_coll: bpy.types.bpy_prop_collection, bl_pointers_ref: dict):
|
def deserialize_collection(cls, stored_value: Any, bl_coll: bpy.types.bpy_prop_collection, bl_pointers_ref: dict):
|
||||||
# Static collection case
|
# Static collection case
|
||||||
@ -242,71 +240,74 @@ class Serializer(ABC):
|
|||||||
cls.sub_deserialize(stored_value, bl_coll, bl_pointers_ref)
|
cls.sub_deserialize(stored_value, bl_coll, bl_pointers_ref)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# We need to call the collection "new" function, parse and construct its parameters
|
||||||
# TODO: Code pasted as is, review later
|
|
||||||
new_func = bl_coll.bl_rna.functions["new"]
|
new_func = bl_coll.bl_rna.functions["new"]
|
||||||
|
|
||||||
for i, value in enumerate(stored_value):
|
for i, value in enumerate(stored_value):
|
||||||
if value.get("_new"):
|
# Using a dictionary of {parameter: parameter_value}
|
||||||
params = value["_new"]
|
default_new_func_params = {
|
||||||
else:
|
|
||||||
params = {
|
|
||||||
k: value.get(k, utils.get_bl_default(v))
|
k: value.get(k, utils.get_bl_default(v))
|
||||||
for k, v in new_func.parameters.items()[:-1]
|
for k, v in new_func.parameters.items()[:-1]
|
||||||
}
|
}
|
||||||
|
new_func_params = value.get("_kit_new_params", default_new_func_params)
|
||||||
|
|
||||||
# Replace arg pointer with bl object
|
solved_all_pointers = True
|
||||||
valid_pointers = True
|
|
||||||
for param in bl_coll.bl_rna.functions["new"].parameters:
|
for param in bl_coll.bl_rna.functions["new"].parameters:
|
||||||
if param.identifier not in params or param.type != "POINTER":
|
if param.identifier not in new_func_params or param.type != "POINTER":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# TODO: It might be possible to abstract this into deserialize_pointer somehow
|
pointer_id = param[param.identifier]
|
||||||
pointer_id = params[param.identifier]
|
|
||||||
if bl_object := bl_pointers_ref.get(pointer_id):
|
if bl_object := bl_pointers_ref.get(pointer_id):
|
||||||
params[param.identifier] = bl_object
|
new_func_params[param.identifier] = bl_object
|
||||||
else:
|
else:
|
||||||
print(f"No Pointer found for param {param.identifier} of {bl_coll}")
|
print(f"No pointer found for param {param.identifier} of new function of {bl_coll}")
|
||||||
valid_pointers = False
|
solved_all_pointers = False
|
||||||
|
|
||||||
if not valid_pointers:
|
# Bail out if we fail to solve all pointers (TODO: I'm guessing this causes a runtimerror, but double check)
|
||||||
|
if not solved_all_pointers:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# TODO: Ugly bad :(
|
print("Calling BL collection new with the following parameters")
|
||||||
try:
|
print(new_func_params)
|
||||||
item = bl_coll.new(**params)
|
|
||||||
except RuntimeError as e:
|
|
||||||
try:
|
|
||||||
item = bl_coll[i]
|
|
||||||
except IndexError as e:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
# Creates a collection item, type from the collection type, no need to manually construct
|
||||||
|
collection_item = bl_coll.new(**new_func_params)
|
||||||
|
deserializer = cls.get_serializer(collection_item)
|
||||||
|
|
||||||
|
# Recursively deserialize into the newly constructured object
|
||||||
|
cls.deserialize(value, collection_item, bl_pointers_ref)
|
||||||
|
|
||||||
|
# Old code guarded by a RuntimeError before, investigate later
|
||||||
|
# Static collection case, would need to check how to detect this.
|
||||||
|
collection_item = bl_coll[i]
|
||||||
|
|
||||||
|
# TODO: The target_bl_prop_attr terminology is unclear
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialize_pointer(cls, stored_value: Any, target_bl_prop_attr: bpy.types.bpy_struct, bl_pointers_ref: dict):
|
def deserialize_pointer(cls, stored_value: Any, target_bl_prop_attr: bpy.types.bpy_struct, bl_pointers_ref: dict):
|
||||||
if stored_value is None:
|
if stored_value is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Actual pointer
|
# Actual existing pointer, dereference and return
|
||||||
if isinstance(stored_value, int):
|
if isinstance(stored_value, int):
|
||||||
if stored_value not in bl_pointers_ref:
|
if stored_value not in bl_pointers_ref:
|
||||||
print("DEBUG: Pointer reference hasn't been loaded yet")
|
print("DEBUG: Pointer reference hasn't been loaded yet")
|
||||||
# Obtain a reference to a previously dereferenced object
|
# Obtain a reference to a previously dereferenced object
|
||||||
return bl_pointers_ref[stored_value]
|
return bl_pointers_ref[stored_value]
|
||||||
|
|
||||||
# Create the object by passing it to the sub-serializer
|
# Pointer doesn't exist yet, create it if it doesn't exist yet, store it, and return its object
|
||||||
cls.sub_deserialize(stored_value, target_bl_prop_attr, bl_pointers_ref)
|
deserializer = cls.get_serializer(target_bl_prop_attr)
|
||||||
|
|
||||||
|
# Create the Blender object if it doesn't exist yet
|
||||||
|
if target_bl_prop_attr is None:
|
||||||
|
target_bl_prop_attr = cls.construct_bl_object(stored_value)
|
||||||
|
|
||||||
|
# Recursively deserialize into the target object
|
||||||
|
deserializer.deserialize(stored_value, target_bl_prop_attr, bl_pointers_ref)
|
||||||
|
|
||||||
bl_pointers_ref[stored_value["_kit_ptr"]] = target_bl_prop_attr
|
bl_pointers_ref[stored_value["_kit_ptr"]] = target_bl_prop_attr
|
||||||
|
|
||||||
return target_bl_prop_attr
|
return target_bl_prop_attr
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sub_deserialize(cls, data: dict, target_bl_obj: bpy.types.bpy_struct, bl_pointers_ref: dict):
|
|
||||||
# Get the deserializer for the corresponding target_bl_prop
|
|
||||||
# Introspects on its type, recreateas the object even if its None
|
|
||||||
deserializer = cls.get_serializer(target_bl_obj)
|
|
||||||
|
|
||||||
return deserializer.deserialize(data, bl_pointers_ref, target_bl_obj)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_data_to_deserialize(cls, data: dict, target_obj: bpy.types.bpy_struct=None):
|
def get_data_to_deserialize(cls, data: dict, target_obj: bpy.types.bpy_struct=None):
|
||||||
props_to_deserialize = cls.get_serialized_properties(target_obj)
|
props_to_deserialize = cls.get_serialized_properties(target_obj)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user