(On Hold) Rework Properties UI Editor #159
@ -45,10 +45,10 @@ modules = [
|
|||||||
# be registered before they themselves are.
|
# be registered before they themselves are.
|
||||||
# - For Panels, they must be registered before their bl_parent_id is.
|
# - For Panels, they must be registered before their bl_parent_id is.
|
||||||
# - Hotkeys must come after `cloudrig`, since we're storing them on a panel.
|
# - Hotkeys must come after `cloudrig`, since we're storing them on a panel.
|
||||||
rig_component_features,
|
|
||||||
rig_components,
|
rig_components,
|
||||||
prefs,
|
prefs,
|
||||||
generation,
|
generation,
|
||||||
|
rig_component_features,
|
||||||
operators,
|
operators,
|
||||||
properties,
|
properties,
|
||||||
metarigs,
|
metarigs,
|
||||||
|
@ -583,8 +583,7 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
self.report({'INFO'}, "Snapping complete.")
|
self.report({'INFO'}, "Snapping complete.")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def set_bone_selection(self, rig, select=False, pbones: list[PoseBone] = None):
|
||||||
def set_bone_selection(self, rig, select=False, pbones: list[PoseBone]=None):
|
|
||||||
"""Overrides SnapBakeOpMixin to also select the IK pole before keying."""
|
"""Overrides SnapBakeOpMixin to also select the IK pole before keying."""
|
||||||
print("PBONES:", pbones, select)
|
print("PBONES:", pbones, select)
|
||||||
if select and self.target_value == 1 and self.ik_pole:
|
if select and self.target_value == 1 and self.ik_pole:
|
||||||
@ -599,7 +598,6 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
if self.target_value == 1 and self.ik_pole:
|
if self.target_value == 1 and self.ik_pole:
|
||||||
self.snap_pole_target()
|
self.snap_pole_target()
|
||||||
|
|
||||||
|
|
||||||
def snap_pole_target(self) -> Matrix:
|
def snap_pole_target(self) -> Matrix:
|
||||||
"""Snap the pole target based on the first IK bone.
|
"""Snap the pole target based on the first IK bone.
|
||||||
This needs to run after the IK wrist control had already been snapped.
|
This needs to run after the IK wrist control had already been snapped.
|
||||||
@ -615,56 +613,58 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
# https://blenderartists.org/t/visual-transform-helper-functions-for-2-5/500965
|
# https://blenderartists.org/t/visual-transform-helper-functions-for-2-5/500965
|
||||||
|
|
||||||
def perpendicular_vector(v):
|
def perpendicular_vector(v):
|
||||||
""" Returns a vector that is perpendicular to the one given.
|
"""Returns a vector that is perpendicular to the one given.
|
||||||
The returned vector is _not_ guaranteed to be normalized.
|
The returned vector is _not_ guaranteed to be normalized.
|
||||||
"""
|
"""
|
||||||
# Create a vector that is not aligned with v.
|
# Create a vector that is not aligned with v.
|
||||||
# It doesn't matter what vector. Just any vector
|
# It doesn't matter what vector. Just any vector
|
||||||
# that's guaranteed to not be pointing in the same
|
# that's guaranteed to not be pointing in the same
|
||||||
# direction.
|
# direction.
|
||||||
if abs(v[0]) < abs(v[1]):
|
if abs(v[0]) < abs(v[1]):
|
||||||
tv = Vector((1,0,0))
|
tv = Vector((1, 0, 0))
|
||||||
else:
|
else:
|
||||||
tv = Vector((0,1,0))
|
tv = Vector((0, 1, 0))
|
||||||
|
|
||||||
# Use cross prouct to generate a vector perpendicular to
|
# Use cross prouct to generate a vector perpendicular to
|
||||||
# both tv and (more importantly) v.
|
# both tv and (more importantly) v.
|
||||||
return v.cross(tv)
|
return v.cross(tv)
|
||||||
|
|
||||||
def rotation_difference(mat1, mat2):
|
def rotation_difference(mat1, mat2):
|
||||||
""" Returns the shortest-path rotational difference between two
|
"""Returns the shortest-path rotational difference between two
|
||||||
matrices.
|
matrices.
|
||||||
"""
|
"""
|
||||||
q1 = mat1.to_quaternion()
|
q1 = mat1.to_quaternion()
|
||||||
q2 = mat2.to_quaternion()
|
q2 = mat2.to_quaternion()
|
||||||
angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
|
angle = acos(min(1, max(-1, q1.dot(q2)))) * 2
|
||||||
if angle > pi:
|
if angle > pi:
|
||||||
angle = -angle + (2*pi)
|
angle = -angle + (2 * pi)
|
||||||
return angle
|
return angle
|
||||||
|
|
||||||
def get_pose_matrix_in_other_space(mat, pose_bone):
|
def get_pose_matrix_in_other_space(mat, pose_bone):
|
||||||
""" Returns the transform matrix relative to pose_bone's current
|
"""Returns the transform matrix relative to pose_bone's current
|
||||||
transform space. In other words, presuming that mat is in
|
transform space. In other words, presuming that mat is in
|
||||||
armature space, slapping the returned matrix onto pose_bone
|
armature space, slapping the returned matrix onto pose_bone
|
||||||
should give it the armature-space transforms of mat.
|
should give it the armature-space transforms of mat.
|
||||||
"""
|
"""
|
||||||
return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
|
return pose_bone.id_data.convert_space(
|
||||||
|
matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL'
|
||||||
|
)
|
||||||
|
|
||||||
def set_pose_translation(pose_bone, mat):
|
def set_pose_translation(pose_bone, mat):
|
||||||
""" Sets the pose bone's translation to the same translation as the given matrix.
|
"""Sets the pose bone's translation to the same translation as the given matrix.
|
||||||
Matrix should be given in bone's local space.
|
Matrix should be given in bone's local space.
|
||||||
"""
|
"""
|
||||||
pose_bone.location = mat.to_translation()
|
pose_bone.location = mat.to_translation()
|
||||||
|
|
||||||
def match_pole_target(ik_first, ik_last, pole, match_bone):
|
def match_pole_target(ik_first, ik_last, pole, match_bone):
|
||||||
""" Places an IK chain's pole target to match ik_first's
|
"""Places an IK chain's pole target to match ik_first's
|
||||||
transforms to match_bone. All bones should be given as pose bones.
|
transforms to match_bone. All bones should be given as pose bones.
|
||||||
You need to be in pose mode on the relevant armature object.
|
You need to be in pose mode on the relevant armature object.
|
||||||
ik_first: first bone in the IK chain
|
ik_first: first bone in the IK chain
|
||||||
ik_last: last bone in the IK chain
|
ik_last: last bone in the IK chain
|
||||||
pole: pole target bone for the IK chain
|
pole: pole target bone for the IK chain
|
||||||
match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
|
match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
|
||||||
length: distance pole target should be placed from the chain center
|
length: distance pole target should be placed from the chain center
|
||||||
"""
|
"""
|
||||||
a = ik_first.matrix.to_translation()
|
a = ik_first.matrix.to_translation()
|
||||||
b = ik_last.matrix.to_translation() + ik_last.vector
|
b = ik_last.matrix.to_translation() + ik_last.vector
|
||||||
@ -679,11 +679,11 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
pv = perpendicular_vector(ikv).normalized() * length
|
pv = perpendicular_vector(ikv).normalized() * length
|
||||||
|
|
||||||
def set_pole(pvi):
|
def set_pole(pvi):
|
||||||
""" Set pole target's position based on a vector
|
"""Set pole target's position based on a vector
|
||||||
from the arm center line.
|
from the arm center line.
|
||||||
"""
|
"""
|
||||||
# Translate pvi into armature space
|
# Translate pvi into armature space
|
||||||
ploc = a + (ikv/2) + pvi
|
ploc = a + (ikv / 2) + pvi
|
||||||
|
|
||||||
# Set pole target to location
|
# Set pole target to location
|
||||||
mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
|
mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
|
||||||
@ -715,8 +715,6 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
match_pole_target(ik_first, self.ik_last, ik_pole, fk_first)
|
match_pole_target(ik_first, self.ik_last, ik_pole, fk_first)
|
||||||
return ik_pole.matrix
|
return ik_pole.matrix
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def map_single_frame_to_bone_matrices(
|
def map_single_frame_to_bone_matrices(
|
||||||
self, context, frame_number, bones_to_snap, snap_to_bones
|
self, context, frame_number, bones_to_snap, snap_to_bones
|
||||||
):
|
):
|
||||||
@ -745,7 +743,6 @@ class POSE_OT_cloudrig_toggle_ikfk_bake(SnapBakeOpMixin, CloudRigOperator):
|
|||||||
bone_column.label(text=f"{' '*10} {self.ik_pole}")
|
bone_column.label(text=f"{' '*10} {self.ik_pole}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
######## Convenience Operators ########
|
######## Convenience Operators ########
|
||||||
#######################################
|
#######################################
|
||||||
@ -1432,21 +1429,33 @@ def unquote_custom_prop_name(prop_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class CloudRig_UIElement(PropertyGroup):
|
class CloudRig_UIElement(PropertyGroup):
|
||||||
|
@property
|
||||||
|
def rig(self):
|
||||||
|
return self.id_data
|
||||||
|
|
||||||
element_type: EnumProperty(
|
element_type: EnumProperty(
|
||||||
name="Element Type",
|
name="Element Type",
|
||||||
description="How this UI element is drawn",
|
description="How this UI element is drawn",
|
||||||
items=[
|
items=[
|
||||||
('PANEL', "Panel", "Collapsible panel. May contain Panels, Labels, Rows"),
|
('PANEL', "Panel", "Collapsible panel. May contain Panels, Labels, Rows"),
|
||||||
('LABEL', "Label", "Label. May contain Panels, Labels, Rows"),
|
('LABEL', "Label", "Label. May contain Panels, Labels, Rows"),
|
||||||
('ROW', "Row", "Grouping for elements that allow multiple per row. Must be used for such elements, even if there is only one in the row. May contain Rows, Properties, and Operators"),
|
(
|
||||||
('PROPERTY', "Property", "A single Property. Must belong to a Row. May contain conditional Panels, Labels, Rows"),
|
'ROW',
|
||||||
|
"Row",
|
||||||
|
"Grouping for elements that allow multiple per row. Must be used for such elements, even if there is only one in the row. May contain Rows, Properties, and Operators",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'PROPERTY',
|
||||||
|
"Property",
|
||||||
|
"A single Property. Must belong to a Row. May contain conditional Panels, Labels, Rows",
|
||||||
|
),
|
||||||
('OPERATOR', "Operator", "A single Operator. Must belong to a Row"),
|
('OPERATOR', "Operator", "A single Operator. Must belong to a Row"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
display_name: StringProperty(
|
display_name: StringProperty(
|
||||||
name="Display Name",
|
name="Display Name",
|
||||||
description="(Optional) Display name of this UI element",
|
description="Display name of this UI element",
|
||||||
default="",
|
default="",
|
||||||
)
|
)
|
||||||
# NOTE: This needs to be updated when elements are removed.
|
# NOTE: This needs to be updated when elements are removed.
|
||||||
@ -1457,17 +1466,19 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
description="Index of the parent UI element",
|
description="Index of the parent UI element",
|
||||||
default=-1,
|
default=-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
if self.parent_index >= 0:
|
if self.parent_index >= 0:
|
||||||
return self.id_data.cloudrig_ui[self.parent_index]
|
return self.rig.cloudrig_ui[self.parent_index]
|
||||||
|
|
||||||
@parent.setter
|
@parent.setter
|
||||||
def parent(self, value):
|
def parent(self, value):
|
||||||
self.parent_index = value.index
|
self.parent_index = value.index
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def index(self):
|
def index(self):
|
||||||
for i, elem in enumerate(self.id_data.cloudrig_ui):
|
for i, elem in enumerate(self.rig.cloudrig_ui):
|
||||||
if elem == self:
|
if elem == self:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
@ -1483,60 +1494,70 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
parent_values: StringProperty(
|
parent_values: StringProperty(
|
||||||
# Supported Types: Panel, Label, Row, only when Element Type of parent element is Property.
|
# Supported Types: Panel, Label, Row, only when Element Type of parent element is Property.
|
||||||
name="Parent Values",
|
name="Parent Values",
|
||||||
description="Condition for this UI element to be drawn, when its parent is a Property. This UI element will only be drawn if the parent property has one of these comma-separated values"
|
description="Condition for this UI element to be drawn, when its parent is a Property. This UI element will only be drawn if the parent property has one of these comma-separated values",
|
||||||
)
|
)
|
||||||
|
|
||||||
texts: StringProperty(
|
texts: StringProperty(
|
||||||
# Supported Types: Property, only Boolean and Integer.
|
# Supported Types: Property, only Boolean and Integer.
|
||||||
name="Texts",
|
name="Texts",
|
||||||
description="Comma-separated display texts for Integer and Boolean Properties"
|
description="Comma-separated display texts for Integer and Boolean Properties",
|
||||||
)
|
)
|
||||||
|
|
||||||
bl_idname: StringProperty(
|
bl_idname: StringProperty(
|
||||||
# Supported Types: Operator
|
# Supported Types: Operator
|
||||||
name="Operator ID",
|
name="Operator ID",
|
||||||
description="Operator bl_idname"
|
description="Operator bl_idname",
|
||||||
)
|
)
|
||||||
op_kwargs: StringProperty(
|
op_kwargs: StringProperty(
|
||||||
# Supported Types: Operator
|
# Supported Types: Operator
|
||||||
name="Operator Arguments",
|
name="Operator Arguments",
|
||||||
description="Operator Keyword Arguments, as a json dict",
|
description="Operator Keyword Arguments, as a json dict",
|
||||||
default="{}"
|
default="{}",
|
||||||
)
|
)
|
||||||
icon: StringProperty(
|
icon: StringProperty(
|
||||||
# Supported Types: Label, Row, Property(bool), Operator
|
# Supported Types: Label, Row, Property(bool), Operator
|
||||||
name="Icon",
|
name="Icon",
|
||||||
description="Icon"
|
description="Icon",
|
||||||
)
|
)
|
||||||
icon_false: StringProperty(
|
icon_false: StringProperty(
|
||||||
# Supported Types: Property(bool)
|
# Supported Types: Property(bool)
|
||||||
name="Icon False",
|
name="Icon False",
|
||||||
description="Icon to display when this boolean property is False"
|
description="Icon to display when this boolean property is False",
|
||||||
)
|
)
|
||||||
|
|
||||||
prop_owner_path: StringProperty(
|
prop_owner_path: StringProperty(
|
||||||
# Supported Types: Property
|
# Supported Types: Property
|
||||||
name="Property Owner",
|
name="Property Owner",
|
||||||
description="Data Path from the rig object to the direct owner of the property to be drawn"
|
description="Data Path from the rig object to the direct owner of the property to be drawn",
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prop_owner(self):
|
def prop_owner(self):
|
||||||
try:
|
try:
|
||||||
return self.id_data.path_resolve(self.prop_owner_path)
|
return self.rig.path_resolve(self.prop_owner_path)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# This can happen eg. if user adds a constraint influence to the UI, then deletes the constraint.
|
# This can happen eg. if user adds a constraint influence to the UI, then deletes the constraint.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def update_prop_name(self, context):
|
||||||
|
if self.is_custom_prop:
|
||||||
|
self.display_name = self.prop_name.replace("_", " ").title()
|
||||||
|
elif self.prop_name == 'is_visible':
|
||||||
|
self.display_name = self.prop_owner.name
|
||||||
|
|
||||||
prop_name: StringProperty(
|
prop_name: StringProperty(
|
||||||
# Supported Types: Property
|
# Supported Types: Property
|
||||||
name="Property Name",
|
name="Property Name",
|
||||||
description="Name of the property to be drawn"
|
description="Name of the property to be drawn",
|
||||||
|
update=update_prop_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bracketed_prop_name(self):
|
def bracketed_prop_name(self):
|
||||||
if self.prop_is_custom:
|
if self.is_custom_prop:
|
||||||
return f'["{self.prop_name}"]'
|
return f'["{self.prop_name}"]'
|
||||||
return self.prop_name
|
return self.prop_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prop_value(self):
|
def prop_value(self):
|
||||||
if not hasattr(self.prop_owner, 'path_resolve'):
|
if not hasattr(self.prop_owner, 'path_resolve'):
|
||||||
@ -1548,14 +1569,15 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
# Property may have been removed.
|
# Property may have been removed.
|
||||||
return {'MISSING'}
|
return {'MISSING'}
|
||||||
|
|
||||||
prop_is_custom: BoolProperty(
|
is_custom_prop: BoolProperty(
|
||||||
# Supported Types: Property
|
# Supported Types: Property # TODO: This should be set from the update of prop_name.
|
||||||
name="Is Custom Property",
|
name="Is Custom Property",
|
||||||
description="Whether this is a custom or a built-in property. Set automatically"
|
description="Whether this is a custom or a built-in property. Set automatically",
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_prop_settings(self):
|
def custom_prop_settings(self):
|
||||||
if not self.prop_is_custom:
|
if not self.is_custom_prop:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
return self.prop_owner.id_properties_ui(self.prop_name).as_dict()
|
return self.prop_owner.id_properties_ui(self.prop_name).as_dict()
|
||||||
@ -1565,7 +1587,7 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self):
|
def children(self):
|
||||||
return [elem for elem in self.id_data.cloudrig_ui if elem.parent==self]
|
return [elem for elem in self.rig.cloudrig_ui if elem.parent == self]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_draw(self):
|
def should_draw(self):
|
||||||
@ -1578,7 +1600,7 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def draw(self, context, layout):
|
def draw_ui_element(self, context, layout):
|
||||||
if not self.should_draw or not layout:
|
if not self.should_draw or not layout:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1597,19 +1619,23 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
if self.display_name:
|
if self.display_name:
|
||||||
layout.label(text=self.display_name)
|
layout.label(text=self.display_name)
|
||||||
if self.element_type == 'PROPERTY':
|
if self.element_type == 'PROPERTY':
|
||||||
|
if not self.parent or self.parent.element_type != 'ROW':
|
||||||
|
layout = remove_op_ui = layout.row()
|
||||||
self.draw_property(context, layout)
|
self.draw_property(context, layout)
|
||||||
if any([child.should_draw for child in self.children]):
|
if any([child.should_draw for child in self.children]):
|
||||||
layout = layout.box()
|
layout = layout.box()
|
||||||
if self.element_type == 'OPERATOR':
|
if self.element_type == 'OPERATOR':
|
||||||
self.draw_operator(context, layout)
|
self.draw_operator(context, layout)
|
||||||
|
|
||||||
if self.id_data.cloudrig.ui_edit_mode:
|
if self.rig.cloudrig.ui_edit_mode:
|
||||||
remove_op_ui.operator('object.cloudrig_ui_element_remove', text="", icon='X').element_index = self.index
|
remove_op_ui.operator(
|
||||||
|
'object.cloudrig_ui_element_remove', text="", icon='X'
|
||||||
|
).element_index = self.index
|
||||||
|
|
||||||
if not layout:
|
if not layout:
|
||||||
return
|
return
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child.draw(context, layout)
|
child.draw_ui_element(context, layout)
|
||||||
|
|
||||||
def draw_property(self, context, layout):
|
def draw_property(self, context, layout):
|
||||||
prop_owner, prop_value = self.prop_owner, self.prop_value
|
prop_owner, prop_value = self.prop_owner, self.prop_value
|
||||||
@ -1627,8 +1653,7 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
icon='ERROR',
|
icon='ERROR',
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.display_name:
|
display_name = self.display_name or self.prop_name
|
||||||
display_name = self.prop_name
|
|
||||||
|
|
||||||
bracketed_prop_name = self.bracketed_prop_name
|
bracketed_prop_name = self.bracketed_prop_name
|
||||||
value_type, is_array = rna_idprop_value_item_type(prop_value)
|
value_type, is_array = rna_idprop_value_item_type(prop_value)
|
||||||
@ -1637,15 +1662,25 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
# Property is a Datablock Pointer.
|
# Property is a Datablock Pointer.
|
||||||
layout.prop(self.prop_owner, bracketed_prop_name, text=display_name)
|
layout.prop(self.prop_owner, bracketed_prop_name, text=display_name)
|
||||||
elif value_type in {int, float, bool}:
|
elif value_type in {int, float, bool}:
|
||||||
if self.texts and not is_array and len(self.texts) - 1 >= int(prop_value) >= 0:
|
if (
|
||||||
|
self.texts
|
||||||
|
and not is_array
|
||||||
|
and len(self.texts) - 1 >= int(prop_value) >= 0
|
||||||
|
):
|
||||||
text = self.texts[int(prop_value)].strip()
|
text = self.texts[int(prop_value)].strip()
|
||||||
if text:
|
if text:
|
||||||
display_name += ": " + text
|
display_name += ": " + text
|
||||||
if value_type == bool:
|
if value_type == bool:
|
||||||
icon = self.icon if prop_value else self.icon_flase
|
icon = self.icon if prop_value else self.icon_flase
|
||||||
layout.prop(self.prop_owner, bracketed_prop_name, toggle=True, text=display_name, icon=icon)
|
layout.prop(
|
||||||
|
self.prop_owner,
|
||||||
|
bracketed_prop_name,
|
||||||
|
toggle=True,
|
||||||
|
text=display_name,
|
||||||
|
icon=icon,
|
||||||
|
)
|
||||||
elif value_type in {int, float}:
|
elif value_type in {int, float}:
|
||||||
if self.prop_is_custom:
|
if self.is_custom_prop:
|
||||||
# Property is a float/int/color
|
# Property is a float/int/color
|
||||||
# For large ranges, a slider doesn't make sense.
|
# For large ranges, a slider doesn't make sense.
|
||||||
prop_settings = self.custom_prop_settings
|
prop_settings = self.custom_prop_settings
|
||||||
@ -1653,7 +1688,12 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
not is_array
|
not is_array
|
||||||
and prop_settings['soft_max'] - prop_settings['soft_min'] < 100
|
and prop_settings['soft_max'] - prop_settings['soft_min'] < 100
|
||||||
)
|
)
|
||||||
layout.prop(prop_owner, bracketed_prop_name, slider=is_slider, text=display_name)
|
layout.prop(
|
||||||
|
prop_owner,
|
||||||
|
bracketed_prop_name,
|
||||||
|
slider=is_slider,
|
||||||
|
text=display_name,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
layout.prop(prop_owner, bracketed_prop_name, text=display_name)
|
layout.prop(prop_owner, bracketed_prop_name, text=display_name)
|
||||||
elif value_type == str:
|
elif value_type == str:
|
||||||
@ -1664,7 +1704,9 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
and prop_owner.target.type == 'ARMATURE'
|
and prop_owner.target.type == 'ARMATURE'
|
||||||
):
|
):
|
||||||
# Special case for nice constraint sub-target selectors.
|
# Special case for nice constraint sub-target selectors.
|
||||||
layout.prop_search(prop_owner, bracketed_prop_name, prop_owner.target.pose, 'bones')
|
layout.prop_search(
|
||||||
|
prop_owner, bracketed_prop_name, prop_owner.target.pose, 'bones'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
layout.prop(prop_owner, bracketed_prop_name)
|
layout.prop(prop_owner, bracketed_prop_name)
|
||||||
else:
|
else:
|
||||||
@ -1678,6 +1720,13 @@ class CloudRig_UIElement(PropertyGroup):
|
|||||||
feed_op_props(op_props, self.op_kwargs)
|
feed_op_props(op_props, self.op_kwargs)
|
||||||
return op_props
|
return op_props
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
rna = self.bl_rna
|
||||||
|
for prop_name, prop_data in rna.properties.items():
|
||||||
|
if prop_name == 'rna_type':
|
||||||
|
continue
|
||||||
|
setattr(self, prop_name, prop_data.default)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.identifier
|
return self.identifier
|
||||||
|
|
||||||
@ -1691,11 +1740,12 @@ class CLOUDRIG_PT_custom_ui(CLOUDRIG_PT_base):
|
|||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
|
|
||||||
rig = context.active_object # TODO
|
rig = context.active_object # TODO
|
||||||
|
|
||||||
for elem in rig.cloudrig_ui:
|
for elem in rig.cloudrig_ui:
|
||||||
if not elem.parent:
|
if not elem.parent:
|
||||||
elem.draw(context, layout)
|
elem.draw_ui_element(context, layout)
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
########### Rig Preferences ###########
|
########### Rig Preferences ###########
|
||||||
@ -1886,7 +1936,6 @@ class CloudRigBoneCollection(PropertyGroup):
|
|||||||
if not component.active_bone_set:
|
if not component.active_bone_set:
|
||||||
component.bone_sets_active_index = 0
|
component.bone_sets_active_index = 0
|
||||||
|
|
||||||
|
|
||||||
# Metarig: Update bone sets with this collection assigned to refer to the new name.
|
# Metarig: Update bone sets with this collection assigned to refer to the new name.
|
||||||
if is_active_cloud_metarig(context):
|
if is_active_cloud_metarig(context):
|
||||||
rig = context.pose_object or context.active_object
|
rig = context.pose_object or context.active_object
|
||||||
@ -2923,7 +2972,9 @@ class POSE_OT_cloudrig_collection_clipboard_copy(CloudRigOperator):
|
|||||||
counter += 1
|
counter += 1
|
||||||
json_obj[coll.name]['bone_names'] = [bone.name for bone in coll.bones]
|
json_obj[coll.name]['bone_names'] = [bone.name for bone in coll.bones]
|
||||||
json_obj[coll.name]['cloudrig_info'] = coll['cloudrig_info'].to_dict()
|
json_obj[coll.name]['cloudrig_info'] = coll['cloudrig_info'].to_dict()
|
||||||
json_obj[coll.name]['parent_name'] = coll.parent.name if coll.parent else ""
|
json_obj[coll.name]['parent_name'] = (
|
||||||
|
coll.parent.name if coll.parent else ""
|
||||||
|
)
|
||||||
|
|
||||||
if counter == 0:
|
if counter == 0:
|
||||||
self.report({'ERROR'}, "No visible collections to copy.")
|
self.report({'ERROR'}, "No visible collections to copy.")
|
||||||
@ -3309,8 +3360,6 @@ def register():
|
|||||||
|
|
||||||
for c in classes:
|
for c in classes:
|
||||||
if not is_registered(c):
|
if not is_registered(c):
|
||||||
# This if statement is important to avoid re-registering UI panels,
|
|
||||||
# which would cause them to lose their sub-panels. (They would become top-level.)
|
|
||||||
register_class(c)
|
register_class(c)
|
||||||
|
|
||||||
bpy.types.Object.cloudrig_prefs = PointerProperty(
|
bpy.types.Object.cloudrig_prefs = PointerProperty(
|
||||||
|
@ -1,21 +1,212 @@
|
|||||||
from ..generation.cloudrig import CloudRig_UIElement, find_cloudrig
|
from ..generation.cloudrig import CloudRig_UIElement, find_cloudrig, feed_op_props
|
||||||
from .properties_ui import UIPathProperty
|
from .properties_ui import UIPathProperty
|
||||||
from bpy.types import Operator
|
from bpy.types import Operator, UILayout, ID, PoseBone, BoneCollection
|
||||||
from bpy.props import CollectionProperty, StringProperty, IntProperty, BoolProperty
|
from bpy.props import (
|
||||||
|
CollectionProperty,
|
||||||
|
StringProperty,
|
||||||
|
IntProperty,
|
||||||
|
BoolProperty,
|
||||||
|
EnumProperty,
|
||||||
|
PointerProperty,
|
||||||
|
)
|
||||||
|
from rna_prop_ui import rna_idprop_value_item_type
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
class CLOUDRIG_OT_ui_element_add(Operator):
|
|
||||||
"""Add a UI element"""
|
|
||||||
|
|
||||||
bl_idname = "object.cloudrig_ui_element_add"
|
def draw_ui_editing(context, layout, ui_element, operator):
|
||||||
bl_label = "Add Property to UI"
|
rig = find_cloudrig(context)
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
# Copy the definition of a single UIElement, which will be added
|
layout.prop(ui_element, 'element_type')
|
||||||
# by this operator, when the "OK" button is clicked.
|
name_row = layout.row()
|
||||||
__annotations__ = CloudRig_UIElement.__annotations__
|
if (
|
||||||
|
ui_element.element_type in {'PANEL', 'LABEL', 'ROW'}
|
||||||
|
and ui_element.display_name.strip() == ""
|
||||||
|
):
|
||||||
|
name_row.alert = True
|
||||||
|
|
||||||
parent_element: StringProperty(name="Parent Element")
|
if ui_element.element_type == 'PROPERTY':
|
||||||
|
layout.prop(operator, 'prop_owner_type', expand=True)
|
||||||
|
if operator.prop_owner_type == 'BONE':
|
||||||
|
layout.prop_search(operator, 'prop_bone', rig.pose, 'bones')
|
||||||
|
elif operator.prop_owner_type == 'COLLECTION':
|
||||||
|
layout.prop_search(
|
||||||
|
operator,
|
||||||
|
'prop_coll',
|
||||||
|
rig.data,
|
||||||
|
'collections_all',
|
||||||
|
icon='OUTLINER_COLLECTION',
|
||||||
|
)
|
||||||
|
elif operator.prop_owner_type == 'DATA_PATH':
|
||||||
|
layout.prop(operator, 'prop_data_path', icon='RNA')
|
||||||
|
|
||||||
|
if context.scene.cloudrig_ui_prop_selector:
|
||||||
|
layout.prop_search(
|
||||||
|
ui_element, 'prop_name', context.scene, 'cloudrig_ui_prop_selector'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
layout.prop(ui_element, 'prop_name')
|
||||||
|
|
||||||
|
if ui_element.element_type == 'OPERATOR':
|
||||||
|
draw_op_editing(context, layout, ui_element, operator)
|
||||||
|
|
||||||
|
name_row.prop(ui_element, 'display_name')
|
||||||
|
if (
|
||||||
|
ui_element.element_type in {'PANEL', 'LABEL', 'ROW'}
|
||||||
|
and ui_element.display_name.strip() == ""
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# debug
|
||||||
|
# layout.prop(ui_element, 'prop_owner_path')
|
||||||
|
# layout.prop(ui_element, 'is_custom_prop')
|
||||||
|
|
||||||
|
layout.prop_search(
|
||||||
|
operator, 'parent_element', context.scene, 'cloudrig_ui_parent_selector'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_op_editing(context, layout, ui_element, operator):
|
||||||
|
if operator.use_batch_add:
|
||||||
|
return
|
||||||
|
op_box = layout.box().column()
|
||||||
|
op_box.prop(operator.temp_kmi, 'idname', text="Operator")
|
||||||
|
operator.op_kwargs_dict = {}
|
||||||
|
if operator.temp_kmi.idname:
|
||||||
|
box = None
|
||||||
|
op_rna = eval("bpy.ops." + operator.temp_kmi.idname).get_rna_type()
|
||||||
|
for key, value in op_rna.properties.items():
|
||||||
|
if key == 'rna_type':
|
||||||
|
continue
|
||||||
|
if not box:
|
||||||
|
box = op_box.box().column(align=True)
|
||||||
|
box.prop(operator.temp_kmi.properties, key)
|
||||||
|
operator.op_kwargs_dict[key] = str(
|
||||||
|
getattr(operator.temp_kmi.properties, key)
|
||||||
|
)
|
||||||
|
icons = UILayout.bl_rna.functions["prop"].parameters["icon"]
|
||||||
|
op_box.prop_search(
|
||||||
|
ui_element, 'icon', icons, 'enum_items', icon=ui_element.icon
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_parent_selector(context):
|
||||||
|
context.scene.cloudrig_ui_parent_selector.clear()
|
||||||
|
|
||||||
|
rig = find_cloudrig(context)
|
||||||
|
|
||||||
|
for ui_element in rig.cloudrig_ui:
|
||||||
|
if ui_element.element_type in {'PANEL', 'LABEL', 'ROW', 'PROPERTY'}:
|
||||||
|
parent_option = context.scene.cloudrig_ui_parent_selector.add()
|
||||||
|
parent_option.name = ui_element.identifier
|
||||||
|
parent_option.index = ui_element.index
|
||||||
|
|
||||||
|
|
||||||
|
def wipe_parent_selector(context):
|
||||||
|
if 'cloudrig_ui_parent_selector' in context.scene:
|
||||||
|
del context.scene['cloudrig_ui_parent_selector']
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_ui_element(context):
|
||||||
|
rig = find_cloudrig(context)
|
||||||
|
return rig.cloudrig_ui_new_element
|
||||||
|
|
||||||
|
|
||||||
|
def update_property_selector(self, context):
|
||||||
|
context.scene.cloudrig_ui_prop_selector.clear()
|
||||||
|
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
|
||||||
|
prop_owner = ui_element.prop_owner
|
||||||
|
|
||||||
|
if not prop_owner:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Populate the property drop-down selector with available custom properties.
|
||||||
|
ui_element.is_custom_prop = True
|
||||||
|
for key in get_drawable_custom_properties(prop_owner):
|
||||||
|
name_entry = context.scene.cloudrig_ui_prop_selector.add()
|
||||||
|
name_entry.name = key
|
||||||
|
|
||||||
|
if len(context.scene.cloudrig_ui_prop_selector) == 0:
|
||||||
|
# If that failed, populate it with built-in properties instead.
|
||||||
|
ui_element.is_custom_prop = False
|
||||||
|
for key in get_drawable_builtin_properties(prop_owner):
|
||||||
|
name_entry = context.scene.cloudrig_ui_prop_selector.add()
|
||||||
|
name_entry.name = key
|
||||||
|
|
||||||
|
|
||||||
|
class UIElementAddMixin:
|
||||||
|
def update_parent_element(self, context):
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
ui_element.parent_index = context.scene.cloudrig_ui_parent_selector[
|
||||||
|
self.parent_element
|
||||||
|
].index
|
||||||
|
|
||||||
|
parent_element: StringProperty(name="Parent Element", update=update_parent_element)
|
||||||
|
|
||||||
|
def update_prop_bone(self, context):
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
ui_element.prop_owner_path = f'pose.bones["{self.prop_bone}"]'
|
||||||
|
update_property_selector(self, context)
|
||||||
|
|
||||||
|
prop_bone: StringProperty(name="Bone Name", update=update_prop_bone)
|
||||||
|
|
||||||
|
def update_prop_coll(self, context):
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
ui_element.display_name = self.prop_coll
|
||||||
|
ui_element.prop_owner_path = f'data.collections_all["{self.prop_coll}"]'
|
||||||
|
update_property_selector(self, context)
|
||||||
|
|
||||||
|
prop_coll: StringProperty(name="Bone Collection", update=update_prop_coll)
|
||||||
|
|
||||||
|
def update_prop_data_path(self, context):
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
ui_element.prop_owner_path = self.prop_data_path
|
||||||
|
update_property_selector(self, context)
|
||||||
|
|
||||||
|
prop_data_path: StringProperty(name="Data Path", update=update_prop_data_path)
|
||||||
|
|
||||||
|
def update_prop_owner_type(self, context):
|
||||||
|
ui_element = get_new_ui_element(context)
|
||||||
|
if self.prop_owner_type == 'COLLECTION':
|
||||||
|
ui_element.prop_name = 'is_visible'
|
||||||
|
ui_element.icon = 'HIDE_OFF'
|
||||||
|
ui_element.icon_false = 'HIDE_ON'
|
||||||
|
self.prop_coll = self.prop_coll
|
||||||
|
if self.prop_owner_type == 'BONE':
|
||||||
|
self.prop_bone = self.prop_bone
|
||||||
|
if self.prop_owner_type == 'DATA_PATH':
|
||||||
|
self.prop_data_path = ui_element.prop_owner_path
|
||||||
|
|
||||||
|
prop_owner_type: EnumProperty(
|
||||||
|
name="Property Owner Type",
|
||||||
|
description="How you would like to select the owner of the property which will be added to the UI",
|
||||||
|
items=[
|
||||||
|
('BONE', 'Bone', 'Select a bone from the rig', 'BONE_DATA', 0),
|
||||||
|
(
|
||||||
|
'COLLECTION',
|
||||||
|
'Collection',
|
||||||
|
'Select a bone collection from the rig',
|
||||||
|
'OUTLINER_COLLECTION',
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'DATA_PATH',
|
||||||
|
'Data Path',
|
||||||
|
'Enter a Python Data Path to any property of the rig',
|
||||||
|
'RNA',
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
update=update_prop_owner_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
use_batch_add: BoolProperty(
|
||||||
|
name="Batch Add",
|
||||||
|
options={'SKIP_SAVE'},
|
||||||
|
default=False,
|
||||||
|
description="Add all custom properties of the selected ID to the UI",
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@ -25,15 +216,19 @@ class CLOUDRIG_OT_ui_element_add(Operator):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def invoke(self, context, _event):
|
def invoke(self, context, _event):
|
||||||
context.scene.cloudrig_ui_parent_selector.clear()
|
update_parent_selector(context)
|
||||||
|
|
||||||
rig = find_cloudrig(context)
|
self.ui_element = get_new_ui_element(context)
|
||||||
|
self.ui_element.reset()
|
||||||
|
|
||||||
for ui_element in rig.cloudrig_ui:
|
self.temp_kmi = context.window_manager.keyconfigs.default.keymaps[
|
||||||
if ui_element.element_type in {'PANEL', 'LABEL', 'ROW'}:
|
'Info'
|
||||||
parent_option = context.scene.cloudrig_ui_parent_selector.add()
|
].keymap_items.new('', 'NUMPAD_5', 'PRESS')
|
||||||
parent_option.name = ui_element.identifier
|
if self.bl_idname:
|
||||||
parent_option.index = ui_element.index
|
self.temp_kmi.idname = self.bl_idname
|
||||||
|
if self.ui_element.op_kwargs:
|
||||||
|
op_props = self.temp_kmi.properties
|
||||||
|
feed_op_props(op_props, self.ui_element.op_kwargs)
|
||||||
|
|
||||||
return context.window_manager.invoke_props_dialog(self, width=500)
|
return context.window_manager.invoke_props_dialog(self, width=500)
|
||||||
|
|
||||||
@ -42,23 +237,62 @@ class CLOUDRIG_OT_ui_element_add(Operator):
|
|||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
|
|
||||||
layout.prop(self, 'element_type')
|
draw_ui_editing(context, layout, self.ui_element, self)
|
||||||
layout.prop(self, 'display_name')
|
|
||||||
if self.element_type in {'PANEL', 'LABEL', 'ROW'}:
|
|
||||||
layout.prop_search(self, 'parent_element', context.scene, 'cloudrig_ui_parent_selector')
|
class CLOUDRIG_OT_ui_element_add(UIElementAddMixin, Operator):
|
||||||
|
"""Add UI Element"""
|
||||||
|
|
||||||
|
bl_idname = "object.cloudrig_ui_element_add"
|
||||||
|
bl_label = "Add UI Element"
|
||||||
|
bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
rig = find_cloudrig(context)
|
rig = find_cloudrig(context)
|
||||||
|
temp_ui_element = get_new_ui_element(context)
|
||||||
|
|
||||||
|
if (
|
||||||
|
temp_ui_element.element_type in {'PANEL', 'LABEL', 'ROW'}
|
||||||
|
and temp_ui_element.display_name.strip() == ""
|
||||||
|
):
|
||||||
|
self.report({'ERROR'}, "This UI element must have a display name!")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
new_ui_element = rig.cloudrig_ui.add()
|
new_ui_element = rig.cloudrig_ui.add()
|
||||||
new_ui_element.display_name = self.display_name
|
|
||||||
new_ui_element.element_type = self.element_type
|
for prop_name in new_ui_element.bl_rna.properties.keys():
|
||||||
if self.parent_element:
|
if prop_name == 'rna_type':
|
||||||
new_ui_element.parent_index = context.scene.cloudrig_ui_parent_selector[self.parent_element].index
|
continue
|
||||||
|
setattr(new_ui_element, prop_name, getattr(temp_ui_element, prop_name))
|
||||||
|
|
||||||
|
wipe_parent_selector(context)
|
||||||
|
del rig['cloudrig_ui_new_element']
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class CLOUDRIG_OT_ui_element_edit(UIElementAddMixin, Operator):
|
||||||
|
"""Add a UI element"""
|
||||||
|
|
||||||
|
bl_idname = "object.cloudrig_ui_element_edit"
|
||||||
|
bl_label = "Edit UI Element"
|
||||||
|
bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
element_index: IntProperty()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_decorate = False
|
||||||
|
layout.use_property_split = True
|
||||||
|
|
||||||
|
rig = find_cloudrig(context)
|
||||||
|
elem_to_edit = rig.cloudrig_ui[self.element_index]
|
||||||
|
|
||||||
|
draw_ui_editing(context, layout, elem_to_edit, self)
|
||||||
|
|
||||||
|
|
||||||
class CLOUDRIG_OT_ui_element_remove(Operator):
|
class CLOUDRIG_OT_ui_element_remove(Operator):
|
||||||
"""Remove this UI element.\n\n""" \
|
"""Remove this UI element.\n\n""" """Ctrl: Do not remove children"""
|
||||||
"""Ctrl: Do not remove children"""
|
|
||||||
|
|
||||||
bl_idname = "object.cloudrig_ui_element_remove"
|
bl_idname = "object.cloudrig_ui_element_remove"
|
||||||
bl_label = "Remove UI Element"
|
bl_label = "Remove UI Element"
|
||||||
@ -97,12 +331,56 @@ class CLOUDRIG_OT_ui_element_remove(Operator):
|
|||||||
rig.cloudrig_ui.remove(index)
|
rig.cloudrig_ui.remove(index)
|
||||||
|
|
||||||
|
|
||||||
|
def supports_custom_props(prop_owner):
|
||||||
|
return isinstance(prop_owner, ID) or type(prop_owner) in {PoseBone, BoneCollection}
|
||||||
|
|
||||||
|
|
||||||
|
def has_custom_props(prop_owner) -> bool:
|
||||||
|
if not supports_custom_props(prop_owner):
|
||||||
|
return False
|
||||||
|
return bool(list(get_drawable_custom_properties(prop_owner)))
|
||||||
|
|
||||||
|
|
||||||
|
def get_drawable_custom_properties(prop_owner):
|
||||||
|
if not supports_custom_props(prop_owner):
|
||||||
|
return []
|
||||||
|
for prop_name in prop_owner.keys():
|
||||||
|
try:
|
||||||
|
prop_owner.id_properties_ui(prop_name).as_dict()
|
||||||
|
except TypeError:
|
||||||
|
# This happens for Python properties. There's not much point in drawing them.
|
||||||
|
continue
|
||||||
|
yield prop_name
|
||||||
|
|
||||||
|
|
||||||
|
def path_resolve_safe(owner, data_path):
|
||||||
|
try:
|
||||||
|
return owner.path_resolve(data_path)
|
||||||
|
except ValueError:
|
||||||
|
# This can happen eg. if user adds a constraint influence to the UI, then deletes the constraint.
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def get_drawable_builtin_properties(prop_owner):
|
||||||
|
for prop_name, prop_data in prop_owner.bl_rna.properties.items():
|
||||||
|
if prop_data.is_runtime:
|
||||||
|
continue
|
||||||
|
prop_value = getattr(prop_owner, prop_name)
|
||||||
|
value_type, is_array = rna_idprop_value_item_type(prop_value)
|
||||||
|
if value_type in {bool, int, float, str}:
|
||||||
|
yield prop_name
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.types.Scene.cloudrig_ui_parent_selector = CollectionProperty(
|
bpy.types.Scene.cloudrig_ui_parent_selector = CollectionProperty(
|
||||||
type=UIPathProperty
|
type=UIPathProperty
|
||||||
)
|
)
|
||||||
|
bpy.types.Scene.cloudrig_ui_prop_selector = CollectionProperty(type=UIPathProperty)
|
||||||
|
bpy.types.Object.cloudrig_ui_new_element = PointerProperty(type=CloudRig_UIElement)
|
||||||
|
|
||||||
|
|
||||||
registry = [
|
registry = [
|
||||||
CLOUDRIG_OT_ui_element_add,
|
CLOUDRIG_OT_ui_element_add,
|
||||||
|
CLOUDRIG_OT_ui_element_edit,
|
||||||
CLOUDRIG_OT_ui_element_remove,
|
CLOUDRIG_OT_ui_element_remove,
|
||||||
]
|
]
|
Loading…
Reference in New Issue
Block a user