diff --git a/packages/tree_clipper/src/tree_clipper/import_nodes.py b/packages/tree_clipper/src/tree_clipper/import_nodes.py index 007c86d..d1f5dde 100644 --- a/packages/tree_clipper/src/tree_clipper/import_nodes.py +++ b/packages/tree_clipper/src/tree_clipper/import_nodes.py @@ -424,6 +424,21 @@ def deserializer( if self.debug_prints: print(f"{prop_from_root.to_str()}: missing, assume not set") continue + + # https://github.com/Algebraic-UG/tree_clipper/issues/161 + if ( + prop.type in [PROP_TYPE_POINTER, PROP_TYPE_COLLECTION] + and prop.fixed_type.__module__ != "_bpy_types" + ): + self.report.warnings.append( + f"""This property is missing in serialization. +It appears to be from a third-party addon: {prop.fixed_type.__module__} + +Tree Clipper is skipping it. +{prop_from_root.to_str()}""" + ) + continue + self._error_out( getter=getter, reason="missing property in serialization", diff --git a/packages/tree_clipper/tests/test_third_party_attributes.py b/packages/tree_clipper/tests/test_third_party_attributes.py new file mode 100644 index 0000000..82d76be --- /dev/null +++ b/packages/tree_clipper/tests/test_third_party_attributes.py @@ -0,0 +1,132 @@ +import bpy + +from tree_clipper.import_nodes import ImportIntermediate, ImportParameters +from tree_clipper.specific_handlers import BUILT_IN_EXPORTER, BUILT_IN_IMPORTER + +from .util import ( + make_test_node_tree, + round_trip_without_external, + export_to_string, + diff_exports, +) + + +# just to pick any node that isn't specifically handled +# that is the "worst case" scenario +def test_third_party_node_type_to_test(): + assert bpy.types.NodeGroupInput not in BUILT_IN_EXPORTER + assert bpy.types.NodeGroupInput not in BUILT_IN_IMPORTER + + +class TreeClipperTest(bpy.types.PropertyGroup): + foo: bpy.props.StringProperty() # ty:ignore + + +def register_third_party_properties(): + # simple properties + bpy.types.NodeGroupInput.tree_clipper_test_bool = bpy.props.BoolProperty() + bpy.types.NodeGroupInput.tree_clipper_test_enum = bpy.props.EnumProperty( + items=[("foo",) * 3] + ) + bpy.types.NodeGroupInput.tree_clipper_test_float = bpy.props.FloatProperty() + bpy.types.NodeGroupInput.tree_clipper_test_int = bpy.props.IntProperty() + bpy.types.NodeGroupInput.tree_clipper_test_string = bpy.props.StringProperty() + + # pointer property + bpy.utils.register_class(TreeClipperTest) + bpy.types.NodeGroupInput.tree_clipper_test_pointer = bpy.props.PointerProperty( + type=TreeClipperTest + ) + + # collection property + bpy.types.NodeGroupInput.tree_clipper_test_collection = ( + bpy.props.CollectionProperty(type=TreeClipperTest) + ) + + +def unregister_third_party_properties(): + del bpy.types.NodeGroupInput.tree_clipper_test_bool + del bpy.types.NodeGroupInput.tree_clipper_test_enum + del bpy.types.NodeGroupInput.tree_clipper_test_float + del bpy.types.NodeGroupInput.tree_clipper_test_int + del bpy.types.NodeGroupInput.tree_clipper_test_string + del bpy.types.NodeGroupInput.tree_clipper_test_pointer + del bpy.types.NodeGroupInput.tree_clipper_test_collection + + bpy.utils.unregister_class(TreeClipperTest) + + +def test_third_party_both_have_it(): + try: + register_third_party_properties() + + tree = make_test_node_tree() + tree.nodes.new("NodeGroupInput") + + round_trip_without_external(tree.name) + + finally: + unregister_third_party_properties() + + +def test_third_party_only_exporter_has_it(): + try: + register_third_party_properties() + + tree = make_test_node_tree() + tree.nodes.new("NodeGroupInput") + original_name = tree.name + + before = export_to_string(original_name) + + unregister_third_party_properties() + + import_intermediate = ImportIntermediate(string=before) + import_report = import_intermediate.import_all( + parameters=ImportParameters( + specific_handlers=BUILT_IN_IMPORTER, + debug_prints=True, + ) + ) + + after = export_to_string(import_report.renames_node_group[original_name]) + + try: + diff_exports(before=before, import_report=import_report, after=after) + assert False, "diff should be the properties" + except: + pass + + register_third_party_properties() + finally: + unregister_third_party_properties() + + +def test_third_party_only_impoter_has_it(): + try: + tree = make_test_node_tree() + tree.nodes.new("NodeGroupInput") + original_name = tree.name + + before = export_to_string(original_name) + + register_third_party_properties() + + import_intermediate = ImportIntermediate(string=before) + import_report = import_intermediate.import_all( + parameters=ImportParameters( + specific_handlers=BUILT_IN_IMPORTER, + debug_prints=True, + ) + ) + + after = export_to_string(import_report.renames_node_group[original_name]) + + try: + diff_exports(before=before, import_report=import_report, after=after) + assert False, "diff should be the properties" + except: + pass + + finally: + unregister_third_party_properties() diff --git a/packages/tree_clipper/tests/util.py b/packages/tree_clipper/tests/util.py index 92aa044..bad43ce 100644 --- a/packages/tree_clipper/tests/util.py +++ b/packages/tree_clipper/tests/util.py @@ -78,26 +78,27 @@ def diff_exports( assert diff == {} -def round_trip_without_external(original_name: str): - def export_to_string(name: str) -> str: - export_intermediate = ExportIntermediate( - parameters=ExportParameters( - is_material=False, - name=name, - specific_handlers=BUILT_IN_EXPORTER, - export_sub_trees=True, - debug_prints=True, - write_from_roots=False, - ) +def export_to_string(name: str) -> str: + export_intermediate = ExportIntermediate( + parameters=ExportParameters( + is_material=False, + name=name, + specific_handlers=BUILT_IN_EXPORTER, + export_sub_trees=True, + debug_prints=True, + write_from_roots=False, ) + ) - while export_intermediate.step(): - pass + while export_intermediate.step(): + pass + + string = export_intermediate.export_to_str(compress=False, json_indent=4) + print(string) + return string - string = export_intermediate.export_to_str(compress=False, json_indent=4) - print(string) - return string +def round_trip_without_external(original_name: str): before = export_to_string(original_name) import_intermediate = ImportIntermediate(string=before)