Import_3ds: Improved distance cue chunk import
[blender-addons.git] / rigify / utils / rig.py
blobb079f28e8357994671fd3e8389ddfe94c0f0af80
1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 import importlib
7 import importlib.util
8 import re
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
21 if TYPE_CHECKING:
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",
44 "biped.arm": "",
45 "biped.leg": "",
46 "finger": "",
47 "neck_short": "",
48 "misc.delta": "",
49 "spine": ""
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):
95 obj = context.object
96 if not context.object:
97 return False
98 if obj.type != 'ARMATURE' or obj.data.get("rig_id") is not None:
99 return False
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)
111 if revert:
112 vals = list(outdated_types.values())
113 rig_defs = {v: k for k, v in outdated_types.items() if vals.count(v) == 1}
114 else:
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)
145 def needs_rename(n):
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):
152 while col_idx > 0:
153 col_idx -= 1
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):
158 while row_idx > 1:
159 row_idx -= 1
160 prev_row = ui_rows[row_idx]
161 if len(prev_row) != len(my_row):
162 return None
163 if not needs_rename(prev_row[col_idx]):
164 return clean_stem(prev_row[col_idx])
166 names = []
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
182 names.append(name)
184 return names
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)
191 arm = metarig.data
193 # Find layer collections
194 coll_table = {}
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):
205 coll.name = name
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]:
214 names[idx] = name
216 cur_idx = 0
218 for i, layer in enumerate(layers):
219 coll = coll_table.get(i)
221 old_name = layer.get("name", "").strip()
222 new_name = names[i]
224 if new_name:
225 if not coll:
226 coll = arm.collections.new(new_name)
227 coll_table[i] = coll
228 else:
229 coll.name = new_name
231 if coll:
232 coll_idx = find_index(arm.collections_all, coll)
233 arm.collections.move(coll_idx, cur_idx)
234 cur_idx += 1
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)
251 used_rows = set()
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)
256 row_map = {}
257 for i in range(1, max(used_rows) + 1):
258 if i in used_rows:
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)]
269 default_map = {
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:
293 entries = []
295 for i, show in enumerate(prop_value.to_list()):
296 if show:
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 ##############################################
308 # Misc
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.
316 if parent is None:
317 return True
319 if rig and strict:
320 rig = rig.rigify_parent
322 while rig:
323 if rig is parent:
324 return True
326 rig = rig.rigify_parent
328 return False
331 def get_parent_rigs(rig: 'BaseRig') -> list['BaseRig']:
332 """Returns a list containing the rig and all of its parents."""
333 result = []
334 while rig:
335 result.append(rig)
336 rig = rig.rigify_parent
337 return result
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)
345 return 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]
354 names = []
356 while True:
357 connects = 0
358 con_name = ""
360 for child in bone.children:
361 if child.use_connect:
362 connects += 1
363 con_name = child.name
365 if connects == 1:
366 names += [con_name]
367 bone = obj.data.bones[con_name]
368 else:
369 break
371 return names
374 def has_connected_children(bone: Bone):
375 """ Returns true/false whether a bone has connected children or not.
377 t = False
378 for b in bone.children:
379 t = t or b.use_connect
380 return t
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."""
392 result_list = []
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)
398 return result_list
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):
405 value = tuple(value)
406 return value
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]:
436 continue
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]))
441 else:
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
448 widget_set = set()
450 for pbone in obj.pose.bones:
451 if pbone.custom_shape:
452 widget_set.add(pbone.custom_shape)
454 id_set = set()
455 widget_map = {}
456 code = []
458 for widget_obj in widget_set:
459 ident = re.sub("[^0-9a-zA-Z_]+", "_", widget_obj.name)
461 if ident in id_set:
462 for i in count(1):
463 if ident+'_'+str(i) not in id_set:
464 break
466 id_set.add(ident)
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
482 code = [
483 "import bpy\n",
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
489 if widgets:
490 widget_map, widget_code = write_metarig_widgets(obj)
492 if widget_map:
493 code.append("from rigify.utils.widgets import widget_generator\n\n")
494 code += widget_code
495 else:
496 widget_map = {}
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")
505 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
531 if layers:
532 collection_attrs = {
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")
549 code.append("""
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])
560 """)
562 for bcoll in flatten_children(arm.collections):
563 args = [repr(bcoll.name)]
564 if bcoll.parent:
565 args.append(f"parent={bcoll.parent.name!r}")
566 for k, v in collection_attrs.items():
567 value = getattr(bcoll, "rigify_" + k)
568 if value != v:
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
580 extra_props = {
581 'bbone_segments': 1,
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))
600 if bone.parent:
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)
604 if value != default:
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')
609 code.append("")
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})")
645 continue
647 if param is not None:
648 code.append(" try:")
649 code += _format_property_value(
650 f" pbone.rigify_parameters.{param_name} = ", param)
651 code.append(" except AttributeError:")
652 code.append(" pass")
654 # Custom properties
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']},")
686 code.append(f" )")
687 if props_data['default'] != current_value:
688 code.append(f" pbone[{custom_property!r}] = {current_value}")
690 # Constraints
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")
703 # Generic properties
704 _generate_properties(
705 code, " con", con, Constraint,
706 defaults={
707 'owner_space': 'WORLD', 'target_space': 'WORLD',
708 'mute': False, 'influence': 1.0,
709 'target': obj,
711 objects={obj: 'obj'},
713 # Custom widgets
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")
736 if not layers:
737 code.append(" if bcoll := arm.collections.active:")
738 code.append(" bcoll.assign(bone)")
739 else:
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)