1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2018, Blender Foundation
17  * This is a new part of Blender
18  */
19 
20 /** \file
21  * \ingroup edgpencil
22  *
23  * Operators for dealing with armatures and GP data-blocks.
24  */
25 
26 #include <math.h>
27 #include <stddef.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "MEM_guardedalloc.h"
33 
34 #include "BLI_blenlib.h"
35 #include "BLI_math.h"
36 #include "BLI_utildefines.h"
37 
38 #include "BLT_translation.h"
39 
40 #include "DNA_armature_types.h"
41 #include "DNA_gpencil_types.h"
42 #include "DNA_meshdata_types.h"
43 #include "DNA_scene_types.h"
44 
45 #include "BKE_action.h"
46 #include "BKE_armature.h"
47 #include "BKE_context.h"
48 #include "BKE_deform.h"
49 #include "BKE_gpencil.h"
50 #include "BKE_gpencil_modifier.h"
51 #include "BKE_main.h"
52 #include "BKE_object_deform.h"
53 #include "BKE_report.h"
54 
55 #include "WM_api.h"
56 #include "WM_types.h"
57 
58 #include "RNA_access.h"
59 #include "RNA_define.h"
60 #include "RNA_enum_types.h"
61 
62 #include "ED_gpencil.h"
63 #include "ED_mesh.h"
64 #include "ED_object.h"
65 
66 #include "DEG_depsgraph.h"
67 #include "DEG_depsgraph_query.h"
68 
69 #include "gpencil_intern.h"
70 
71 enum {
72   GP_ARMATURE_NAME = 0,
73   GP_ARMATURE_AUTO = 1,
74 };
75 
76 #define DEFAULT_RATIO 0.10f
77 #define DEFAULT_DECAY 0.8f
78 
gpencil_bone_looper(Object * ob,Bone * bone,void * data,int (* bone_func)(Object *,Bone *,void *))79 static int gpencil_bone_looper(Object *ob,
80                                Bone *bone,
81                                void *data,
82                                int (*bone_func)(Object *, Bone *, void *))
83 {
84   /* We want to apply the function bone_func to every bone
85    * in an armature -- feed bone_looper the first bone and
86    * a pointer to the bone_func and watch it go!. The int count
87    * can be useful for counting bones with a certain property
88    * (e.g. skinnable)
89    */
90   int count = 0;
91 
92   if (bone) {
93     /* only do bone_func if the bone is non null */
94     count += bone_func(ob, bone, data);
95 
96     /* try to execute bone_func for the first child */
97     count += gpencil_bone_looper(ob, bone->childbase.first, data, bone_func);
98 
99     /* try to execute bone_func for the next bone at this
100      * depth of the recursion.
101      */
102     count += gpencil_bone_looper(ob, bone->next, data, bone_func);
103   }
104 
105   return count;
106 }
107 
gpencil_bone_skinnable_cb(Object * UNUSED (ob),Bone * bone,void * datap)108 static int gpencil_bone_skinnable_cb(Object *UNUSED(ob), Bone *bone, void *datap)
109 {
110   /* Bones that are deforming
111    * are regarded to be "skinnable" and are eligible for
112    * auto-skinning.
113    *
114    * This function performs 2 functions:
115    *
116    *   a) It returns 1 if the bone is skinnable.
117    *      If we loop over all bones with this
118    *      function, we can count the number of
119    *      skinnable bones.
120    *   b) If the pointer data is non null,
121    *      it is treated like a handle to a
122    *      bone pointer -- the bone pointer
123    *      is set to point at this bone, and
124    *      the pointer the handle points to
125    *      is incremented to point to the
126    *      next member of an array of pointers
127    *      to bones. This way we can loop using
128    *      this function to construct an array of
129    *      pointers to bones that point to all
130    *      skinnable bones.
131    */
132   Bone ***hbone;
133   int a, segments;
134   struct {
135     Object *armob;
136     void *list;
137     int heat;
138   } *data = datap;
139 
140   if (!(bone->flag & BONE_HIDDEN_P)) {
141     if (!(bone->flag & BONE_NO_DEFORM)) {
142       if (data->heat && data->armob->pose &&
143           BKE_pose_channel_find_name(data->armob->pose, bone->name)) {
144         segments = bone->segments;
145       }
146       else {
147         segments = 1;
148       }
149 
150       if (data->list != NULL) {
151         hbone = (Bone ***)&data->list;
152 
153         for (a = 0; a < segments; a++) {
154           **hbone = bone;
155           (*hbone)++;
156         }
157       }
158       return segments;
159     }
160   }
161   return 0;
162 }
163 
vgroup_add_unique_bone_cb(Object * ob,Bone * bone,void * UNUSED (ptr))164 static int vgroup_add_unique_bone_cb(Object *ob, Bone *bone, void *UNUSED(ptr))
165 {
166   /* This group creates a vertex group to ob that has the
167    * same name as bone (provided the bone is skinnable).
168    * If such a vertex group already exist the routine exits.
169    */
170   if (!(bone->flag & BONE_NO_DEFORM)) {
171     if (!BKE_object_defgroup_find_name(ob, bone->name)) {
172       BKE_object_defgroup_add_name(ob, bone->name);
173       return 1;
174     }
175   }
176   return 0;
177 }
178 
dgroup_skinnable_cb(Object * ob,Bone * bone,void * datap)179 static int dgroup_skinnable_cb(Object *ob, Bone *bone, void *datap)
180 {
181   /* Bones that are deforming
182    * are regarded to be "skinnable" and are eligible for
183    * auto-skinning.
184    *
185    * This function performs 2 functions:
186    *
187    *   a) If the bone is skinnable, it creates
188    *      a vertex group for ob that has
189    *      the name of the skinnable bone
190    *      (if one doesn't exist already).
191    *   b) If the pointer data is non null,
192    *      it is treated like a handle to a
193    *      bDeformGroup pointer -- the
194    *      bDeformGroup pointer is set to point
195    *      to the deform group with the bone's
196    *      name, and the pointer the handle
197    *      points to is incremented to point to the
198    *      next member of an array of pointers
199    *      to bDeformGroups. This way we can loop using
200    *      this function to construct an array of
201    *      pointers to bDeformGroups, all with names
202    *      of skinnable bones.
203    */
204   bDeformGroup ***hgroup, *defgroup = NULL;
205   int a, segments;
206   struct {
207     Object *armob;
208     void *list;
209     int heat;
210   } *data = datap;
211   bArmature *arm = data->armob->data;
212 
213   if (!(bone->flag & BONE_HIDDEN_P)) {
214     if (!(bone->flag & BONE_NO_DEFORM)) {
215       if (data->heat && data->armob->pose &&
216           BKE_pose_channel_find_name(data->armob->pose, bone->name)) {
217         segments = bone->segments;
218       }
219       else {
220         segments = 1;
221       }
222 
223       if (arm->layer & bone->layer) {
224         if (!(defgroup = BKE_object_defgroup_find_name(ob, bone->name))) {
225           defgroup = BKE_object_defgroup_add_name(ob, bone->name);
226         }
227         else if (defgroup->flag & DG_LOCK_WEIGHT) {
228           /* In case vgroup already exists and is locked, do not modify it here. See T43814. */
229           defgroup = NULL;
230         }
231       }
232 
233       if (data->list != NULL) {
234         hgroup = (bDeformGroup ***)&data->list;
235 
236         for (a = 0; a < segments; a++) {
237           **hgroup = defgroup;
238           (*hgroup)++;
239         }
240       }
241       return segments;
242     }
243   }
244   return 0;
245 }
246 
247 /* get weight value depending of distance and decay value */
get_weight(float dist,float decay_rad,float dif_rad)248 static float get_weight(float dist, float decay_rad, float dif_rad)
249 {
250   float weight = 1.0f;
251   if (dist < decay_rad) {
252     weight = 1.0f;
253   }
254   else {
255     weight = interpf(0.0f, 0.9f, (dist - decay_rad) / dif_rad);
256   }
257 
258   return weight;
259 }
260 
261 /* This functions implements the automatic computation of vertex group weights */
gpencil_add_verts_to_dgroups(const bContext * C,Object * ob,Object * ob_arm,const float ratio,const float decay)262 static void gpencil_add_verts_to_dgroups(
263     const bContext *C, Object *ob, Object *ob_arm, const float ratio, const float decay)
264 {
265   bArmature *arm = ob_arm->data;
266   Bone **bonelist, *bone;
267   bDeformGroup **dgrouplist;
268   bPoseChannel *pchan;
269   bGPdata *gpd = (bGPdata *)ob->data;
270   const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
271 
272   Mat4 bbone_array[MAX_BBONE_SUBDIV], *bbone = NULL;
273   float(*root)[3], (*tip)[3], (*verts)[3];
274   float *radsqr;
275   int *selected;
276   float weight;
277   int numbones, i, j, segments = 0;
278   struct {
279     Object *armob;
280     void *list;
281     int heat;
282   } looper_data;
283 
284   looper_data.armob = ob_arm;
285   looper_data.heat = true;
286   looper_data.list = NULL;
287 
288   /* count the number of skinnable bones */
289   numbones = gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, gpencil_bone_skinnable_cb);
290 
291   if (numbones == 0) {
292     return;
293   }
294 
295   /* create an array of pointer to bones that are skinnable
296    * and fill it with all of the skinnable bones */
297   bonelist = MEM_callocN(numbones * sizeof(Bone *), "bonelist");
298   looper_data.list = bonelist;
299   gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, gpencil_bone_skinnable_cb);
300 
301   /* create an array of pointers to the deform groups that
302    * correspond to the skinnable bones (creating them
303    * as necessary. */
304   dgrouplist = MEM_callocN(numbones * sizeof(bDeformGroup *), "dgrouplist");
305 
306   looper_data.list = dgrouplist;
307   gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, dgroup_skinnable_cb);
308 
309   /* create an array of root and tip positions transformed into
310    * global coords */
311   root = MEM_callocN(sizeof(float[3]) * numbones, "root");
312   tip = MEM_callocN(sizeof(float[3]) * numbones, "tip");
313   selected = MEM_callocN(sizeof(int) * numbones, "selected");
314   radsqr = MEM_callocN(sizeof(float) * numbones, "radsqr");
315 
316   for (j = 0; j < numbones; j++) {
317     bone = bonelist[j];
318 
319     /* handle bbone */
320     if (segments == 0) {
321       segments = 1;
322       bbone = NULL;
323 
324       if ((ob_arm->pose) && (pchan = BKE_pose_channel_find_name(ob_arm->pose, bone->name))) {
325         if (bone->segments > 1) {
326           segments = bone->segments;
327           BKE_pchan_bbone_spline_setup(pchan, true, false, bbone_array);
328           bbone = bbone_array;
329         }
330       }
331     }
332 
333     segments--;
334 
335     /* compute root and tip */
336     if (bbone) {
337       mul_v3_m4v3(root[j], bone->arm_mat, bbone[segments].mat[3]);
338       if ((segments + 1) < bone->segments) {
339         mul_v3_m4v3(tip[j], bone->arm_mat, bbone[segments + 1].mat[3]);
340       }
341       else {
342         copy_v3_v3(tip[j], bone->arm_tail);
343       }
344     }
345     else {
346       copy_v3_v3(root[j], bone->arm_head);
347       copy_v3_v3(tip[j], bone->arm_tail);
348     }
349 
350     mul_m4_v3(ob_arm->obmat, root[j]);
351     mul_m4_v3(ob_arm->obmat, tip[j]);
352 
353     selected[j] = 1;
354 
355     /* calculate radius squared */
356     radsqr[j] = len_squared_v3v3(root[j], tip[j]) * ratio;
357   }
358 
359   /* loop all strokes */
360   LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
361     bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
362     bGPDspoint *pt = NULL;
363 
364     for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
365       if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
366 
367         if (gpf == NULL) {
368           continue;
369         }
370 
371         LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
372           /* skip strokes that are invalid for current view */
373           if (ED_gpencil_stroke_can_use(C, gps) == false) {
374             continue;
375           }
376 
377           BKE_gpencil_dvert_ensure(gps);
378 
379           /* create verts array */
380           verts = MEM_callocN(gps->totpoints * sizeof(*verts), __func__);
381 
382           /* transform stroke points to global space */
383           for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
384             copy_v3_v3(verts[i], &pt->x);
385             mul_m4_v3(ob->obmat, verts[i]);
386           }
387 
388           /* loop groups and assign weight */
389           for (j = 0; j < numbones; j++) {
390             int def_nr = BLI_findindex(&ob->defbase, dgrouplist[j]);
391             if (def_nr < 0) {
392               continue;
393             }
394 
395             float decay_rad = radsqr[j] - (radsqr[j] * decay);
396             float dif_rad = radsqr[j] - decay_rad;
397 
398             for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
399               MDeformVert *dvert = &gps->dvert[i];
400               float dist = dist_squared_to_line_segment_v3(verts[i], root[j], tip[j]);
401               if (dist > radsqr[j]) {
402                 /* if not in cylinder, check if inside extreme spheres */
403                 weight = 0.0f;
404                 dist = len_squared_v3v3(root[j], verts[i]);
405                 if (dist < radsqr[j]) {
406                   weight = get_weight(dist, decay_rad, dif_rad);
407                 }
408                 else {
409                   dist = len_squared_v3v3(tip[j], verts[i]);
410                   if (dist < radsqr[j]) {
411                     weight = get_weight(dist, decay_rad, dif_rad);
412                   }
413                 }
414               }
415               else {
416                 /* inside bone cylinder */
417                 weight = get_weight(dist, decay_rad, dif_rad);
418               }
419 
420               /* assign weight */
421               MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr);
422               if (dw) {
423                 dw->weight = weight;
424               }
425             }
426           }
427           MEM_SAFE_FREE(verts);
428         }
429       }
430 
431       /* if not multiedit, exit loop*/
432       if (!is_multiedit) {
433         break;
434       }
435     }
436   }
437 
438   /* free the memory allocated */
439   MEM_SAFE_FREE(bonelist);
440   MEM_SAFE_FREE(dgrouplist);
441   MEM_SAFE_FREE(root);
442   MEM_SAFE_FREE(tip);
443   MEM_SAFE_FREE(radsqr);
444   MEM_SAFE_FREE(selected);
445 }
446 
gpencil_object_vgroup_calc_from_armature(const bContext * C,Object * ob,Object * ob_arm,const int mode,const float ratio,const float decay)447 static void gpencil_object_vgroup_calc_from_armature(const bContext *C,
448                                                      Object *ob,
449                                                      Object *ob_arm,
450                                                      const int mode,
451                                                      const float ratio,
452                                                      const float decay)
453 {
454   /* Lets try to create some vertex groups
455    * based on the bones of the parent armature.
456    */
457   bArmature *arm = ob_arm->data;
458 
459   /* always create groups */
460   const int defbase_tot = BLI_listbase_count(&ob->defbase);
461   int defbase_add;
462   /* Traverse the bone list, trying to create empty vertex
463    * groups corresponding to the bone.
464    */
465   defbase_add = gpencil_bone_looper(ob, arm->bonebase.first, NULL, vgroup_add_unique_bone_cb);
466 
467   if (defbase_add) {
468     /* its possible there are DWeight's outside the range of the current
469      * objects deform groups, in this case the new groups wont be empty */
470     ED_vgroup_data_clamp_range(ob->data, defbase_tot);
471   }
472 
473   if (mode == GP_ARMATURE_AUTO) {
474     /* Traverse the bone list, trying to fill vertex groups
475      * with the corresponding vertice weights for which the
476      * bone is closest.
477      */
478     gpencil_add_verts_to_dgroups(C, ob, ob_arm, ratio, decay);
479   }
480 
481   DEG_relations_tag_update(CTX_data_main(C));
482 }
483 
ED_gpencil_add_armature(const bContext * C,ReportList * reports,Object * ob,Object * ob_arm)484 bool ED_gpencil_add_armature(const bContext *C, ReportList *reports, Object *ob, Object *ob_arm)
485 {
486   Main *bmain = CTX_data_main(C);
487   Scene *scene = CTX_data_scene(C);
488 
489   if (ob == NULL) {
490     return false;
491   }
492 
493   /* if no armature modifier, add a new one */
494   GpencilModifierData *md = BKE_gpencil_modifiers_findby_type(ob, eGpencilModifierType_Armature);
495   if (md == NULL) {
496     md = ED_object_gpencil_modifier_add(
497         reports, bmain, scene, ob, "Armature", eGpencilModifierType_Armature);
498     if (md == NULL) {
499       BKE_report(reports, RPT_ERROR, "Unable to add a new Armature modifier to object");
500       return false;
501     }
502     DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
503   }
504 
505   /* verify armature */
506   ArmatureGpencilModifierData *mmd = (ArmatureGpencilModifierData *)md;
507   if (mmd->object == NULL) {
508     mmd->object = ob_arm;
509   }
510   else {
511     if (ob_arm != mmd->object) {
512       BKE_report(reports,
513                  RPT_ERROR,
514                  "The existing Armature modifier is already using a different Armature object");
515       return false;
516     }
517   }
518   return true;
519 }
520 
ED_gpencil_add_armature_weights(const bContext * C,ReportList * reports,Object * ob,Object * ob_arm,int mode)521 bool ED_gpencil_add_armature_weights(
522     const bContext *C, ReportList *reports, Object *ob, Object *ob_arm, int mode)
523 {
524   if (ob == NULL) {
525     return false;
526   }
527 
528   bool success = ED_gpencil_add_armature(C, reports, ob, ob_arm);
529 
530   /* add weights */
531   if (success) {
532     gpencil_object_vgroup_calc_from_armature(C, ob, ob_arm, mode, DEFAULT_RATIO, DEFAULT_DECAY);
533   }
534 
535   return success;
536 }
537 /* ***************** Generate armature weights ************************** */
gpencil_generate_weights_poll(bContext * C)538 static bool gpencil_generate_weights_poll(bContext *C)
539 {
540   Object *ob = CTX_data_active_object(C);
541 
542   if (ob == NULL) {
543     return false;
544   }
545 
546   if (ob->type != OB_GPENCIL) {
547     return false;
548   }
549 
550   ViewLayer *view_layer = CTX_data_view_layer(C);
551   bGPdata *gpd = (bGPdata *)ob->data;
552 
553   if (BLI_listbase_count(&gpd->layers) == 0) {
554     return false;
555   }
556 
557   /* need some armature in the view layer */
558   LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
559     if (base->object->type == OB_ARMATURE) {
560       return true;
561     }
562   }
563 
564   return false;
565 }
566 
gpencil_generate_weights_exec(bContext * C,wmOperator * op)567 static int gpencil_generate_weights_exec(bContext *C, wmOperator *op)
568 {
569   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
570   ViewLayer *view_layer = CTX_data_view_layer(C);
571   Object *ob = CTX_data_active_object(C);
572   Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
573   bGPdata *gpd = (bGPdata *)ob->data;
574   Object *ob_arm = NULL;
575 
576   const int mode = RNA_enum_get(op->ptr, "mode");
577   const float ratio = RNA_float_get(op->ptr, "ratio");
578   const float decay = RNA_float_get(op->ptr, "decay");
579 
580   /* sanity checks */
581   if (ELEM(NULL, ob, gpd)) {
582     return OPERATOR_CANCELLED;
583   }
584 
585   /* get armature */
586   const int arm_idx = RNA_enum_get(op->ptr, "armature");
587   if (arm_idx > 0) {
588     Base *base = BLI_findlink(&view_layer->object_bases, arm_idx - 1);
589     ob_arm = base->object;
590   }
591   else {
592     /* get armature from modifier */
593     GpencilModifierData *md = BKE_gpencil_modifiers_findby_type(ob_eval,
594                                                                 eGpencilModifierType_Armature);
595     if (md == NULL) {
596       BKE_report(op->reports, RPT_ERROR, "The grease pencil object need an Armature modifier");
597       return OPERATOR_CANCELLED;
598     }
599 
600     ArmatureGpencilModifierData *mmd = (ArmatureGpencilModifierData *)md;
601     if (mmd->object == NULL) {
602       BKE_report(op->reports, RPT_ERROR, "Armature modifier is not valid or wrong defined");
603       return OPERATOR_CANCELLED;
604     }
605 
606     ob_arm = mmd->object;
607   }
608 
609   if (ob_arm == NULL) {
610     BKE_report(op->reports, RPT_ERROR, "No Armature object in the view layer");
611     return OPERATOR_CANCELLED;
612   }
613 
614   gpencil_object_vgroup_calc_from_armature(C, ob, ob_arm, mode, ratio, decay);
615 
616   /* notifiers */
617   DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
618   WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
619 
620   return OPERATOR_FINISHED;
621 }
622 
623 /* Dynamically populate an enum of Armatures */
gpencil_armatures_enum_itemf(bContext * C,PointerRNA * UNUSED (ptr),PropertyRNA * UNUSED (prop),bool * r_free)624 static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C,
625                                                             PointerRNA *UNUSED(ptr),
626                                                             PropertyRNA *UNUSED(prop),
627                                                             bool *r_free)
628 {
629   ViewLayer *view_layer = CTX_data_view_layer(C);
630   EnumPropertyItem *item = NULL, item_tmp = {0};
631   int totitem = 0;
632   int i = 0;
633 
634   if (C == NULL) {
635     return DummyRNA_DEFAULT_items;
636   }
637 
638   /* add default */
639   item_tmp.identifier = "DEFAULT";
640   item_tmp.name = "Default";
641   item_tmp.value = 0;
642   RNA_enum_item_add(&item, &totitem, &item_tmp);
643   i++;
644 
645   LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
646     Object *ob = base->object;
647     if (ob->type == OB_ARMATURE) {
648       item_tmp.identifier = item_tmp.name = ob->id.name + 2;
649       item_tmp.value = i;
650       RNA_enum_item_add(&item, &totitem, &item_tmp);
651     }
652     i++;
653   }
654 
655   RNA_enum_item_end(&item, &totitem);
656   *r_free = true;
657 
658   return item;
659 }
660 
GPENCIL_OT_generate_weights(wmOperatorType * ot)661 void GPENCIL_OT_generate_weights(wmOperatorType *ot)
662 {
663   static const EnumPropertyItem mode_type[] = {
664       {GP_ARMATURE_NAME, "NAME", 0, "Empty Groups", ""},
665       {GP_ARMATURE_AUTO, "AUTO", 0, "Automatic Weights", ""},
666       {0, NULL, 0, NULL, NULL},
667   };
668 
669   PropertyRNA *prop;
670 
671   /* identifiers */
672   ot->name = "Generate Automatic Weights";
673   ot->idname = "GPENCIL_OT_generate_weights";
674   ot->description = "Generate automatic weights for armatures (requires armature modifier)";
675 
676   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
677 
678   /* callbacks */
679   ot->exec = gpencil_generate_weights_exec;
680   ot->poll = gpencil_generate_weights_poll;
681 
682   ot->prop = RNA_def_enum(ot->srna, "mode", mode_type, 0, "Mode", "");
683 
684   prop = RNA_def_enum(
685       ot->srna, "armature", DummyRNA_DEFAULT_items, 0, "Armature", "Armature to use");
686   RNA_def_enum_funcs(prop, gpencil_armatures_enum_itemf);
687 
688   RNA_def_float(ot->srna,
689                 "ratio",
690                 DEFAULT_RATIO,
691                 0.0f,
692                 2.0f,
693                 "Ratio",
694                 "Ratio between bone length and influence radius",
695                 0.001f,
696                 1.0f);
697 
698   RNA_def_float(ot->srna,
699                 "decay",
700                 DEFAULT_DECAY,
701                 0.0f,
702                 1.0f,
703                 "Decay",
704                 "Factor to reduce influence depending of distance to bone axis",
705                 0.0f,
706                 1.0f);
707 }
708