|
| 1 | +from collections import namedtuple |
1 | 2 | from fnmatch import fnmatch |
2 | 3 | from itertools import chain |
3 | 4 | from mathutils import Vector, Quaternion, Euler |
4 | 5 | import bpy |
| 6 | +import re |
5 | 7 |
|
6 | 8 | from ..patcher import FunctionPatcher |
7 | | -from ..helpers import intercept, get_context, select_only |
| 9 | +from ..helpers import intercept, get_context, select_only, titlecase |
8 | 10 | from ..log import log, logd |
9 | 11 |
|
10 | 12 | arp_export_module_names = [ |
|
60 | 62 | 'y_scale': 2, # Bone original |
61 | 63 | } |
62 | 64 | default_pose_values = {} |
| 65 | +custom_prop_pattern = re.compile(r'(.+)?\["([^"]+)"\]') |
| 66 | +prop_pattern = re.compile(r'(?:(.+)\.)?([^"\.]+)') |
| 67 | + |
| 68 | +class PropertyWrapper(namedtuple('PrettyProperty', ['data', 'path', 'is_custom'])): |
| 69 | + # To set a property given a data path it's necessary to split the object and attribute name |
| 70 | + # `data.path_resolve(path, False)` returns a bpy_prop, and bpy_prop.data holds the object |
| 71 | + # Unfortunately bpy_prop knows the attribute name but doesn't expose it (see `bpy_prop.__str__`) |
| 72 | + # Also need to determine if it's a custom property, since those are accessed dict-like instead |
| 73 | + # For these reasons, resort to the less robust way of just parsing the data path with regexes |
| 74 | + |
| 75 | + @classmethod |
| 76 | + def from_path(cls, data, data_path): |
| 77 | + try: |
| 78 | + prop_match = custom_prop_pattern.fullmatch(data_path) |
| 79 | + if prop_match: |
| 80 | + if prop_match[1]: |
| 81 | + data = data.path_resolve(prop_match[1]) |
| 82 | + data_path = f'["{prop_match[2]}"]' |
| 83 | + data.path_resolve(data_path) # Make sure the property exists |
| 84 | + return cls(data, data_path, True) |
| 85 | + |
| 86 | + prop_match = prop_pattern.fullmatch(data_path) |
| 87 | + if prop_match: |
| 88 | + if prop_match[1]: |
| 89 | + data = data.path_resolve(prop_match[1]) |
| 90 | + data_path = prop_match[2] |
| 91 | + data.path_resolve(data_path) # Make sure the property exists |
| 92 | + return cls(data, data_path, False) |
| 93 | + except ValueError: |
| 94 | + return None |
| 95 | + |
| 96 | + @property |
| 97 | + def title(self): |
| 98 | + if self.is_custom: |
| 99 | + return titlecase(self.path[2:-2]) # Custom property name should be descriptive enough |
| 100 | + else: |
| 101 | + return f"{self.data.name} {titlecase(self.path)}" |
| 102 | + |
| 103 | + @property |
| 104 | + def default_value(self): |
| 105 | + if self.is_custom: |
| 106 | + return self.data.id_properties_ui(self.path[2:-2]).as_dict()['default'] |
| 107 | + else: |
| 108 | + return self.data.bl_rna.properties[self.path].default |
| 109 | + |
| 110 | + @property |
| 111 | + def value(self): |
| 112 | + return self.data.path_resolve(self.path, True) |
| 113 | + |
| 114 | + @value.setter |
| 115 | + def value(self, new_value): |
| 116 | + if self.is_custom: |
| 117 | + self.data[self.path[2:-2]] = new_value |
| 118 | + else: |
| 119 | + setattr(self.data, self.path, new_value) |
63 | 120 |
|
64 | 121 | def is_object_arp(obj): |
65 | 122 | """Returns whether the object is an Auto-Rig Pro armature.""" |
@@ -96,24 +153,13 @@ def clear_pose(obj, clear_gret_props=True, clear_armature_props=False, clear_bon |
96 | 153 | return |
97 | 154 |
|
98 | 155 | if clear_gret_props: |
99 | | - properties = obj.get('properties', []) |
100 | | - for prop_path in properties: |
| 156 | + for data_path in obj.get('properties', []): |
101 | 157 | try: |
102 | | - bpy_prop = obj.path_resolve(prop_path, False) |
103 | | - prop_data = bpy_prop.data |
104 | | - dot_pos = prop_path.rfind(".") |
105 | | - if dot_pos >= 0: |
106 | | - # Native property |
107 | | - prop_name = prop_path[dot_pos+1:] |
108 | | - default_value = prop_data.bl_rna.properties[prop_name].default |
109 | | - setattr(prop_data, prop_name, default_value) |
110 | | - else: |
111 | | - # Custom property |
112 | | - prop_name = prop_path[2:-2] |
113 | | - default_value = prop_data.id_properties_ui(prop_name).as_dict()["default"] |
114 | | - prop_data[prop_name] = default_value |
| 158 | + prop_wrapper = PropertyWrapper.from_path(obj, data_path) |
| 159 | + if prop_wrapper: |
| 160 | + prop_wrapper.value = prop_wrapper.default_value |
115 | 161 | except Exception as e: |
116 | | - logd(f"Couldn't clear property \"{prop_path}\": {e}") |
| 162 | + logd(f"Couldn't clear property \"{data_path}\": {e}") |
117 | 163 |
|
118 | 164 | if clear_armature_props: |
119 | 165 | for prop_name, prop_value in obj.items(): |
|
0 commit comments