Blender V4.3
armature_relations.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10#include "MEM_guardedalloc.h"
11
12#include "DNA_anim_types.h"
13#include "DNA_armature_types.h"
15#include "DNA_object_types.h"
16#include "DNA_scene_types.h"
17
18#include "BLI_blenlib.h"
19#include "BLI_ghash.h"
20#include "BLI_map.hh"
21#include "BLI_math_matrix.h"
22#include "BLI_math_vector.h"
23
24#include "BLT_translation.hh"
25
26#include "BKE_action.hh"
27#include "BKE_anim_data.hh"
28#include "BKE_animsys.h"
29#include "BKE_armature.hh"
30#include "BKE_constraint.h"
31#include "BKE_context.hh"
32#include "BKE_fcurve_driver.h"
33#include "BKE_idprop.hh"
34#include "BKE_layer.hh"
35#include "BKE_main.hh"
36#include "BKE_report.hh"
37
38#include "DEG_depsgraph.hh"
40
41#include "RNA_access.hh"
42#include "RNA_define.hh"
43
44#include "WM_api.hh"
45#include "WM_types.hh"
46
47#include "ED_armature.hh"
48#include "ED_object.hh"
49#include "ED_outliner.hh"
50#include "ED_screen.hh"
51
52#include "UI_interface.hh"
53#include "UI_resources.hh"
54
56
57#include "armature_intern.hh"
58
59using blender::Vector;
60
61/* -------------------------------------------------------------------- */
68 Object *ob,
69 Object *tarArm,
70 Object *srcArm,
71 bPoseChannel *pchan,
72 EditBone *curbone,
73 ListBase *lb)
74{
75 bool changed = false;
76
77 LISTBASE_FOREACH (bConstraint *, con, lb) {
78 ListBase targets = {nullptr, nullptr};
79
80 /* constraint targets */
81 if (BKE_constraint_targets_get(con, &targets)) {
82 LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
83 if (ct->tar == srcArm) {
84 if (ct->subtarget[0] == '\0') {
85 ct->tar = tarArm;
86 changed = true;
87 }
88 else if (STREQ(ct->subtarget, pchan->name)) {
89 ct->tar = tarArm;
90 STRNCPY(ct->subtarget, curbone->name);
91 changed = true;
92 }
93 }
94 }
95
96 BKE_constraint_targets_flush(con, &targets, false);
97 }
98
99 /* action constraint? (pose constraints only) */
100 if (con->type == CONSTRAINT_TYPE_ACTION) {
101 bActionConstraint *data = static_cast<bActionConstraint *>(con->data);
102
103 if (data->act) {
105 &tarArm->id, data->act, "pose.bones[", pchan->name, curbone->name, 0, 0, false);
106
107 DEG_id_tag_update_ex(bmain, &data->act->id, ID_RECALC_SYNC_TO_EVAL);
108 }
109 }
110 }
111
112 if (changed) {
114 }
115}
116
117/* Callback to pass to BKE_animdata_main_cb() for fixing driver ID's to point to the new ID. */
118/* FIXME: For now, we only care about drivers here.
119 * When editing rigs, it's very rare to have animation on the rigs being edited already,
120 * so it should be safe to skip these.
121 */
123 Main *bmain, ID *id, FCurve *fcu, Object *srcArm, Object *tarArm, GHash *names_map)
124{
125 ID *src_id = &srcArm->id;
126 ID *dst_id = &tarArm->id;
127
128 GHashIterator gh_iter;
129 bool changed = false;
130
131 /* Fix paths - If this is the target object, it will have some "dirty" paths */
132 if ((id == src_id) && strstr(fcu->rna_path, "pose.bones[")) {
133 GHASH_ITER (gh_iter, names_map) {
134 const char *old_name = static_cast<const char *>(BLI_ghashIterator_getKey(&gh_iter));
135 const char *new_name = static_cast<const char *>(BLI_ghashIterator_getValue(&gh_iter));
136
137 /* only remap if changed; this still means there will be some
138 * waste if there aren't many drivers/keys */
139 if (!STREQ(old_name, new_name) && strstr(fcu->rna_path, old_name)) {
141 id, fcu->rna_path, "pose.bones", old_name, new_name, 0, 0, false);
142
143 changed = true;
144
145 /* we don't want to apply a second remapping on this driver now,
146 * so stop trying names, but keep fixing drivers
147 */
148 break;
149 }
150 }
151 }
152
153 /* Driver targets */
154 if (fcu->driver) {
155 ChannelDriver *driver = fcu->driver;
156
157 /* Ensure that invalid drivers gets re-evaluated in case they become valid once the join
158 * operation is finished. */
159 fcu->flag &= ~FCURVE_DISABLED;
160 driver->flag &= ~DRIVER_FLAG_INVALID;
161
162 /* Fix driver references to invalid ID's */
163 LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
164 /* only change the used targets, since the others will need fixing manually anyway */
166 /* change the ID's used... */
167 if (dtar->id == src_id) {
168 dtar->id = dst_id;
169
170 changed = true;
171
172 /* also check on the subtarget...
173 * XXX: We duplicate the logic from drivers_path_rename_fix() here, with our own
174 * little twists so that we know that it isn't going to clobber the wrong data
175 */
176 if ((dtar->rna_path && strstr(dtar->rna_path, "pose.bones[")) || (dtar->pchan_name[0])) {
177 GHASH_ITER (gh_iter, names_map) {
178 const char *old_name = static_cast<const char *>(BLI_ghashIterator_getKey(&gh_iter));
179 const char *new_name = static_cast<const char *>(
181
182 /* only remap if changed */
183 if (!STREQ(old_name, new_name)) {
184 if ((dtar->rna_path) && strstr(dtar->rna_path, old_name)) {
185 /* Fix up path */
186 dtar->rna_path = BKE_animsys_fix_rna_path_rename(
187 id, dtar->rna_path, "pose.bones", old_name, new_name, 0, 0, false);
188 break; /* no need to try any more names for bone path */
189 }
190 if (STREQ(dtar->pchan_name, old_name)) {
191 /* Change target bone name */
192 STRNCPY(dtar->pchan_name, new_name);
193 break; /* no need to try any more names for bone subtarget */
194 }
195 }
196 }
197 }
198 }
199 }
201 }
202 }
203
204 if (changed) {
206 }
207}
208
209/* Helper function for armature joining - link fixing */
211 Main *bmain, Object *tarArm, Object *srcArm, bPoseChannel *pchan, EditBone *curbone)
212{
213 Object *ob;
214 bPose *pose;
215
216 /* let's go through all objects in database */
217 for (ob = static_cast<Object *>(bmain->objects.first); ob;
218 ob = static_cast<Object *>(ob->id.next))
219 {
220 /* do some object-type specific things */
221 if (ob->type == OB_ARMATURE) {
222 pose = ob->pose;
223 LISTBASE_FOREACH (bPoseChannel *, pchant, &pose->chanbase) {
225 bmain, ob, tarArm, srcArm, pchan, curbone, &pchant->constraints);
226 }
227 }
228
229 /* fix object-level constraints */
230 if (ob != srcArm) {
232 bmain, ob, tarArm, srcArm, pchan, curbone, &ob->constraints);
233 }
234
235 /* See if an object is parented to this armature */
236 if (ob->parent && (ob->parent == srcArm)) {
237 /* Is object parented to a bone of this src armature? */
238 if (ob->partype == PARBONE) {
239 /* bone name in object */
240 if (STREQ(ob->parsubstr, pchan->name)) {
241 STRNCPY(ob->parsubstr, curbone->name);
242 }
243 }
244
245 /* make tar armature be new parent */
246 ob->parent = tarArm;
247
249 }
250 }
251}
252
254 const bArmature *src_arm,
255 const int src_index,
256 bArmature *dest_arm,
257 blender::Map<std::string, BoneCollection *> &bone_collection_by_name)
258{
259 using namespace blender::animrig;
260 const BoneCollection *bcoll = src_arm->collection_array[src_index];
261
262 /* Check if already remapped. */
263 BoneCollection *mapped = bone_collection_by_name.lookup_default(bcoll->name, nullptr);
264
265 if (mapped) {
266 return mapped;
267 }
268
269 /* Remap the parent collection if necessary. */
270 const int src_parent_index = armature_bonecoll_find_parent_index(src_arm, src_index);
271 int parent_index = -1;
272
273 if (src_parent_index >= 0) {
275 src_arm, src_parent_index, dest_arm, bone_collection_by_name);
276
277 if (mapped_parent) {
278 parent_index = armature_bonecoll_find_index(dest_arm, mapped_parent);
279 }
280 }
281
282 /* Create the new collection instance. */
283 BoneCollection *new_bcoll = ANIM_armature_bonecoll_new(dest_arm, bcoll->name, parent_index);
284
285 /* Copy collection visibility. */
286 new_bcoll->flags = bcoll->flags;
287
288 /* Copy custom properties. */
289 if (bcoll->prop) {
290 new_bcoll->prop = IDP_CopyProperty_ex(bcoll->prop, 0);
291 }
292
293 bone_collection_by_name.add(bcoll->name, new_bcoll);
294 return new_bcoll;
295}
296
298{
299 Main *bmain = CTX_data_main(C);
300 Scene *scene = CTX_data_scene(C);
301 Object *ob_active = CTX_data_active_object(C);
302 bArmature *arm = static_cast<bArmature *>((ob_active) ? ob_active->data : nullptr);
303 bPose *pose, *opose;
304 bPoseChannel *pchan, *pchann;
305 EditBone *curbone;
306 float mat[4][4], oimat[4][4];
307 bool ok = false;
308
309 /* Ensure we're not in edit-mode and that the active object is an armature. */
310 if (!ob_active || ob_active->type != OB_ARMATURE) {
311 return OPERATOR_CANCELLED;
312 }
313 if (!arm || arm->edbo) {
314 return OPERATOR_CANCELLED;
315 }
316
317 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
318 if (ob_iter == ob_active) {
319 ok = true;
320 break;
321 }
322 }
324
325 /* that way the active object is always selected */
326 if (ok == false) {
327 BKE_report(op->reports, RPT_WARNING, "Active object is not a selected armature");
328 return OPERATOR_CANCELLED;
329 }
330
331 /* Inverse transform for all selected armatures in this object,
332 * See #object_join_exec for detailed comment on why the safe version is used. */
333 invert_m4_m4_safe_ortho(oimat, ob_active->object_to_world().ptr());
334
335 /* Index bone collections by name. This is also used later to keep track
336 * of collections added from other armatures. */
337 blender::Map<std::string, BoneCollection *> bone_collection_by_name;
338 for (BoneCollection *bcoll : arm->collections_span()) {
339 bone_collection_by_name.add(bcoll->name, bcoll);
340 }
341
342 /* Used to track how bone collections should be remapped after merging
343 * other armatures. */
345
346 /* Get edit-bones of active armature to add edit-bones to */
348
349 /* Get pose of active object and move it out of pose-mode */
350 pose = ob_active->pose;
351 ob_active->mode &= ~OB_MODE_POSE;
352
353 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
354 if ((ob_iter->type == OB_ARMATURE) && (ob_iter != ob_active)) {
355 bArmature *curarm = static_cast<bArmature *>(ob_iter->data);
356
357 /* we assume that each armature datablock is only used in a single place */
358 BLI_assert(ob_active->data != ob_iter->data);
359
360 /* init callback data for fixing up AnimData links later */
361 GHash *names_map = BLI_ghash_str_new("join_armature_adt_fix");
362
363 /* Make a list of edit-bones in current armature */
364 ED_armature_to_edit(curarm);
365
366 /* Copy new bone collections, and store their remapping info. */
367 for (int i = 0; i < curarm->collection_array_num; i++) {
369 curarm, i, arm, bone_collection_by_name);
370
371 bone_collection_remap.add(curarm->collection_array[i], mapped);
372 }
373
374 /* Get Pose of current armature */
375 opose = ob_iter->pose;
376 ob_iter->mode &= ~OB_MODE_POSE;
377 // BASACT->flag &= ~OB_MODE_POSE;
378
379 /* Find the difference matrix */
380 mul_m4_m4m4(mat, oimat, ob_iter->object_to_world().ptr());
381
382 /* Copy bones and posechannels from the object to the edit armature */
383 for (pchan = static_cast<bPoseChannel *>(opose->chanbase.first); pchan; pchan = pchann) {
384 pchann = pchan->next;
385 curbone = ED_armature_ebone_find_name(curarm->edbo, pchan->name);
386
387 /* Get new name */
388 ED_armature_ebone_unique_name(arm->edbo, curbone->name, nullptr);
389 BLI_ghash_insert(names_map, BLI_strdup(pchan->name), curbone->name);
390
391 /* Transform the bone */
392 {
393 float premat[4][4];
394 float postmat[4][4];
395 float difmat[4][4];
396 float imat[4][4];
397 float temp[3][3];
398
399 /* Get the premat */
400 ED_armature_ebone_to_mat3(curbone, temp);
401
402 unit_m4(premat); /* mul_m4_m3m4 only sets 3x3 part */
403 mul_m4_m3m4(premat, temp, mat);
404
405 mul_m4_v3(mat, curbone->head);
406 mul_m4_v3(mat, curbone->tail);
407
408 /* Get the postmat */
409 ED_armature_ebone_to_mat3(curbone, temp);
410 copy_m4_m3(postmat, temp);
411
412 /* Find the roll */
413 invert_m4_m4(imat, premat);
414 mul_m4_m4m4(difmat, imat, postmat);
415
416 curbone->roll -= atan2f(difmat[2][0], difmat[2][2]);
417 }
418
419 /* Fix Constraints and Other Links to this Bone and Armature */
420 joined_armature_fix_links(bmain, ob_active, ob_iter, pchan, curbone);
421
422 /* Rename pchan */
423 STRNCPY(pchan->name, curbone->name);
424
425 /* Jump Ship! */
426 BLI_remlink(curarm->edbo, curbone);
427 BLI_addtail(arm->edbo, curbone);
428
429 /* Pose channel is moved from one storage to another, its UUID is still unique. */
430 BLI_remlink(&opose->chanbase, pchan);
431 BLI_addtail(&pose->chanbase, pchan);
434
435 /* Remap collections. */
437 bcoll_ref->bcoll = bone_collection_remap.lookup(bcoll_ref->bcoll);
438 }
439 }
440
441 /* Armature ID itself is not freed below, however it has been modified (and is now completely
442 * empty). This needs to be told to the depsgraph, it will also ensure that the global
443 * memfile undo system properly detects the change.
444 *
445 * FIXME: Modifying an existing obdata because we are joining an object using it into another
446 * object is a very questionable behavior, which also does not match with other object types
447 * joining. */
449
450 /* Fix all the drivers (and animation data) */
451 BKE_fcurves_main_cb(bmain, [&](ID *id, FCurve *fcu) {
452 joined_armature_fix_animdata_cb(bmain, id, fcu, ob_iter, ob_active, names_map);
453 });
454 BLI_ghash_free(names_map, MEM_freeN, nullptr);
455
456 /* Only copy over animdata now, after all the remapping has been done,
457 * so that we don't have to worry about ambiguities re which armature
458 * a bone came from!
459 */
460 if (ob_iter->adt) {
461 if (ob_active->adt == nullptr) {
462 /* no animdata, so just use a copy of the whole thing */
463 ob_active->adt = BKE_animdata_copy(bmain, ob_iter->adt, 0);
464 }
465 else {
466 /* merge in data - we'll fix the drivers manually */
468 bmain, &ob_active->id, &ob_iter->id, ADT_MERGECOPY_KEEP_DST, false);
469 }
470 }
471
472 if (curarm->adt) {
473 if (arm->adt == nullptr) {
474 /* no animdata, so just use a copy of the whole thing */
475 arm->adt = BKE_animdata_copy(bmain, curarm->adt, 0);
476 }
477 else {
478 /* merge in data - we'll fix the drivers manually */
479 BKE_animdata_merge_copy(bmain, &arm->id, &curarm->id, ADT_MERGECOPY_KEEP_DST, false);
480 }
481 }
482
483 /* Free the old object data */
484 blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
485 }
486 }
488
489 DEG_relations_tag_update(bmain); /* because we removed object(s) */
490
491 ED_armature_from_edit(bmain, arm);
493
494 /* Make sure to recompute bone collection visibility. */
496
500
501 return OPERATOR_FINISHED;
502}
503
506/* -------------------------------------------------------------------- */
510/* Helper function for armature separating - link fixing */
511static void separated_armature_fix_links(Main *bmain, Object *origArm, Object *newArm)
512{
513 Object *ob;
514 ListBase *opchans, *npchans;
515
516 /* Get reference to list of bones in original and new armatures. */
517 opchans = &origArm->pose->chanbase;
518 npchans = &newArm->pose->chanbase;
519
520 /* let's go through all objects in database */
521 for (ob = static_cast<Object *>(bmain->objects.first); ob;
522 ob = static_cast<Object *>(ob->id.next))
523 {
524 /* do some object-type specific things */
525 if (ob->type == OB_ARMATURE) {
526 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
527 LISTBASE_FOREACH (bConstraint *, con, &pchan->constraints) {
528 ListBase targets = {nullptr, nullptr};
529
530 /* constraint targets */
531 if (BKE_constraint_targets_get(con, &targets)) {
532 LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
533 /* Any targets which point to original armature
534 * are redirected to the new one only if:
535 * - The target isn't origArm/newArm itself.
536 * - The target is one that can be found in newArm/origArm.
537 */
538 if (ct->subtarget[0] != 0) {
539 if (ct->tar == origArm) {
540 if (BLI_findstring(npchans, ct->subtarget, offsetof(bPoseChannel, name))) {
541 ct->tar = newArm;
542 }
543 }
544 else if (ct->tar == newArm) {
545 if (BLI_findstring(opchans, ct->subtarget, offsetof(bPoseChannel, name))) {
546 ct->tar = origArm;
547 }
548 }
549 }
550 }
551
552 BKE_constraint_targets_flush(con, &targets, false);
553 }
554 }
555 }
556 }
557
558 /* fix object-level constraints */
559 if (ob != origArm) {
561 ListBase targets = {nullptr, nullptr};
562
563 /* constraint targets */
564 if (BKE_constraint_targets_get(con, &targets)) {
565 LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
566 /* any targets which point to original armature are redirected to the new one only if:
567 * - the target isn't origArm/newArm itself
568 * - the target is one that can be found in newArm/origArm
569 */
570 if (ct->subtarget[0] != '\0') {
571 if (ct->tar == origArm) {
572 if (BLI_findstring(npchans, ct->subtarget, offsetof(bPoseChannel, name))) {
573 ct->tar = newArm;
574 }
575 }
576 else if (ct->tar == newArm) {
577 if (BLI_findstring(opchans, ct->subtarget, offsetof(bPoseChannel, name))) {
578 ct->tar = origArm;
579 }
580 }
581 }
582 }
583
584 BKE_constraint_targets_flush(con, &targets, false);
585 }
586 }
587 }
588
589 /* See if an object is parented to this armature */
590 if (ob->parent && (ob->parent == origArm)) {
591 /* Is object parented to a bone of this src armature? */
592 if ((ob->partype == PARBONE) && (ob->parsubstr[0] != '\0')) {
593 if (BLI_findstring(npchans, ob->parsubstr, offsetof(bPoseChannel, name))) {
594 ob->parent = newArm;
595 }
596 }
597 }
598 }
599}
600
608static void separate_armature_bones(Main *bmain, Object *ob, const bool is_select)
609{
610 bArmature *arm = (bArmature *)ob->data;
611 bPoseChannel *pchan, *pchann;
612 EditBone *curbone;
613
614 /* make local set of edit-bones to manipulate here */
616
617 /* go through pose-channels, checking if a bone should be removed */
618 for (pchan = static_cast<bPoseChannel *>(ob->pose->chanbase.first); pchan; pchan = pchann) {
619 pchann = pchan->next;
620 curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
621
622 /* check if bone needs to be removed */
623 if (is_select == (EBONE_VISIBLE(arm, curbone) && (curbone->flag & BONE_SELECTED))) {
624
625 /* Clear the bone->parent var of any bone that had this as its parent. */
626 LISTBASE_FOREACH (EditBone *, ebo, arm->edbo) {
627 if (ebo->parent == curbone) {
628 ebo->parent = nullptr;
629 /* this is needed to prevent random crashes with in ED_armature_from_edit */
630 ebo->temp.p = nullptr;
631 ebo->flag &= ~BONE_CONNECTED;
632 }
633 }
634
635 /* clear the pchan->parent var of any pchan that had this as its parent */
636 LISTBASE_FOREACH (bPoseChannel *, pchn, &ob->pose->chanbase) {
637 if (pchn->parent == pchan) {
638 pchn->parent = nullptr;
639 }
640 if (pchn->bbone_next == pchan) {
641 pchn->bbone_next = nullptr;
642 }
643 if (pchn->bbone_prev == pchan) {
644 pchn->bbone_prev = nullptr;
645 }
646 }
647
648 /* Free any of the extra-data this pchan might have. */
651
652 /* get rid of unneeded bone */
653 bone_free(arm, curbone);
654 BLI_freelinkN(&ob->pose->chanbase, pchan);
655 }
656 }
657
658 /* Exit edit-mode (recalculates pose-channels too). */
660 ED_armature_from_edit(bmain, static_cast<bArmature *>(ob->data));
661 ED_armature_edit_free(static_cast<bArmature *>(ob->data));
662}
663
664/* separate selected bones into their armature */
666{
667 Main *bmain = CTX_data_main(C);
668 Scene *scene = CTX_data_scene(C);
669 ViewLayer *view_layer = CTX_data_view_layer(C);
670 bool ok = false;
671
672 /* set wait cursor in case this takes a while */
673 WM_cursor_wait(true);
674
676 scene, view_layer, CTX_wm_view3d(C));
677
678 for (Base *base_old : bases) {
679 Object *ob_old = base_old->object;
680
681 {
682 bArmature *arm_old = static_cast<bArmature *>(ob_old->data);
683 bool has_selected_bone = false;
684 bool has_selected_any = false;
685 LISTBASE_FOREACH (EditBone *, ebone, arm_old->edbo) {
686 if (EBONE_VISIBLE(arm_old, ebone)) {
687 if (ebone->flag & BONE_SELECTED) {
688 has_selected_bone = true;
689 break;
690 }
691 if (ebone->flag & (BONE_TIPSEL | BONE_ROOTSEL)) {
692 has_selected_any = true;
693 }
694 }
695 }
696 if (has_selected_bone == false) {
697 if (has_selected_any) {
698 /* Without this, we may leave head/tail selected
699 * which isn't expected after separating. */
701 }
702 continue;
703 }
704 }
705
706 /* We are going to do this as follows (unlike every other instance of separate):
707 * 1. Exit edit-mode & pose-mode for active armature/base. Take note of what this is.
708 * 2. Duplicate base - BASACT is the new one now
709 * 3. For each of the two armatures,
710 * enter edit-mode -> remove appropriate bones -> exit edit-mode + recalculate.
711 * 4. Fix constraint links
712 * 5. Make original armature active and enter edit-mode
713 */
714
715 /* 1) store starting settings and exit edit-mode */
716 ob_old->mode &= ~OB_MODE_POSE;
717
718 ED_armature_from_edit(bmain, static_cast<bArmature *>(ob_old->data));
719 ED_armature_edit_free(static_cast<bArmature *>(ob_old->data));
720
721 /* 2) duplicate base */
722
723 /* Only duplicate linked armature but take into account
724 * user preferences for duplicating actions. */
725 short dupflag = USER_DUP_ARM | (U.dupflag & USER_DUP_ACT);
727 bmain, scene, view_layer, base_old, eDupli_ID_Flags(dupflag));
728 Object *ob_new = base_new->object;
729
731
732 /* 3) remove bones that shouldn't still be around on both armatures */
733 separate_armature_bones(bmain, ob_old, true);
734 separate_armature_bones(bmain, ob_new, false);
735
736 /* 4) fix links before depsgraph flushes, err... or after? */
737 separated_armature_fix_links(bmain, ob_old, ob_new);
738
739 DEG_id_tag_update(&ob_old->id, ID_RECALC_GEOMETRY); /* this is the original one */
740 DEG_id_tag_update(&ob_new->id, ID_RECALC_GEOMETRY); /* this is the separated one */
741
742 /* 5) restore original conditions */
743 ED_armature_to_edit(static_cast<bArmature *>(ob_old->data));
744
745 /* parents tips remain selected when connected children are removed. */
747
748 ok = true;
749
750 /* NOTE: notifier might evolve. */
752 }
753
754 /* Recalculate/redraw + cleanup */
755 WM_cursor_wait(false);
756
757 if (ok) {
758 BKE_report(op->reports, RPT_INFO, "Separated bones");
760 }
761
762 return OPERATOR_FINISHED;
763}
764
766{
767 /* identifiers */
768 ot->name = "Separate Bones";
769 ot->idname = "ARMATURE_OT_separate";
770 ot->description = "Isolate selected bones into a separate armature";
771
772 /* callbacks */
775
776 /* flags */
778}
779
782/* -------------------------------------------------------------------- */
786/* armature parenting options */
787#define ARM_PAR_CONNECT 1
788#define ARM_PAR_OFFSET 2
789
790/* armature un-parenting options */
791#define ARM_PAR_CLEAR 1
792#define ARM_PAR_CLEAR_DISCONNECT 2
793
794/* check for null, before calling! */
796{
797 bone->flag |= BONE_CONNECTED;
798 copy_v3_v3(bone->head, bone->parent->tail);
799 bone->rad_head = bone->parent->rad_tail;
800}
801
803 EditBone *selbone,
804 EditBone *actbone,
805 short mode)
806{
807 EditBone *ebone;
808 float offset[3];
809
810 if ((selbone->parent) && (selbone->flag & BONE_CONNECTED)) {
811 selbone->parent->flag &= ~BONE_TIPSEL;
812 }
813
814 /* make actbone the parent of selbone */
815 selbone->parent = actbone;
816
817 /* in actbone tree we cannot have a loop */
818 for (ebone = actbone->parent; ebone; ebone = ebone->parent) {
819 if (ebone->parent == selbone) {
820 ebone->parent = nullptr;
821 ebone->flag &= ~BONE_CONNECTED;
822 }
823 }
824
825 if (mode == ARM_PAR_CONNECT) {
826 /* Connected: Child bones will be moved to the parent tip */
827 selbone->flag |= BONE_CONNECTED;
828 sub_v3_v3v3(offset, actbone->tail, selbone->head);
829
830 copy_v3_v3(selbone->head, actbone->tail);
831 selbone->rad_head = actbone->rad_tail;
832
833 add_v3_v3(selbone->tail, offset);
834
835 /* offset for all its children */
836 LISTBASE_FOREACH (EditBone *, ebone, edbo) {
837 EditBone *par;
838
839 for (par = ebone->parent; par; par = par->parent) {
840 if (par == selbone) {
841 add_v3_v3(ebone->head, offset);
842 add_v3_v3(ebone->tail, offset);
843 break;
844 }
845 }
846 }
847 }
848 else {
849 /* Offset: Child bones will retain their distance from the parent tip */
850 selbone->flag &= ~BONE_CONNECTED;
851 }
852}
853
855 {ARM_PAR_CONNECT, "CONNECTED", 0, "Connected", ""},
856 {ARM_PAR_OFFSET, "OFFSET", 0, "Keep Offset", ""},
857 {0, nullptr, 0, nullptr, nullptr},
858};
859
861{
863 bArmature *arm = (bArmature *)ob->data;
864 EditBone *actbone = CTX_data_active_bone(C);
865 EditBone *actmirb = nullptr;
866 short val = RNA_enum_get(op->ptr, "type");
867
868 /* there must be an active bone */
869 if (actbone == nullptr) {
870 BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone");
871 return OPERATOR_CANCELLED;
872 }
873 if (arm->flag & ARM_MIRROR_EDIT) {
874 /* For X-Axis Mirror Editing option, we may need a mirror copy of actbone:
875 * - If there's a mirrored copy of selbone, try to find a mirrored copy of actbone
876 * (i.e. selbone="child.L" and actbone="parent.L", find "child.R" and "parent.R").
877 * This is useful for arm-chains, for example parenting lower arm to upper arm.
878 * - If there's no mirrored copy of actbone (i.e. actbone = "parent.C" or "parent")
879 * then just use actbone. Useful when doing upper arm to spine.
880 */
881 actmirb = ED_armature_ebone_get_mirrored(arm->edbo, actbone);
882 if (actmirb == nullptr) {
883 actmirb = actbone;
884 }
885 }
886
887 /* If there is only 1 selected bone, we assume that it is the active bone,
888 * since a user will need to have clicked on a bone (thus selecting it) to make it active. */
889 bool is_active_only_selected = false;
890 if (actbone->flag & BONE_SELECTED) {
891 is_active_only_selected = true;
892 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
893 if (EBONE_EDITABLE(ebone) && (ebone->flag & BONE_SELECTED)) {
894 if (ebone != actbone) {
895 is_active_only_selected = false;
896 break;
897 }
898 }
899 }
900 }
901
902 if (is_active_only_selected) {
903 /* When only the active bone is selected, and it has a parent,
904 * connect it to the parent, as that is the only possible outcome.
905 */
906 if (actbone->parent) {
908
909 if ((arm->flag & ARM_MIRROR_EDIT) && (actmirb->parent)) {
911 }
912 }
913 }
914 else {
915 /* Parent 'selected' bones to the active one:
916 * - The context iterator contains both selected bones and their mirrored copies,
917 * so we assume that unselected bones are mirrored copies of some selected bone.
918 * - Since the active one (and/or its mirror) will also be selected, we also need
919 * to check that we are not trying to operate on them, since such an operation
920 * would cause errors.
921 */
922
923 /* Parent selected bones to the active one. */
924 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
925 if (EBONE_EDITABLE(ebone) && (ebone->flag & BONE_SELECTED)) {
926 if (ebone != actbone) {
927 bone_connect_to_new_parent(arm->edbo, ebone, actbone, val);
928 }
929
930 if (arm->flag & ARM_MIRROR_EDIT) {
931 EditBone *ebone_mirror = ED_armature_ebone_get_mirrored(arm->edbo, ebone);
932 if (ebone_mirror && (ebone_mirror->flag & BONE_SELECTED) == 0) {
933 if (ebone_mirror != actmirb) {
934 bone_connect_to_new_parent(arm->edbo, ebone_mirror, actmirb, val);
935 }
936 }
937 }
938 }
939 }
940 }
941
942 /* NOTE: notifier might evolve. */
945
946 return OPERATOR_FINISHED;
947}
948
949static int armature_parent_set_invoke(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/)
950{
951 /* False when all selected bones are parented to the active bone. */
952 bool enable_offset = false;
953 /* False when all selected bones are connected to the active bone. */
954 bool enable_connect = false;
955 {
957 bArmature *arm = static_cast<bArmature *>(ob->data);
958 EditBone *actbone = arm->act_edbone;
959 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
960 if (!EBONE_EDITABLE(ebone) || !(ebone->flag & BONE_SELECTED)) {
961 continue;
962 }
963 if (ebone == actbone) {
964 continue;
965 }
966
967 if (ebone->parent != actbone) {
968 enable_offset = true;
969 enable_connect = true;
970 break;
971 }
972 if (!(ebone->flag & BONE_CONNECTED)) {
973 enable_connect = true;
974 }
975 }
976 }
977
979 C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Make Parent"), ICON_NONE);
980 uiLayout *layout = UI_popup_menu_layout(pup);
981
982 uiLayout *row_offset = uiLayoutRow(layout, false);
983 uiLayoutSetEnabled(row_offset, enable_offset);
984 uiItemEnumO(row_offset, "ARMATURE_OT_parent_set", nullptr, ICON_NONE, "type", ARM_PAR_OFFSET);
985
986 uiLayout *row_connect = uiLayoutRow(layout, false);
987 uiLayoutSetEnabled(row_connect, enable_connect);
988 uiItemEnumO(row_connect, "ARMATURE_OT_parent_set", nullptr, ICON_NONE, "type", ARM_PAR_CONNECT);
989
990 UI_popup_menu_end(C, pup);
991
992 return OPERATOR_INTERFACE;
993}
994
996{
997 /* identifiers */
998 ot->name = "Make Parent";
999 ot->idname = "ARMATURE_OT_parent_set";
1000 ot->description = "Set the active bone as the parent of the selected bones";
1001
1002 /* api callbacks */
1006
1007 /* flags */
1009
1011 ot->srna, "type", prop_editarm_make_parent_types, 0, "Parent Type", "Type of parenting");
1012}
1013
1015 {ARM_PAR_CLEAR, "CLEAR", 0, "Clear Parent", ""},
1016 {ARM_PAR_CLEAR_DISCONNECT, "DISCONNECT", 0, "Disconnect Bone", ""},
1017 {0, nullptr, 0, nullptr, nullptr},
1018};
1019
1020static void editbone_clear_parent(EditBone *ebone, int mode)
1021{
1022 if (ebone->parent) {
1023 /* for nice selection */
1024 ebone->parent->flag &= ~BONE_TIPSEL;
1025 }
1026
1027 if (mode == 1) {
1028 ebone->parent = nullptr;
1029 }
1030 ebone->flag &= ~BONE_CONNECTED;
1031}
1032
1034{
1035 const Scene *scene = CTX_data_scene(C);
1036 ViewLayer *view_layer = CTX_data_view_layer(C);
1037 const int val = RNA_enum_get(op->ptr, "type");
1038
1039 CTX_DATA_BEGIN (C, EditBone *, ebone, selected_editable_bones) {
1040 editbone_clear_parent(ebone, val);
1041 }
1043
1045 scene, view_layer, CTX_wm_view3d(C));
1046 for (Object *ob : objects) {
1047 bArmature *arm = static_cast<bArmature *>(ob->data);
1048 bool changed = false;
1049
1050 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
1051 if (EBONE_EDITABLE(ebone)) {
1052 changed = true;
1053 break;
1054 }
1055 }
1056
1057 if (!changed) {
1058 continue;
1059 }
1060
1062
1063 /* NOTE: notifier might evolve. */
1065 }
1066 return OPERATOR_FINISHED;
1067}
1068
1070 wmOperator * /*op*/,
1071 const wmEvent * /*event*/)
1072{
1073 /* False when no selected bones are connected to the active bone. */
1074 bool enable_disconnect = false;
1075 /* False when no selected bones are parented to the active bone. */
1076 bool enable_clear = false;
1077 {
1079 bArmature *arm = static_cast<bArmature *>(ob->data);
1080 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
1081 if (!EBONE_EDITABLE(ebone) || !(ebone->flag & BONE_SELECTED)) {
1082 continue;
1083 }
1084 if (ebone->parent == nullptr) {
1085 continue;
1086 }
1087 enable_clear = true;
1088
1089 if (ebone->flag & BONE_CONNECTED) {
1090 enable_disconnect = true;
1091 break;
1092 }
1093 }
1094 }
1095
1097 C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Parent"), ICON_NONE);
1098 uiLayout *layout = UI_popup_menu_layout(pup);
1099
1100 uiLayout *row_clear = uiLayoutRow(layout, false);
1101 uiLayoutSetEnabled(row_clear, enable_clear);
1102 uiItemEnumO(row_clear, "ARMATURE_OT_parent_clear", nullptr, ICON_NONE, "type", ARM_PAR_CLEAR);
1103
1104 uiLayout *row_disconnect = uiLayoutRow(layout, false);
1105 uiLayoutSetEnabled(row_disconnect, enable_disconnect);
1106 uiItemEnumO(row_disconnect,
1107 "ARMATURE_OT_parent_clear",
1108 nullptr,
1109 ICON_NONE,
1110 "type",
1112
1113 UI_popup_menu_end(C, pup);
1114
1115 return OPERATOR_INTERFACE;
1116}
1117
1119{
1120 /* identifiers */
1121 ot->name = "Clear Parent";
1122 ot->idname = "ARMATURE_OT_parent_clear";
1123 ot->description =
1124 "Remove the parent-child relationship between selected bones and their parents";
1125
1126 /* api callbacks */
1130
1131 /* flags */
1133
1135 "type",
1137 0,
1138 "Clear Type",
1139 "What way to clear parenting");
1140}
1141
C++ functions to deal with Armature collections (i.e. the successor of bone layers).
void ANIM_armature_runtime_refresh(bArmature *armature)
BoneCollection * ANIM_armature_bonecoll_new(bArmature *armature, const char *name, int parent_index=-1)
Blender kernel action and pose functionality.
void BKE_pose_channels_hash_free(bPose *pose) ATTR_NONNULL(1)
void BKE_pose_channel_free(bPoseChannel *pchan) ATTR_NONNULL(1)
void BKE_animdata_merge_copy(Main *bmain, ID *dst_id, ID *src_id, eAnimData_MergeCopy_Modes action_mode, bool fix_drivers)
Definition anim_data.cc:530
AnimData * BKE_animdata_copy(Main *bmain, AnimData *adt, int flag)
Definition anim_data.cc:447
@ ADT_MERGECOPY_KEEP_DST
void BKE_fcurves_main_cb(struct Main *bmain, blender::FunctionRef< void(ID *, FCurve *)> func)
void BKE_action_fix_paths_rename(struct ID *owner_id, struct bAction *act, const char *prefix, const char *oldName, const char *newName, int oldSubscript, int newSubscript, bool verify_paths)
char * BKE_animsys_fix_rna_path_rename(struct ID *owner_id, char *old_path, const char *prefix, const char *oldName, const char *newName, int oldSubscript, int newSubscript, bool verify_paths)
Definition anim_data.cc:981
void BKE_constraint_targets_flush(struct bConstraint *con, struct ListBase *targets, bool no_copy)
int BKE_constraint_targets_get(struct bConstraint *con, struct ListBase *r_targets)
#define CTX_DATA_BEGIN(C, Type, instance, member)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Object * CTX_data_edit_object(const bContext *C)
Main * CTX_data_main(const bContext *C)
EditBone * CTX_data_active_bone(const bContext *C)
#define CTX_DATA_END
View3D * CTX_wm_view3d(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
#define DRIVER_TARGETS_USED_LOOPER_BEGIN(dvar)
#define DRIVER_TARGETS_LOOPER_END
IDProperty * IDP_CopyProperty_ex(const IDProperty *prop, int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition idprop.cc:843
blender::Vector< Base * > BKE_view_layer_array_from_bases_in_edit_mode_unique_data(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
blender::Vector< Object * > BKE_view_layer_array_from_objects_in_edit_mode_unique_data(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert(a)
Definition BLI_assert.h:50
BLI_INLINE void * BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:299
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:303
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:322
GHash * BLI_ghash_str_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition BLI_ghash.c:707
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.c:860
void * BLI_findstring(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define LISTBASE_FOREACH(type, var, list)
void BLI_freelinkN(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:269
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
void mul_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4])
void unit_m4(float m[4][4])
Definition rct.c:1127
void copy_m4_m3(float m1[4][4], const float m2[3][3])
void mul_m4_v3(const float M[4][4], float r[3])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
void invert_m4_m4_safe_ortho(float inverse[4][4], const float mat[4][4])
void mul_m4_m3m4(float R[4][4], const float A[3][3], const float B[4][4])
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v3_v3(float r[3], const float a[3])
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.c:40
#define STRNCPY(dst, src)
Definition BLI_string.h:593
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_id_tag_update_ex(Main *bmain, ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
@ ID_RECALC_SELECT
Definition DNA_ID.h:1068
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1085
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ BONE_ROOTSEL
@ BONE_SELECTED
@ BONE_TIPSEL
@ BONE_CONNECTED
@ ARM_MIRROR_EDIT
@ CONSTRAINT_TYPE_ACTION
Object is a sort of wrapper for general info.
@ PARBONE
@ OB_ARMATURE
eDupli_ID_Flags
@ USER_DUP_ARM
@ USER_DUP_ACT
#define EBONE_VISIBLE(arm, ebone)
#define EBONE_EDITABLE(ebone)
void ED_outliner_select_sync_from_object_tag(bContext *C)
bool ED_operator_editarmature(bContext *C)
Read Guarded memory(de)allocation.
void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
void uiItemEnumO(uiLayout *layout, const char *opname, const char *name, int icon, const char *propname, int value)
uiPopupMenu * UI_popup_menu_begin(bContext *C, const char *title, int icon) ATTR_NONNULL()
uiLayout * UI_popup_menu_layout(uiPopupMenu *pup)
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define ND_OB_ACTIVE
Definition WM_types.hh:407
#define NC_SCENE
Definition WM_types.hh:345
#define ND_LAYER_CONTENT
Definition WM_types.hh:420
#define ND_POSE
Definition WM_types.hh:425
#define ND_BONE_SELECT
Definition WM_types.hh:427
#define NC_OBJECT
Definition WM_types.hh:346
void bone_free(bArmature *arm, EditBone *bone)
void ED_armature_ebone_unique_name(ListBase *ebones, char *name, EditBone *bone)
static void separate_armature_bones(Main *bmain, Object *ob, const bool is_select)
static void joined_armature_fix_links(Main *bmain, Object *tarArm, Object *srcArm, bPoseChannel *pchan, EditBone *curbone)
static const EnumPropertyItem prop_editarm_clear_parent_types[]
void ARMATURE_OT_parent_clear(wmOperatorType *ot)
static void joined_armature_fix_animdata_cb(Main *bmain, ID *id, FCurve *fcu, Object *srcArm, Object *tarArm, GHash *names_map)
static int armature_parent_set_invoke(bContext *C, wmOperator *, const wmEvent *)
static BoneCollection * join_armature_remap_collection(const bArmature *src_arm, const int src_index, bArmature *dest_arm, blender::Map< std::string, BoneCollection * > &bone_collection_by_name)
#define ARM_PAR_CLEAR_DISCONNECT
static int armature_parent_set_exec(bContext *C, wmOperator *op)
static void joined_armature_fix_links_constraints(Main *bmain, Object *ob, Object *tarArm, Object *srcArm, bPoseChannel *pchan, EditBone *curbone, ListBase *lb)
static int separate_armature_exec(bContext *C, wmOperator *op)
static int armature_parent_clear_exec(bContext *C, wmOperator *op)
int ED_armature_join_objects_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_parent_set(wmOperatorType *ot)
static void separated_armature_fix_links(Main *bmain, Object *origArm, Object *newArm)
static void bone_connect_to_new_parent(ListBase *edbo, EditBone *selbone, EditBone *actbone, short mode)
#define ARM_PAR_OFFSET
void ARMATURE_OT_separate(wmOperatorType *ot)
static void editbone_clear_parent(EditBone *ebone, int mode)
#define ARM_PAR_CONNECT
#define ARM_PAR_CLEAR
static const EnumPropertyItem prop_editarm_make_parent_types[]
static void bone_connect_to_existing_parent(EditBone *bone)
static int armature_parent_clear_invoke(bContext *C, wmOperator *, const wmEvent *)
bool ED_armature_edit_deselect_all(Object *obedit)
EditBone * ED_armature_ebone_find_name(const ListBase *edbo, const char *name)
void ED_armature_edit_sync_selection(ListBase *edbo)
void ED_armature_edit_free(bArmature *arm)
void ED_armature_ebone_to_mat3(EditBone *ebone, float r_mat[3][3])
void ED_armature_from_edit(Main *bmain, bArmature *arm)
void ED_armature_to_edit(bArmature *arm)
EditBone * ED_armature_ebone_get_mirrored(const ListBase *edbo, EditBone *ebo)
unsigned int U
Definition btGjkEpa3.h:78
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
const Value & lookup(const Key &key) const
Definition BLI_map.hh:506
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:531
#define atan2f(x, y)
#define offsetof(t, d)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
Base * add_duplicate(Main *bmain, Scene *scene, ViewLayer *view_layer, Base *base, eDupli_ID_Flags dupflag)
void base_free_and_unlink(Main *bmain, Scene *scene, Object *ob)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
struct Object * object
struct IDProperty * prop
char name[64]
float tail[3]
EditBone * parent
ListBase bone_collections
float rad_tail
float rad_head
float head[3]
char * rna_path
ChannelDriver * driver
Definition DNA_ID.h:413
void * next
Definition DNA_ID.h:416
void * first
ListBase objects
Definition BKE_main.hh:212
ListBase constraints
struct bPose * pose
struct AnimData * adt
struct Object * parent
char parsubstr[64]
struct AnimData * adt
struct BoneCollection ** collection_array
struct EditBone * act_edbone
ListBase * edbo
struct bPoseChannel * next
ListBase chanbase
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
PropertyRNA * prop
Definition WM_types.hh:1092
StructRNA * srna
Definition WM_types.hh:1080
struct ReportList * reports
struct PointerRNA * ptr
void WM_cursor_wait(bool val)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4125