File: bl_rna_paths.py

package info (click to toggle)
blender 5.0.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 329,128 kB
  • sloc: cpp: 2,489,823; python: 349,859; ansic: 261,364; xml: 2,103; sh: 999; javascript: 317; makefile: 193
file content (142 lines) | stat: -rw-r--r-- 6,090 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0


import bpy
import unittest


def process_rna_struct(self, struct, rna_path):
    # These paths are currently known failures of `path_from_id`.
    KNOWN_FAILURES = {
        "render.views[\"left\"]",
        "render.views[\"right\"]",
        "uv_layers[\"UVMap\"]",
        "uv_layers[\"UVMap\"]",
    }
    SKIP_KNOWN_FAILURES = True

    def filter_prop_iter(struct):
        for p in struct.bl_rna.properties:
            # Internal rna meta-data, not expected to support RNA paths generation.
            if p.identifier in {"bl_rna", "rna_type"}:
                continue
            # Only these types can point to sub-structs.
            if p.type not in {'POINTER', 'COLLECTION'}:
                continue
            # TODO: Dynamic typed pointer/collection properties are ignored for now.
            if not p.fixed_type:
                continue
            if bpy.types.ID.bl_rna in {p.fixed_type, p.fixed_type.base}:
                continue
            yield p

    def validate_rna_path(self, struct, rna_path_root, p, p_data, p_keys=[]):
        if not p_data:
            return None
        rna_path_references = [rna_path_root + "." + p.identifier if rna_path_root else p.identifier]
        if p_keys:
            rna_path_reference = rna_path_references[0]
            rna_path_references = [rna_path_reference + '[' + p_key + ']' for p_key in p_keys]
        try:
            rna_path_generated = p_data.path_from_id()
        except ValueError:
            return None
        if SKIP_KNOWN_FAILURES and rna_path_generated in KNOWN_FAILURES:
            return None
        self.assertTrue((rna_path_generated in rna_path_references) or ("..." in rna_path_generated),
                        msg=f"\"{rna_path_generated}\" (from {struct}) failed to match expected paths {rna_path_references}")
        return rna_path_generated

    for p in filter_prop_iter(struct):
        if p.type == 'COLLECTION':
            for p_idx, (p_key, p_data) in enumerate(getattr(struct, p.identifier).items()):
                p_keys = ['"' + p_key + '"', str(p_idx)] if isinstance(p_key, str) else [str(p_key), str(p_idx)]
                rna_path_sub = validate_rna_path(self, struct, rna_path, p, p_data, p_keys)
                if rna_path_sub is not None:
                    process_rna_struct(self, p_data, rna_path_sub)
        else:
            assert (p.type == 'POINTER')
            p_data = getattr(struct, p.identifier)
            rna_path_sub = validate_rna_path(self, struct, rna_path, p, p_data)
            if rna_path_sub is not None:
                process_rna_struct(self, p_data, rna_path_sub)


# Walk over all exposed RNA properties in factory startup file, and compare generated RNA paths to 'actual' paths.
class TestRnaPaths(unittest.TestCase):
    def test_paths_generation(self):
        bpy.ops.wm.read_factory_settings()
        for data_block in bpy.data.user_map().keys():
            process_rna_struct(self, data_block, "")


# Walk over all exposed RNA Collection and Pointer properties in factory startup file, and compare generated RNA
# ancestors to 'actual' ones.
class TestRnaAncestors(unittest.TestCase):
    def process_rna_struct(self, struct, ancestors):
        def filter_prop_iter(struct):
            # These paths are problematic to process, skip for the time being.
            SKIP_PROPERTIES = {
                bpy.types.Depsgraph.bl_rna.properties["object_instances"],
                # XXX To be removed once #133551 is fixed.
                bpy.types.ToolSettings.bl_rna.properties["uv_sculpt"],
            }

            for p in struct.bl_rna.properties:
                # Internal rna meta-data, not expected to support RNA paths generation.
                if p.identifier in {"bl_rna", "rna_type"}:
                    continue
                if p in SKIP_PROPERTIES:
                    continue
                # Only these types can point to sub-structs.
                if p.type not in {'POINTER', 'COLLECTION'}:
                    continue
                # TODO: Dynamic typed pointer/collection properties are ignored for now.
                if not p.fixed_type:
                    continue
                if bpy.types.ID.bl_rna in {p.fixed_type, p.fixed_type.base}:
                    continue
                yield p

        def process_pointer_property(self, p_data, ancestors_sub):
            if not p_data:
                return
            rna_ancestors = p_data.rna_ancestors()
            if not rna_ancestors:
                # Do not error for now. Only ensure that if there is a rna_ancestors array, it is valid.
                return
            if repr(rna_ancestors[0]) != ancestors_sub[0]:
                # Do not error for now. There are valid cases wher the data is 'rebased' on a new 'root' ID.
                return
            if repr(p_data) in ancestors_sub:
                # Loop back onto itself, skip.
                # E.g. `Scene.view_layer.depsgraph.view_layer`.
                return
            self.process_rna_struct(p_data, ancestors_sub)

        print(struct, "from", ancestors)
        self.assertEqual([repr(a) for a in struct.rna_ancestors()], ancestors)

        ancestors_sub = ancestors + [repr(struct)]

        for p in filter_prop_iter(struct):
            if p.type == 'COLLECTION':
                for p_key, p_data in getattr(struct, p.identifier).items():
                    process_pointer_property(self, p_data, ancestors_sub)
            else:
                assert (p.type == 'POINTER')
                p_data = getattr(struct, p.identifier)
                process_pointer_property(self, p_data, ancestors_sub)

    def test_ancestors(self):
        bpy.ops.wm.read_factory_settings()
        for data_block in bpy.data.user_map().keys():
            self.process_rna_struct(data_block, [])


if __name__ == '__main__':
    import sys
    sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
    unittest.main()