1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
10 from itertools
import count
11 from collections
import defaultdict
12 from typing
import TYPE_CHECKING
, Any
, Optional
13 from bpy
.types
import bpy_struct
, Constraint
, Object
, PoseBone
, Bone
, Armature
15 from bpy
.types
import bpy_prop_array
, bpy_prop_collection
# noqa
16 from idprop
.types
import IDPropertyArray
17 from mathutils
import Vector
19 from .misc
import ArmatureObject
, wrap_list_to_lines
, IdPropSequence
, find_index
, flatten_children
22 from ..base_rig
import BaseRig
23 from .. import RigifyColorSet
25 RIG_DIR
= "rigs" # Name of the directory where rig types are kept
26 METARIG_DIR
= "metarigs" # Name of the directory where metarigs are kept
27 TEMPLATE_DIR
= "ui_templates" # Name of the directory where ui templates are kept
29 # noinspection SpellCheckingInspection
30 outdated_types
= {"pitchipoy.limbs.super_limb": "limbs.super_limb",
31 "pitchipoy.limbs.super_arm": "limbs.super_limb",
32 "pitchipoy.limbs.super_leg": "limbs.super_limb",
33 "pitchipoy.limbs.super_front_paw": "limbs.super_limb",
34 "pitchipoy.limbs.super_rear_paw": "limbs.super_limb",
35 "pitchipoy.limbs.super_finger": "limbs.super_finger",
36 "pitchipoy.super_torso_turbo": "spines.super_spine",
37 "pitchipoy.simple_tentacle": "limbs.simple_tentacle",
38 "pitchipoy.super_face": "faces.super_face",
39 "pitchipoy.super_palm": "limbs.super_palm",
40 "pitchipoy.super_copy": "basic.super_copy",
41 "pitchipoy.tentacle": "",
42 "palm": "limbs.super_palm",
43 "basic.copy": "basic.super_copy",
53 def get_rigify_type(pose_bone
: PoseBone
) -> str:
54 rigify_type
= pose_bone
.rigify_type
# noqa
55 return rigify_type
.replace(" ", "")
58 def get_rigify_params(pose_bone
: PoseBone
) -> Any
:
59 return pose_bone
.rigify_parameters
# noqa
62 def get_rigify_colors(arm
: Armature
) -> IdPropSequence
['RigifyColorSet']:
63 return arm
.rigify_colors
# noqa
66 def get_rigify_target_rig(arm
: Armature
) -> Optional
[ArmatureObject
]:
67 return arm
.rigify_target_rig
# noqa
70 def get_rigify_rig_basename(arm
: Armature
) -> str:
71 return arm
.rigify_rig_basename
# noqa
74 def get_rigify_mirror_widgets(arm
: Armature
) -> bool:
75 return arm
.rigify_mirror_widgets
# noqa
78 def get_rigify_force_widget_update(arm
: Armature
) -> bool:
79 return arm
.rigify_force_widget_update
# noqa
82 def get_rigify_finalize_script(arm
: Armature
) -> Optional
[bpy
.types
.Text
]:
83 return arm
.rigify_finalize_script
# noqa
86 def is_rig_base_bone(obj
: Object
, name
):
87 return bool(get_rigify_type(obj
.pose
.bones
[name
]))
90 def metarig_needs_upgrade(obj
):
91 return bool(obj
.data
.get("rigify_layers"))
94 def is_valid_metarig(context
, *, allow_needs_upgrade
=False):
96 if not context
.object:
98 if obj
.type != 'ARMATURE' or obj
.data
.get("rig_id") is not None:
100 return allow_needs_upgrade
or not metarig_needs_upgrade(context
.object)
103 def upgrade_metarig_types(metarig
: Object
, revert
=False):
105 Replaces rigify_type properties from old versions with their current names.
107 metarig: rig to update.
108 revert: revert types to previous version (if old type available)
112 vals
= list(outdated_types
.values())
113 rig_defs
= {v
: k
for k
, v
in outdated_types
.items() if vals
.count(v
) == 1}
115 rig_defs
= outdated_types
117 for bone
in metarig
.pose
.bones
:
118 rig_type
= bone
.rigify_type
119 if rig_type
in rig_defs
:
120 bone
.rigify_type
= rig_defs
[rig_type
]
122 parameters
= get_rigify_params(bone
)
124 if 'leg' in rig_type
:
125 parameters
.limb_type
= 'leg'
126 if 'arm' in rig_type
:
127 parameters
.limb_type
= 'arm'
128 if 'paw' in rig_type
:
129 parameters
.limb_type
= 'paw'
130 if rig_type
== "basic.copy":
131 parameters
.make_widget
= False
134 def resolve_layer_names(layers
):
135 """Combine full layer names if some buttons use fragments with parentheses only."""
137 ui_rows
= defaultdict(list)
138 name_counts
= defaultdict(int)
140 for i
, layer
in enumerate(layers
):
141 if name
:= layer
.get("name", "").strip():
142 name_counts
[name
] += 1
143 ui_rows
[layer
.get("row", 1)].append(name
)
146 return name_counts
[n
] > 1 or n
.startswith("(")
148 def clean_stem(raw_name
):
149 return re
.sub(r
"\s*\(.*\)$", "", raw_name
)
151 def search_left(my_row
, col_idx
):
154 if not needs_rename(my_row
[col_idx
]):
155 return clean_stem(my_row
[col_idx
])
157 def search_up(my_row
, row_idx
, col_idx
):
160 prev_row
= ui_rows
[row_idx
]
161 if len(prev_row
) != len(my_row
):
163 if not needs_rename(prev_row
[col_idx
]):
164 return clean_stem(prev_row
[col_idx
])
168 for i
, layer
in enumerate(layers
):
169 name
: str = layer
.get("name", "").strip()
171 if name
and needs_rename(name
):
172 row
= layer
.get("row", 1)
174 cur_row
= ui_rows
[row
]
175 cur_col
= cur_row
.index(name
)
177 if stem
:= search_left(cur_row
, cur_col
):
178 name
= stem
+ " " + name
179 elif stem
:= search_up(cur_row
, row
, cur_col
):
180 name
= stem
+ " " + name
187 def upgrade_metarig_layers(metarig
: ArmatureObject
):
188 from .layers
import (REFS_LIST_SUFFIX
, DEF_COLLECTION
, MCH_COLLECTION
, ORG_COLLECTION
, ROOT_COLLECTION
,
189 ensure_collection_uid
)
193 # Find layer collections
196 for coll
in arm
.collections_all
:
197 if m
:= re
.match(r
'^Layer (\d+)', coll
.name
):
198 coll_table
[int(m
[1]) - 1] = coll
200 # Assign names to special layers if they exist
201 special_layers
= {28: ROOT_COLLECTION
, 29: DEF_COLLECTION
, 30: MCH_COLLECTION
, 31: ORG_COLLECTION
}
203 for idx
, name
in special_layers
.items():
204 if coll
:= coll_table
.get(idx
):
207 # Apply existing layer metadata
208 if layers
:= arm
.get("rigify_layers"):
209 names
= resolve_layer_names(layers
)
211 # Enforce the special names
212 for idx
, name
in special_layers
.items():
213 if idx
< len(names
) and names
[idx
]:
218 for i
, layer
in enumerate(layers
):
219 coll
= coll_table
.get(i
)
221 old_name
= layer
.get("name", "").strip()
226 coll
= arm
.collections
.new(new_name
)
232 coll_idx
= find_index(arm
.collections_all
, coll
)
233 arm
.collections
.move(coll_idx
, cur_idx
)
236 coll
.rigify_ui_row
= layer
.get("row", 1)
238 if old_name
and old_name
!= coll
.name
:
239 coll
.rigify_ui_title
= old_name
241 coll
.rigify_sel_set
= layer
.get("selset", False)
242 coll
.rigify_color_set_id
= layer
.get("group_prop", 0)
244 del arm
["rigify_layers"]
246 arm
.collections
.active_index
= 0
248 # Remove empty rows, and ensure the root button position is at the bottom
249 root_bcoll
= coll_table
.get(28)
252 for bcoll
in arm
.collections_all
:
253 if bcoll
!= root_bcoll
and bcoll
.rigify_ui_row
> 0:
254 used_rows
.add(bcoll
.rigify_ui_row
)
257 for i
in range(1, max(used_rows
) + 1):
259 row_map
[i
] = len(row_map
) + 1
261 for bcoll
in arm
.collections_all
:
262 if bcoll
== root_bcoll
:
263 bcoll
.rigify_ui_row
= len(row_map
) + 3
264 elif bcoll
.rigify_ui_row
> 0:
265 bcoll
.rigify_ui_row
= row_map
[bcoll
.rigify_ui_row
]
267 # Convert the layer references in rig component parameters
268 default_layers
= [i
== 1 for i
in range(32)]
270 'faces.super_face': ['primary', 'secondary'],
271 'limbs.arm': ['fk', 'tweak'],
272 'limbs.front_paw': ['fk', 'tweak'],
273 'limbs.leg': ['fk', 'tweak'],
274 'limbs.paw': ['fk', 'tweak'],
275 'limbs.rear_paw': ['fk', 'tweak'],
276 'limbs.simple_tentacle': ['tweak'],
277 'limbs.super_finger': ['tweak'],
278 'limbs.super_limb': ['fk', 'tweak'],
279 'spines.basic_spine': ['fk', 'tweak'],
282 for pose_bone
in metarig
.pose
.bones
:
283 params
= get_rigify_params(pose_bone
)
285 # Work around the stupid legacy default where one layer is implicitly selected
286 for name_stem
in default_map
.get(get_rigify_type(pose_bone
), []):
287 prop_name
= name_stem
+ "_layers"
288 if prop_name
not in params
and name_stem
+ REFS_LIST_SUFFIX
not in params
:
289 params
[prop_name
] = default_layers
291 for prop_name
, prop_value
in list(params
.items()):
292 if prop_name
.endswith("_layers") and isinstance(prop_value
, IDPropertyArray
) and len(prop_value
) == 32:
295 for i
, show
in enumerate(prop_value
.to_list()):
297 coll
= coll_table
.get(i
)
298 uid
= ensure_collection_uid(coll
) if coll
else i
299 name
= coll
.name
if coll
else f
"Layer {i+1}"
300 entries
.append({"uid": uid
, "name": name
})
302 params
[prop_name
[:-7] + REFS_LIST_SUFFIX
] = entries
304 del params
[prop_name
]
307 ##############################################
309 ##############################################
311 def rig_is_child(rig
: 'BaseRig', parent
: Optional
['BaseRig'], *, strict
=False):
313 Checks if the rig is a child of the parent.
314 Unless strict is True, returns true if the rig and parent are the same.
320 rig
= rig
.rigify_parent
326 rig
= rig
.rigify_parent
331 def get_parent_rigs(rig
: 'BaseRig') -> list['BaseRig']:
332 """Returns a list containing the rig and all of its parents."""
336 rig
= rig
.rigify_parent
340 def get_resource(resource_name
):
341 """ Fetches a rig module by name, and returns it.
343 module
= importlib
.import_module(resource_name
)
344 importlib
.reload(module
)
348 def connected_children_names(obj
: ArmatureObject
, bone_name
: str) -> list[str]:
349 """ Returns a list of bone names (in order) of the bones that form a single
350 connected chain starting with the given bone as a parent.
351 If there is a connected branch, the list stops there.
353 bone
= obj
.data
.bones
[bone_name
]
360 for child
in bone
.children
:
361 if child
.use_connect
:
363 con_name
= child
.name
367 bone
= obj
.data
.bones
[con_name
]
374 def has_connected_children(bone
: Bone
):
375 """ Returns true/false whether a bone has connected children or not.
378 for b
in bone
.children
:
379 t
= t
or b
.use_connect
383 def _list_bone_names_depth_first_sorted_rec(result_list
: list[str], bone
: Bone
):
384 result_list
.append(bone
.name
)
386 for child
in sorted(list(bone
.children
), key
=lambda b
: b
.name
):
387 _list_bone_names_depth_first_sorted_rec(result_list
, child
)
390 def list_bone_names_depth_first_sorted(obj
: ArmatureObject
):
391 """Returns a list of bone names in depth first name sorted order."""
394 for bone
in sorted(list(obj
.data
.bones
), key
=lambda b
: b
.name
):
395 if bone
.parent
is None:
396 _list_bone_names_depth_first_sorted_rec(result_list
, bone
)
401 def _get_property_value(obj
, name
: str):
402 """Retrieve the attribute value, converting from Blender to python types."""
403 value
= getattr(obj
, name
, None)
404 if isinstance(value
, bpy_prop_array
):
409 def _format_property_value(prefix
: str, value
: Any
, *, limit
=90, indent
=4) -> list[str]:
410 """Format a property value assignment to lines, wrapping if too long."""
412 if isinstance(value
, tuple):
413 return wrap_list_to_lines(prefix
, '()', map(repr, value
), limit
=limit
, indent
=indent
)
415 if isinstance(value
, list):
416 return wrap_list_to_lines(prefix
, '[]', map(repr, value
), limit
=limit
, indent
=indent
)
418 return [prefix
+ repr(value
)]
421 def _generate_properties(lines
, prefix
, obj
: bpy_struct
, base_class
: type, *,
422 defaults
: Optional
[dict[str, Any
]] = None,
423 objects
: Optional
[dict[Any
, str]] = None):
424 obj_rna
: bpy
.types
.Struct
= type(obj
).bl_rna
# noqa
425 base_rna
: bpy
.types
.Struct
= base_class
.bl_rna
# noqa
427 defaults
= defaults
or {}
428 block_props
= set(prop
.identifier
for prop
in base_rna
.properties
) - set(defaults
.keys())
430 for prop
in obj_rna
.properties
:
431 if prop
.identifier
not in block_props
and not prop
.is_readonly
:
432 cur_value
= _get_property_value(obj
, prop
.identifier
)
434 if prop
.identifier
in defaults
:
435 if cur_value
== defaults
[prop
.identifier
]:
438 if isinstance(cur_value
, bpy_struct
):
439 if objects
and cur_value
in objects
:
440 lines
.append('%s.%s = %s' % (prefix
, prop
.identifier
, objects
[cur_value
]))
442 lines
+= _format_property_value('%s.%s = ' % (prefix
, prop
.identifier
), cur_value
)
445 def write_metarig_widgets(obj
: Object
):
446 from .widgets
import write_widget
450 for pbone
in obj
.pose
.bones
:
451 if pbone
.custom_shape
:
452 widget_set
.add(pbone
.custom_shape
)
458 for widget_obj
in widget_set
:
459 ident
= re
.sub("[^0-9a-zA-Z_]+", "_", widget_obj
.name
)
463 if ident
+'_'+str(i
) not in id_set
:
467 widget_map
[widget_obj
] = ident
469 code
.append(write_widget(widget_obj
, name
=ident
, use_size
=False))
471 return widget_map
, code
474 def write_metarig(obj
: ArmatureObject
, layers
=False, func_name
="create",
475 groups
=False, widgets
=False):
477 Write a metarig as a python script, this rig is to have all info needed for
478 generating the real rig with rigify.
480 from .layers
import REFS_LIST_SUFFIX
, is_collection_ref_list_prop
484 "from rna_prop_ui import rna_idprop_ui_create\n",
485 "from mathutils import Color\n\n",
488 # Widget object creation functions if requested
490 widget_map
, widget_code
= write_metarig_widgets(obj
)
493 code
.append("from rigify.utils.widgets import widget_generator\n\n")
498 # Start of the metarig function
499 code
.append("def %s(obj): # noqa" % func_name
)
500 code
.append(" # generated by rigify.utils.write_metarig")
501 bpy
.ops
.object.mode_set(mode
='EDIT')
502 code
.append(" bpy.ops.object.mode_set(mode='EDIT')")
503 code
.append(" arm = obj.data")
507 # Rigify bone group colors info
508 rigify_colors
= get_rigify_colors(arm
)
510 if groups
and len(rigify_colors
) > 0:
511 code
.append("\n for i in range(" + str(len(rigify_colors
)) + "):")
512 code
.append(" arm.rigify_colors.add()\n")
514 for i
in range(len(rigify_colors
)):
515 name
= rigify_colors
[i
].name
516 active
= rigify_colors
[i
].active
517 normal
= rigify_colors
[i
].normal
518 select
= rigify_colors
[i
].select
519 standard_colors_lock
= rigify_colors
[i
].standard_colors_lock
520 code
.append(' arm.rigify_colors[' + str(i
) + '].name = "' + name
+ '"')
521 code
.append(' arm.rigify_colors[' + str(i
)
522 + '].active = Color((%.4f, %.4f, %.4f))' % tuple(active
[:]))
523 code
.append(' arm.rigify_colors[' + str(i
)
524 + '].normal = Color((%.4f, %.4f, %.4f))' % tuple(normal
[:]))
525 code
.append(' arm.rigify_colors[' + str(i
)
526 + '].select = Color((%.4f, %.4f, %.4f))' % tuple(select
[:]))
527 code
.append(' arm.rigify_colors[' + str(i
)
528 + '].standard_colors_lock = ' + str(standard_colors_lock
))
530 # Rigify collection layout info
533 'ui_row': 0, 'ui_title': '', 'sel_set': False, 'color_set_id': 0
536 code
.append('\n bone_collections = {}')
538 code
.append('\n for bcoll in list(arm.collections_all):'
539 '\n arm.collections.remove(bcoll)\n')
541 args
= ', '.join(f
'{k}={repr(v)}' for k
, v
in collection_attrs
.items())
543 code
.append(f
" def add_bone_collection(name, *, parent=None, {args}):")
544 code
.append(f
" new_bcoll = arm.collections.new(name, parent=bone_collections.get(parent))")
545 for k
, _v
in collection_attrs
.items():
546 code
.append(f
" new_bcoll.rigify_{k} = {k}")
547 code
.append(" bone_collections[name] = new_bcoll")
550 def assign_bone_collections(pose_bone, *coll_names):
551 assert not len(pose_bone.bone.collections)
552 for name in coll_names:
553 bone_collections[name].assign(pose_bone)
555 def assign_bone_collection_refs(params, attr_name, *coll_names):
556 ref_list = getattr(params, attr_name + '_coll_refs', None)
557 if ref_list is not None:
558 for name in coll_names:
559 ref_list.add().set_collection(bone_collections[name])
562 for bcoll
in flatten_children(arm
.collections
):
563 args
= [repr(bcoll
.name
)]
565 args
.append(f
"parent={bcoll.parent.name!r}")
566 for k
, v
in collection_attrs
.items():
567 value
= getattr(bcoll
, "rigify_" + k
)
569 args
.append(f
"{k}={repr(value)}")
570 code
.append(f
" add_bone_collection({', '.join(args)})")
572 # write parents first
573 bones
= [(len(bone
.parent_recursive
), bone
.name
) for bone
in arm
.edit_bones
]
574 bones
.sort(key
=lambda item
: item
[0])
575 bones
= [item
[1] for item
in bones
]
577 code
.append("\n bones = {}\n")
579 # noinspection SpellCheckingInspection
582 'bbone_mapping_mode': 'STRAIGHT',
583 'bbone_easein': 1, 'bbone_easeout': 1,
584 'bbone_rollin': 0, 'bbone_rollout': 0,
585 'bbone_curveinx': 0, 'bbone_curveinz': 0,
586 'bbone_curveoutx': 0, 'bbone_curveoutz': 0,
587 'bbone_scalein': Vector((1, 1, 1)),
588 'bbone_scaleout': Vector((1, 1, 1)),
591 for bone_name
in bones
:
592 bone
= arm
.edit_bones
[bone_name
]
593 code
.append(" bone = arm.edit_bones.new(%r)" % bone
.name
)
594 code
.append(" bone.head = %.4f, %.4f, %.4f" % bone
.head
.to_tuple(4))
595 code
.append(" bone.tail = %.4f, %.4f, %.4f" % bone
.tail
.to_tuple(4))
596 code
.append(" bone.roll = %.4f" % bone
.roll
)
597 code
.append(" bone.use_connect = %s" % str(bone
.use_connect
))
598 if bone
.inherit_scale
!= 'FULL':
599 code
.append(" bone.inherit_scale = %r" % str(bone
.inherit_scale
))
601 code
.append(" bone.parent = arm.edit_bones[bones[%r]]" % bone
.parent
.name
)
602 for prop
, default
in extra_props
.items():
603 value
= getattr(bone
, prop
)
605 code
.append(f
" bone.{prop} = {value!r}")
606 code
.append(" bones[%r] = bone.name" % bone
.name
)
608 bpy
.ops
.object.mode_set(mode
='OBJECT')
610 code
.append(" bpy.ops.object.mode_set(mode='OBJECT')")
612 if widgets
and widget_map
:
613 code
.append(" widget_map = {}")
615 # Rig type and other pose properties
616 for bone_name
in bones
:
617 pbone
= obj
.pose
.bones
[bone_name
]
619 rigify_type
= get_rigify_type(pbone
)
620 rigify_parameters
= get_rigify_params(pbone
)
622 code
.append(" pbone = obj.pose.bones[bones[%r]]" % bone_name
)
623 code
.append(" pbone.rigify_type = %r" % rigify_type
)
624 code
.append(" pbone.lock_location = %s" % str(tuple(pbone
.lock_location
)))
625 code
.append(" pbone.lock_rotation = %s" % str(tuple(pbone
.lock_rotation
)))
626 code
.append(" pbone.lock_rotation_w = %s" % str(pbone
.lock_rotation_w
))
627 code
.append(" pbone.lock_scale = %s" % str(tuple(pbone
.lock_scale
)))
628 code
.append(" pbone.rotation_mode = %r" % pbone
.rotation_mode
)
629 if layers
and len(pbone
.bone
.collections
):
630 args
= ', '.join(f
"'{bcoll.name}'" for bcoll
in pbone
.bone
.collections
)
631 code
.append(f
" assign_bone_collections(pbone, {args})")
633 # Rig type parameters
634 for param_name
in rigify_parameters
.keys():
635 param
= _get_property_value(rigify_parameters
, param_name
)
637 if isinstance(param
, bpy_prop_collection
):
638 if layers
and param_name
.endswith(REFS_LIST_SUFFIX
) and is_collection_ref_list_prop(param
):
639 bcoll_set
= [item
.find_collection() for item
in param
]
640 bcoll_set
= [bcoll
for bcoll
in bcoll_set
if bcoll
is not None]
641 if len(bcoll_set
) > 0:
642 args
= ', '.join(f
"'{bcoll.name}'" for bcoll
in bcoll_set
)
643 code
.append(f
" assign_bone_collection_refs("
644 f
"pbone.rigify_parameters, '{param_name[:-10]}', {args})")
647 if param
is not None:
649 code
+= _format_property_value(
650 f
" pbone.rigify_parameters.{param_name} = ", param
)
651 code
.append(" except AttributeError:")
655 custom_properties
= {
656 property_name
: value
for property_name
, value
in pbone
.items()
657 if property_name
not in pbone
.bl_rna
.properties
.keys()
658 and type(pbone
[property_name
]) in (float, int)
661 if custom_properties
:
662 code
.append(' # custom properties')
664 for custom_property
, current_value
in custom_properties
.items():
665 props_data
= pbone
.id_properties_ui(custom_property
).as_dict()
666 code
.append(f
" rna_idprop_ui_create(")
667 code
.append(f
" pbone,")
668 code
.append(f
" {custom_property!r},")
669 code
.append(f
" default={props_data['default']!r},")
670 if 'min' in props_data
:
671 code
.append(f
" min={props_data['min']},")
672 if 'max' in props_data
:
673 code
.append(f
" max={props_data['max']},")
674 if 'soft_min' in props_data
:
675 code
.append(f
" soft_min={props_data['soft_min']},")
676 if 'soft_max' in props_data
:
677 code
.append(f
" soft_max={props_data['soft_max']},")
678 if 'subtype' in props_data
:
679 code
.append(f
" subtype={props_data['subtype']!r},")
680 if 'description' in props_data
:
681 code
.append(f
" description={props_data['description']!r},")
682 if 'precision' in props_data
:
683 code
.append(f
" precision={props_data['precision']},")
684 if 'step' in props_data
:
685 code
.append(f
" step={props_data['step']},")
687 if props_data
['default'] != current_value
:
688 code
.append(f
" pbone[{custom_property!r}] = {current_value}")
691 for con
in pbone
.constraints
:
692 code
.append(" con = pbone.constraints.new(%r)" % con
.type)
693 code
.append(" con.name = %r" % con
.name
)
694 # Add target first because of target_space handling
695 if con
.type == 'ARMATURE':
696 for tgt
in con
.targets
:
697 code
.append(" tgt = con.targets.new()")
698 code
.append(" tgt.target = obj")
699 code
.append(" tgt.subtarget = %r" % tgt
.subtarget
)
700 code
.append(" tgt.weight = %.3f" % tgt
.weight
)
701 elif getattr(con
, 'target', None) == obj
:
702 code
.append(" con.target = obj")
704 _generate_properties(
705 code
, " con", con
, Constraint
,
707 'owner_space': 'WORLD', 'target_space': 'WORLD',
708 'mute': False, 'influence': 1.0,
711 objects
={obj
: 'obj'},
714 if widgets
and pbone
.custom_shape
:
715 widget_id
= widget_map
[pbone
.custom_shape
]
716 code
.append(" if %r not in widget_map:" % widget_id
)
717 code
.append((" widget_map[%r] = create_%s_widget(obj, pbone.name, "
718 "widget_name=%r, widget_force_new=True)")
719 % (widget_id
, widget_id
, pbone
.custom_shape
.name
))
720 code
.append(" pbone.custom_shape = widget_map[%r]" % widget_id
)
722 code
.append("\n bpy.ops.object.mode_set(mode='EDIT')")
723 code
.append(" for bone in arm.edit_bones:")
724 code
.append(" bone.select = False")
725 code
.append(" bone.select_head = False")
726 code
.append(" bone.select_tail = False")
728 code
.append(" for b in bones:")
729 code
.append(" bone = arm.edit_bones[bones[b]]")
730 code
.append(" bone.select = True")
731 code
.append(" bone.select_head = True")
732 code
.append(" bone.select_tail = True")
733 code
.append(" bone.bbone_x = bone.bbone_z = bone.length * 0.05")
734 code
.append(" arm.edit_bones.active = bone")
737 code
.append(" if bcoll := arm.collections.active:")
738 code
.append(" bcoll.assign(bone)")
740 code
.append("\n arm.collections.active_index = 0")
742 code
.append("\n return bones")
744 code
.append('\n\nif __name__ == "__main__":')
745 code
.append(" " + func_name
+ "(bpy.context.active_object)\n")
747 return "\n".join(code
)