Skip to content

Commit c19f9a0

Browse files
committed
Fix some properties not being reset on rig export
1 parent 8b7e6b2 commit c19f9a0

File tree

5 files changed

+79
-63
lines changed

5 files changed

+79
-63
lines changed

anim/channels_delete_unavailable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def execute(self, context):
4141

4242
for fc in action.fcurves:
4343
try:
44-
prop = obj.path_resolve(fc.data_path, False)
44+
obj.path_resolve(fc.data_path)
4545
except ValueError:
4646
if delete_invalid:
4747
print(f"Removing curve, can't resolve {fc.data_path}")

math.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525

2626
class Rect(namedtuple("Rect", ["x0", "y0", "x1", "y1"])):
2727
@classmethod
28-
def from_size(self, x, y, width, height):
29-
return Rect(x, y, x + width, y + height)
28+
def from_size(cls, x, y, width, height):
29+
return cls(x, y, x + width, y + height)
3030

3131
@property
3232
def width(self):

operator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ class GRET_OT_property_warning(bpy.types.Operator):
77
bl_label = "Not Overridable"
88
bl_options = {'INTERNAL'}
99

10-
def draw_warning_if_not_overridable(layout, obj, prop_path):
10+
def draw_warning_if_not_overridable(layout, obj, data_path):
1111
if obj and obj.override_library:
1212
try:
13-
if not obj.is_property_overridable_library(prop_path):
13+
if not obj.is_property_overridable_library(data_path):
1414
layout.operator(GRET_OT_property_warning.bl_idname,
1515
icon='ERROR', text="", emboss=False, depress=True)
1616
return True

rig/helpers.py

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from collections import namedtuple
12
from fnmatch import fnmatch
23
from itertools import chain
34
from mathutils import Vector, Quaternion, Euler
45
import bpy
6+
import re
57

68
from ..patcher import FunctionPatcher
7-
from ..helpers import intercept, get_context, select_only
9+
from ..helpers import intercept, get_context, select_only, titlecase
810
from ..log import log, logd
911

1012
arp_export_module_names = [
@@ -60,6 +62,61 @@
6062
'y_scale': 2, # Bone original
6163
}
6264
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)
63120

64121
def is_object_arp(obj):
65122
"""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
96153
return
97154

98155
if clear_gret_props:
99-
properties = obj.get('properties', [])
100-
for prop_path in properties:
156+
for data_path in obj.get('properties', []):
101157
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
115161
except Exception as e:
116-
logd(f"Couldn't clear property \"{prop_path}\": {e}")
162+
logd(f"Couldn't clear property \"{data_path}\": {e}")
117163

118164
if clear_armature_props:
119165
for prop_name, prop_value in obj.items():

rig/properties.py

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,7 @@
11
import bpy
2-
import re
32

4-
from ..helpers import titlecase
53
from ..operator import draw_warning_if_not_overridable
6-
7-
custom_prop_re = re.compile(r'(.+)?\["([^"]+)"\]$')
8-
prop_re = re.compile(r'(.+)\.([^"\.]+)$')
9-
10-
def parse_prop_path(obj, prop_path):
11-
# Returns target data, property path and pretty property text if the property was found
12-
# Otherwise returns None, None, prop_path
13-
14-
try:
15-
prop_match = custom_prop_re.search(prop_path)
16-
if prop_match:
17-
if prop_match[1]:
18-
obj = obj.path_resolve(prop_match[1])
19-
prop_path = f'["{prop_match[2]}"]'
20-
# Fetch value to make sure the property exists
21-
value = obj.path_resolve(prop_path)
22-
# Don't attach the object name to text, custom property name should be descriptive enough
23-
text = titlecase(prop_match[2])
24-
return obj, prop_path, text
25-
26-
prop_match = prop_re.search(prop_path)
27-
if prop_match:
28-
obj = obj.path_resolve(prop_match[1])
29-
prop_path = prop_match[2]
30-
# Fetch value to make sure the property exists
31-
value = obj.path_resolve(prop_path)
32-
text = f"{obj.name} {titlecase(prop_match[2])}"
33-
return obj, prop_path, text
34-
except ValueError:
35-
pass
36-
37-
return None, None, prop_path
4+
from .helpers import PropertyWrapper
385

396
class GRET_OT_property_add(bpy.types.Operator):
407
"""Add a property to the list"""
@@ -70,15 +37,18 @@ def execute(self, context):
7037

7138
properties = list(obj.get('properties', []))
7239
properties.append(self.path)
73-
properties.sort(key=lambda prop_path: parse_prop_path(obj, prop_path)[2])
40+
def get_property_title(data_path):
41+
prop_wrapper = PropertyWrapper.from_path(obj, data_path)
42+
return prop_wrapper.title if prop_wrapper else data_path
43+
properties.sort(key=get_property_title)
7444
obj['properties'] = properties
7545

7646
return {'FINISHED'}
7747

7848
def invoke(self, context, event):
7949
# Check if the clipboard already has a correct path and paste it
8050
clipboard = bpy.context.window_manager.clipboard
81-
self.path = clipboard if parse_prop_path(context.active_object, clipboard)[0] else ""
51+
self.path = clipboard if PropertyWrapper.from_path(context.active_object, clipboard) else ""
8252
return context.window_manager.invoke_props_dialog(self)
8353

8454
def draw(self, context):
@@ -133,15 +103,15 @@ def draw_panel(self, context):
133103
if properties:
134104
col = box.column(align=True)
135105

136-
for idx, prop_path in enumerate(properties):
106+
for idx, data_path in enumerate(properties):
137107
row = col.row(align=True)
138-
data, prop_path, label = parse_prop_path(obj, prop_path)
108+
prop_wrapper = PropertyWrapper.from_path(obj, data_path)
139109

140-
if data:
141-
row.prop(data, prop_path, text=label)
110+
if prop_wrapper:
111+
row.prop(prop_wrapper.data, prop_wrapper.path, text=prop_wrapper.title)
142112
else:
143113
row.alert = True
144-
row.label(text=f"Missing: {label}")
114+
row.label(text=f"Missing: {data_path}")
145115

146116
if settings.properties_show_edit:
147117
row.operator('gret.property_remove', icon='X', text="").index = idx

0 commit comments

Comments
 (0)