Import_3ds: Improved distance cue node setup
[blender-addons.git] / rigify / utils / action_layers.py
blob5e7caccd71c0fe5bf0a390874842fc36f336bce7
1 # SPDX-FileCopyrightText: 2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 from typing import Optional, List, Dict, Tuple, TYPE_CHECKING
6 from bpy.types import Action, Mesh, Armature
7 from bl_math import clamp
9 from .errors import MetarigError
10 from .misc import MeshObject, IdPropSequence, verify_mesh_obj
11 from .naming import Side, get_name_side, change_name_side, mirror_name
12 from .bones import BoneUtilityMixin
13 from .mechanism import MechanismUtilityMixin, driver_var_transform, quote_property
15 from ..base_rig import RigComponent, stage
16 from ..base_generate import GeneratorPlugin
18 if TYPE_CHECKING:
19 from ..operators.action_layers import ActionSlot
22 def get_rigify_action_slots(metarig_data: Armature) -> IdPropSequence['ActionSlot']:
23 return metarig_data.rigify_action_slots # noqa
26 class ActionSlotBase:
27 """Abstract non-RNA base for the action list slots."""
29 action: Optional[Action]
30 enabled: bool
31 symmetrical: bool
32 subtarget: str
33 transform_channel: str
34 target_space: str
35 frame_start: int
36 frame_end: int
37 trans_min: float
38 trans_max: float
39 is_corrective: bool
40 trigger_action_a: Optional[Action]
41 trigger_action_b: Optional[Action]
43 ############################################
44 # Action Constraint Setup
46 @property
47 def keyed_bone_names(self) -> List[str]:
48 """Return a list of bone names that have keyframes in the Action of this Slot."""
49 keyed_bones = []
51 for fc in self.action.fcurves:
52 # Extracting bone name from fcurve data path
53 if fc.data_path.startswith('pose.bones["'):
54 bone_name = fc.data_path[12:].split('"]')[0]
56 if bone_name not in keyed_bones:
57 keyed_bones.append(bone_name)
59 return keyed_bones
61 @property
62 def do_symmetry(self) -> bool:
63 return self.symmetrical and get_name_side(self.subtarget) != Side.MIDDLE
65 @property
66 def default_side(self):
67 return get_name_side(self.subtarget)
69 def get_min_max(self, side=Side.MIDDLE) -> Tuple[float, float]:
70 if side == -self.default_side:
71 # Flip min/max in some cases - based on code of Paste Pose Flipped
72 if self.transform_channel in ['LOCATION_X', 'ROTATION_Z', 'ROTATION_Y']:
73 return -self.trans_min, -self.trans_max
74 return self.trans_min, self.trans_max
76 def get_factor_expression(self, var, side=Side.MIDDLE):
77 assert not self.is_corrective
79 trans_min, trans_max = self.get_min_max(side)
81 if 'ROTATION' in self.transform_channel:
82 var = f'({var}*180/pi)'
84 return f'clamp(({var} - {trans_min:.4}) / {trans_max - trans_min:.4})'
86 def get_trigger_expression(self, var_a, var_b):
87 assert self.is_corrective
89 return f'clamp({var_a} * {var_b})'
91 ##################################
92 # Default Frame
94 def get_default_channel_value(self) -> float:
95 # The default transformation value for rotation and location is 0, but for scale it's 1.
96 return 1.0 if 'SCALE' in self.transform_channel else 0.0
98 def get_default_factor(self, side=Side.MIDDLE, *, triggers=None) -> float:
99 """ Based on the transform channel, and transform range,
100 calculate the evaluation factor in the default pose.
102 if self.is_corrective:
103 if not triggers or None in triggers:
104 return 0
106 val_a, val_b = [trigger.get_default_factor(side) for trigger in triggers]
108 return clamp(val_a * val_b)
110 else:
111 trans_min, trans_max = self.get_min_max(side)
113 if trans_min == trans_max:
114 # Avoid division by zero
115 return 0
117 def_val = self.get_default_channel_value()
118 factor = (def_val - trans_min) / (trans_max - trans_min)
120 return clamp(factor)
122 def get_default_frame(self, side=Side.MIDDLE, *, triggers=None) -> float:
123 """ Based on the transform channel, frame range and transform range,
124 we can calculate which frame within the action should have the keyframe
125 which has the default pose.
126 This is the frame which will be read when the transformation is at its default
127 (so 1.0 for scale and 0.0 for loc/rot)
129 factor = self.get_default_factor(side, triggers=triggers)
131 return self.frame_start * (1 - factor) + self.frame_end * factor
133 def is_default_frame_integer(self) -> bool:
134 default_frame = self.get_default_frame()
136 return abs(default_frame - round(default_frame)) < 0.001
139 class GeneratedActionSlot(ActionSlotBase):
140 """Non-RNA version of the action list slot."""
142 def __init__(self, action, *, enabled=True, symmetrical=True, subtarget='',
143 transform_channel='LOCATION_X', target_space='LOCAL', frame_start=0,
144 frame_end=2, trans_min=-0.05, trans_max=0.05, is_corrective=False,
145 trigger_action_a=None, trigger_action_b=None):
146 self.action = action
147 self.enabled = enabled
148 self.symmetrical = symmetrical
149 self.subtarget = subtarget
150 self.transform_channel = transform_channel
151 self.target_space = target_space
152 self.frame_start = frame_start
153 self.frame_end = frame_end
154 self.trans_min = trans_min
155 self.trans_max = trans_max
156 self.is_corrective = is_corrective
157 self.trigger_action_a = trigger_action_a
158 self.trigger_action_b = trigger_action_b
161 class ActionLayer(RigComponent):
162 """An action constraint layer instance, applying an action to a symmetry side."""
164 rigify_sub_object_run_late = True
166 owner: 'ActionLayerBuilder'
167 slot: ActionSlotBase
168 side: Side
170 def __init__(self, owner, slot, side):
171 super().__init__(owner)
173 self.slot = slot
174 self.side = side
176 self.name = self._get_name()
178 self.use_trigger = False
180 if slot.is_corrective:
181 trigger_a = self.owner.action_map[slot.trigger_action_a.name]
182 trigger_b = self.owner.action_map[slot.trigger_action_b.name]
184 self.trigger_a = trigger_a.get(side) or trigger_a.get(Side.MIDDLE)
185 self.trigger_b = trigger_b.get(side) or trigger_b.get(Side.MIDDLE)
187 self.trigger_a.use_trigger = True
188 self.trigger_b.use_trigger = True
190 else:
191 self.bone_name = change_name_side(slot.subtarget, side)
193 self.bones = self._filter_bones()
195 self.owner.layers.append(self)
197 @property
198 def use_property(self):
199 return self.slot.is_corrective or self.use_trigger
201 def _get_name(self):
202 name = self.slot.action.name
204 if self.side == Side.LEFT:
205 name += ".L"
206 elif self.side == Side.RIGHT:
207 name += ".R"
209 return name
211 def _filter_bones(self):
212 controls = self._control_bones()
213 bones = [bone for bone in self.slot.keyed_bone_names if bone not in controls]
215 if self.side != Side.MIDDLE:
216 bones = [name for name in bones if get_name_side(name) in (self.side, Side.MIDDLE)]
218 return bones
220 def _control_bones(self):
221 if self.slot.is_corrective:
222 return self.trigger_a._control_bones() | self.trigger_b._control_bones()
223 elif self.slot.do_symmetry:
224 return {self.bone_name, mirror_name(self.bone_name)}
225 else:
226 return {self.bone_name}
228 def configure_bones(self):
229 if self.use_property:
230 factor = self.slot.get_default_factor(self.side)
232 self.make_property(self.owner.property_bone, self.name, float(factor))
234 def rig_bones(self):
235 if self.slot.is_corrective and self.use_trigger:
236 raise MetarigError(f"Corrective action used as trigger: {self.slot.action.name}")
238 if self.use_property:
239 self.rig_input_driver(self.owner.property_bone, quote_property(self.name))
241 for bone_name in self.bones:
242 self.rig_bone(bone_name)
244 def rig_bone(self, bone_name):
245 if bone_name not in self.obj.pose.bones:
246 raise MetarigError(
247 f"Bone '{bone_name}' from action '{self.slot.action.name}' not found")
249 if self.side != Side.MIDDLE and get_name_side(bone_name) == Side.MIDDLE:
250 influence = 0.5
251 else:
252 influence = 1.0
254 con = self.make_constraint(
255 bone_name, 'ACTION',
256 name=f'Action {self.name}',
257 insert_index=0,
258 use_eval_time=True,
259 action=self.slot.action,
260 frame_start=self.slot.frame_start,
261 frame_end=self.slot.frame_end,
262 mix_mode='BEFORE_SPLIT',
263 influence=influence,
266 self.rig_output_driver(con, 'eval_time')
268 def rig_output_driver(self, obj, prop):
269 if self.use_property:
270 self.make_driver(obj, prop, variables=[(self.owner.property_bone, self.name)])
271 else:
272 self.rig_input_driver(obj, prop)
274 def rig_input_driver(self, obj, prop):
275 if self.slot.is_corrective:
276 self.rig_corrective_driver(obj, prop)
277 else:
278 self.rig_factor_driver(obj, prop)
280 def rig_corrective_driver(self, obj, prop):
281 self.make_driver(
282 obj, prop,
283 expression=self.slot.get_trigger_expression('a', 'b'),
284 variables={
285 'a': (self.owner.property_bone, self.trigger_a.name),
286 'b': (self.owner.property_bone, self.trigger_b.name),
290 def rig_factor_driver(self, obj, prop):
291 if self.side != Side.MIDDLE:
292 control_name = change_name_side(self.slot.subtarget, self.side)
293 else:
294 control_name = self.slot.subtarget
296 if control_name not in self.obj.pose.bones:
297 raise MetarigError(
298 f"Control bone '{control_name}' for action '{self.slot.action.name}' not found")
300 channel = self.slot.transform_channel\
301 .replace("LOCATION", "LOC").replace("ROTATION", "ROT")
303 self.make_driver(
304 obj, prop,
305 expression=self.slot.get_factor_expression('var', side=self.side),
306 variables=[
307 driver_var_transform(
308 self.obj, control_name,
309 type=channel,
310 space=self.slot.target_space,
311 rotation_mode='SWING_TWIST_Y',
316 @stage.rig_bones
317 def rig_child_shape_keys(self):
318 for child in self.owner.child_meshes:
319 mesh: Mesh = child.data
321 if mesh.shape_keys:
322 for key_block in mesh.shape_keys.key_blocks[1:]:
323 if key_block.name == self.name:
324 self.rig_shape_key(key_block)
326 def rig_shape_key(self, key_block):
327 self.rig_output_driver(key_block, 'value')
330 class ActionLayerBuilder(GeneratorPlugin, BoneUtilityMixin, MechanismUtilityMixin):
332 Implements centralized generation of action layer constraints.
335 slot_list: List[ActionSlotBase]
336 layers: List[ActionLayer]
337 action_map: Dict[str, Dict[Side, ActionLayer]]
338 property_bone: Optional[str]
339 child_meshes: List[MeshObject]
341 def __init__(self, generator):
342 super().__init__(generator)
344 metarig_data = generator.metarig.data
345 self.slot_list = list(get_rigify_action_slots(metarig_data))
346 self.layers = []
348 def initialize(self):
349 if self.slot_list:
350 self.action_map = {}
351 self.rigify_sub_objects = []
353 # Generate layers for active valid slots
354 action_slots = [slot for slot in self.slot_list if slot.enabled and slot.action]
356 # Constraints will be added in reverse order because each one is added to the top
357 # of the stack when created. However, Before Original reverses the effective
358 # order of transformations again, restoring the original sequence.
359 for act_slot in self.sort_slots(action_slots):
360 self.spawn_slot_layers(act_slot)
362 @staticmethod
363 def sort_slots(slots: List[ActionSlotBase]):
364 indices = {slot.action.name: i for i, slot in enumerate(slots)}
366 def action_key(action: Action):
367 return indices.get(action.name, -1) if action else -1
369 def slot_key(slot: ActionSlotBase):
370 # Ensure corrective actions are added after their triggers.
371 if slot.is_corrective:
372 return max(action_key(slot.action),
373 action_key(slot.trigger_action_a) + 0.5,
374 action_key(slot.trigger_action_b) + 0.5)
375 else:
376 return action_key(slot.action)
378 return sorted(slots, key=slot_key)
380 def spawn_slot_layers(self, act_slot):
381 name = act_slot.action.name
383 if name in self.action_map:
384 raise MetarigError(f"Action slot with duplicate action: {name}")
386 if act_slot.is_corrective:
387 if not act_slot.trigger_action_a or not act_slot.trigger_action_b:
388 raise MetarigError(f"Action slot has missing triggers: {name}")
390 trigger_a = self.action_map.get(act_slot.trigger_action_a.name)
391 trigger_b = self.action_map.get(act_slot.trigger_action_b.name)
393 if not trigger_a or not trigger_b:
394 raise MetarigError(f"Action slot references missing trigger slot(s): {name}")
396 symmetry = Side.LEFT in trigger_a or Side.LEFT in trigger_b
398 else:
399 symmetry = act_slot.do_symmetry
401 if symmetry:
402 self.action_map[name] = {
403 Side.LEFT: ActionLayer(self, act_slot, Side.LEFT),
404 Side.RIGHT: ActionLayer(self, act_slot, Side.RIGHT),
406 else:
407 self.action_map[name] = {
408 Side.MIDDLE: ActionLayer(self, act_slot, Side.MIDDLE)
411 def generate_bones(self):
412 if any(child.use_property for child in self.layers):
413 self.property_bone = self.new_bone("MCH-action-props")
415 def rig_bones(self):
416 if self.layers:
417 self.child_meshes = [
418 verify_mesh_obj(child)
419 for child in self.generator.obj.children_recursive
420 if child.type == 'MESH'