diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_armature.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_armature.c | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/source/blender/editors/gpencil/gpencil_armature.c b/source/blender/editors/gpencil/gpencil_armature.c new file mode 100644 index 00000000000..dcbbeaeaf57 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_armature.c @@ -0,0 +1,677 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2018, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Antonio Vazquez + * + * ***** END GPL LICENSE BLOCK ***** + * + * Operators for dealing with armatures and GP datablocks + */ + +/** \file blender/editors/gpencil/gpencil_armature.c + * \ingroup edgpencil + */ + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_utildefines.h" +#include "BLI_ghash.h" +#include "BLI_math.h" +#include "BLI_string_utils.h" + +#include "BLT_translation.h" + +#include "DNA_armature_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" + +#include "BKE_main.h" +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_object_deform.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "ED_gpencil.h" +#include "ED_object.h" +#include "ED_mesh.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +enum { + GP_ARMATURE_NAME = 0, + GP_ARMATURE_AUTO = 1 +}; + +#define DEFAULT_RATIO 0.10f +#define DEFAULT_DECAY 0.8f + +static int gpencil_bone_looper( + Object *ob, Bone *bone, void *data, + int(*bone_func)(Object *, Bone *, void *)) +{ + /* We want to apply the function bone_func to every bone + * in an armature -- feed bone_looper the first bone and + * a pointer to the bone_func and watch it go!. The int count + * can be useful for counting bones with a certain property + * (e.g. skinnable) + */ + int count = 0; + + if (bone) { + /* only do bone_func if the bone is non null */ + count += bone_func(ob, bone, data); + + /* try to execute bone_func for the first child */ + count += gpencil_bone_looper(ob, bone->childbase.first, data, bone_func); + + /* try to execute bone_func for the next bone at this + * depth of the recursion. + */ + count += gpencil_bone_looper(ob, bone->next, data, bone_func); + } + + return count; +} + +static int gpencil_bone_skinnable_cb(Object *UNUSED(ob), Bone *bone, void *datap) +{ + /* Bones that are deforming + * are regarded to be "skinnable" and are eligible for + * auto-skinning. + * + * This function performs 2 functions: + * + * a) It returns 1 if the bone is skinnable. + * If we loop over all bones with this + * function, we can count the number of + * skinnable bones. + * b) If the pointer data is non null, + * it is treated like a handle to a + * bone pointer -- the bone pointer + * is set to point at this bone, and + * the pointer the handle points to + * is incremented to point to the + * next member of an array of pointers + * to bones. This way we can loop using + * this function to construct an array of + * pointers to bones that point to all + * skinnable bones. + */ + Bone ***hbone; + int a, segments; + struct { Object *armob; void *list; int heat;} *data = datap; + + if (!(bone->flag & BONE_HIDDEN_P)) { + if (!(bone->flag & BONE_NO_DEFORM)) { + if (data->heat && data->armob->pose && + BKE_pose_channel_find_name(data->armob->pose, bone->name)) + { + segments = bone->segments; + } + else { + segments = 1; + } + + if (data->list != NULL) { + hbone = (Bone ***)&data->list; + + for (a = 0; a < segments; a++) { + **hbone = bone; + ++*hbone; + } + } + return segments; + } + } + return 0; +} + +static int vgroup_add_unique_bone_cb(Object *ob, Bone *bone, void *UNUSED(ptr)) +{ + /* This group creates a vertex group to ob that has the + * same name as bone (provided the bone is skinnable). + * If such a vertex group already exist the routine exits. + */ + if (!(bone->flag & BONE_NO_DEFORM)) { + if (!defgroup_find_name(ob, bone->name)) { + BKE_object_defgroup_add_name(ob, bone->name); + return 1; + } + } + return 0; +} + +static int dgroup_skinnable_cb(Object *ob, Bone *bone, void *datap) +{ + /* Bones that are deforming + * are regarded to be "skinnable" and are eligible for + * auto-skinning. + * + * This function performs 2 functions: + * + * a) If the bone is skinnable, it creates + * a vertex group for ob that has + * the name of the skinnable bone + * (if one doesn't exist already). + * b) If the pointer data is non null, + * it is treated like a handle to a + * bDeformGroup pointer -- the + * bDeformGroup pointer is set to point + * to the deform group with the bone's + * name, and the pointer the handle + * points to is incremented to point to the + * next member of an array of pointers + * to bDeformGroups. This way we can loop using + * this function to construct an array of + * pointers to bDeformGroups, all with names + * of skinnable bones. + */ + bDeformGroup ***hgroup, *defgroup = NULL; + int a, segments; + struct { Object *armob; void *list; int heat; } *data = datap; + bArmature *arm = data->armob->data; + + if (!(bone->flag & BONE_HIDDEN_P)) { + if (!(bone->flag & BONE_NO_DEFORM)) { + if (data->heat && data->armob->pose && + BKE_pose_channel_find_name(data->armob->pose, bone->name)) + { + segments = bone->segments; + } + else { + segments = 1; + } + + if (arm->layer & bone->layer) { + if (!(defgroup = defgroup_find_name(ob, bone->name))) { + defgroup = BKE_object_defgroup_add_name(ob, bone->name); + } + else if (defgroup->flag & DG_LOCK_WEIGHT) { + /* In case vgroup already exists and is locked, do not modify it here. See T43814. */ + defgroup = NULL; + } + } + + if (data->list != NULL) { + hgroup = (bDeformGroup ***)&data->list; + + for (a = 0; a < segments; a++) { + **hgroup = defgroup; + ++*hgroup; + } + } + return segments; + } + } + return 0; +} + +/* get weight value depending of distance and decay value */ +static float get_weight(float dist, float decay_rad, float dif_rad) +{ + float weight = 1.0f; + if (dist < decay_rad) { + weight = 1.0f; + } + else { + weight = interpf(0.0f, 0.9f, (dist - decay_rad) / dif_rad); + } + + return weight; +} + +/* This functions implements the automatic computation of vertex group weights */ +static void gpencil_add_verts_to_dgroups( + const bContext *C, ReportList *reports, + Depsgraph *depsgraph, + Object *ob, Object *ob_arm, const float ratio, const float decay) +{ + bArmature *arm = ob_arm->data; + Bone **bonelist, *bone; + bDeformGroup **dgrouplist; + bDeformGroup *dgroup; + bPoseChannel *pchan; + bGPdata *gpd = (bGPdata *)ob->data; + bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + Mat4 bbone_array[MAX_BBONE_SUBDIV], *bbone = NULL; + float(*root)[3], (*tip)[3], (*verts)[3]; + float *radsqr; + int *selected; + float weight; + int numbones, i, j, segments = 0; + struct { Object *armob; void *list; int heat; } looper_data; + + looper_data.armob = ob_arm; + looper_data.heat = true; + looper_data.list = NULL; + + /* count the number of skinnable bones */ + numbones = gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, gpencil_bone_skinnable_cb); + + if (numbones == 0) + return; + + /* create an array of pointer to bones that are skinnable + * and fill it with all of the skinnable bones */ + bonelist = MEM_callocN(numbones * sizeof(Bone *), "bonelist"); + looper_data.list = bonelist; + gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, gpencil_bone_skinnable_cb); + + /* create an array of pointers to the deform groups that + * correspond to the skinnable bones (creating them + * as necessary. */ + dgrouplist = MEM_callocN(numbones * sizeof(bDeformGroup *), "dgrouplist"); + + looper_data.list = dgrouplist; + gpencil_bone_looper(ob, arm->bonebase.first, &looper_data, dgroup_skinnable_cb); + + /* create an array of root and tip positions transformed into + * global coords */ + root = MEM_callocN(numbones * sizeof(float) * 3, "root"); + tip = MEM_callocN(numbones * sizeof(float) * 3, "tip"); + selected = MEM_callocN(numbones * sizeof(int), "selected"); + radsqr = MEM_callocN(numbones * sizeof(float), "radsqr"); + + for (j = 0; j < numbones; j++) { + bone = bonelist[j]; + dgroup = dgrouplist[j]; + + /* handle bbone */ + if (segments == 0) { + segments = 1; + bbone = NULL; + + if ((ob_arm->pose) && + (pchan = BKE_pose_channel_find_name(ob_arm->pose, bone->name))) + { + if (bone->segments > 1) { + segments = bone->segments; + b_bone_spline_setup(pchan, 1, bbone_array); + bbone = bbone_array; + } + } + } + + segments--; + + /* compute root and tip */ + if (bbone) { + mul_v3_m4v3(root[j], bone->arm_mat, bbone[segments].mat[3]); + if ((segments + 1) < bone->segments) { + mul_v3_m4v3(tip[j], bone->arm_mat, bbone[segments + 1].mat[3]); + } + else { + copy_v3_v3(tip[j], bone->arm_tail); + } + } + else { + copy_v3_v3(root[j], bone->arm_head); + copy_v3_v3(tip[j], bone->arm_tail); + } + + mul_m4_v3(ob_arm->obmat, root[j]); + mul_m4_v3(ob_arm->obmat, tip[j]); + + selected[j] = 1; + + /* calculate radius squared */ + radsqr[j] = len_squared_v3v3(root[j], tip[j]) * ratio; + } + + /* loop all strokes */ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + bGPDframe *init_gpf = gpl->actframe; + bGPDspoint *pt = NULL; + + if (is_multiedit) { + init_gpf = gpl->frames.first; + } + + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || + ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) + { + + if (gpf == NULL) + continue; + + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; + + BKE_gpencil_dvert_ensure(gps); + + /* create verts array */ + verts = MEM_callocN(gps->totpoints * sizeof(*verts), __func__); + + /* transform stroke points to global space */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + copy_v3_v3(verts[i], &pt->x); + mul_m4_v3(ob->obmat, verts[i]); + } + + /* loop groups and assign weight */ + for (j = 0; j < numbones; j++) { + int def_nr = BLI_findindex(&ob->defbase, dgrouplist[j]); + if (def_nr < 0) { + continue; + } + + float decay_rad = radsqr[j] - (radsqr[j] * decay); + float dif_rad = radsqr[j] - decay_rad; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + MDeformVert *dvert = &gps->dvert[i]; + float dist = dist_squared_to_line_segment_v3(verts[i], root[j], tip[j]); + if (dist > radsqr[j]) { + /* if not in cylinder, check if inside extreme spheres */ + weight = 0.0f; + dist = len_squared_v3v3(root[j], verts[i]); + if (dist < radsqr[j]) { + weight = get_weight(dist, decay_rad, dif_rad); + } + else { + dist = len_squared_v3v3(tip[j], verts[i]); + if (dist < radsqr[j]) { + weight = get_weight(dist, decay_rad, dif_rad); + } + } + } + else { + /* inside bone cylinder */ + weight = get_weight(dist, decay_rad, dif_rad); + } + + /* assign weight */ + MDeformWeight *dw = defvert_verify_index(dvert, def_nr); + if (dw) { + dw->weight = weight; + } + } + } + MEM_SAFE_FREE(verts); + } + } + + /* if not multiedit, exit loop*/ + if (!is_multiedit) { + break; + } + } + } + + /* free the memory allocated */ + MEM_SAFE_FREE(bonelist); + MEM_SAFE_FREE(dgrouplist); + MEM_SAFE_FREE(root); + MEM_SAFE_FREE(tip); + MEM_SAFE_FREE(radsqr); + MEM_SAFE_FREE(selected); +} + +static void gpencil_object_vgroup_calc_from_armature( + const bContext *C, ReportList *reports, + Depsgraph *depsgraph, Object *ob, Object *ob_arm, + const int mode, const float ratio, const float decay) +{ + /* Lets try to create some vertex groups + * based on the bones of the parent armature. + */ + bArmature *arm = ob_arm->data; + + /* always create groups */ + const int defbase_tot = BLI_listbase_count(&ob->defbase); + int defbase_add; + /* Traverse the bone list, trying to create empty vertex + * groups corresponding to the bone. + */ + defbase_add = gpencil_bone_looper(ob, arm->bonebase.first, NULL, + vgroup_add_unique_bone_cb); + + if (defbase_add) { + /* its possible there are DWeight's outside the range of the current + * objects deform groups, in this case the new groups wont be empty */ + ED_vgroup_data_clamp_range(ob->data, defbase_tot); + } + + if (mode == GP_ARMATURE_AUTO) { + /* Traverse the bone list, trying to fill vertex groups + * with the corresponding vertice weights for which the + * bone is closest. + */ + gpencil_add_verts_to_dgroups(C, reports, depsgraph, ob, ob_arm, + ratio, decay); + } +} + +bool ED_gpencil_add_armature_weights( + const bContext *C, ReportList *reports, + Object *ob, Object *ob_arm, int mode) +{ + Main *bmain = CTX_data_main(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Scene *scene = CTX_data_scene(C); + + /* if no armature modifier, add a new one */ + GpencilModifierData *md = BKE_gpencil_modifiers_findByType(ob, eGpencilModifierType_Armature); + if (md == NULL) { + md = ED_object_gpencil_modifier_add(reports, bmain, scene, + ob, "Armature", eGpencilModifierType_Armature); + if (md == NULL) { + BKE_report(reports, RPT_ERROR, + "Unable to add a new Armature modifier to object"); + return false; + } + DEG_id_tag_update(&ob->id, OB_RECALC_OB | OB_RECALC_DATA); + } + + /* verify armature */ + ArmatureGpencilModifierData *mmd = (ArmatureGpencilModifierData *)md; + if (mmd->object == NULL) { + mmd->object = ob_arm; + } + else { + if (ob_arm != mmd->object) { + BKE_report(reports, RPT_ERROR, + "The existing Armature modifier is already using a different Armature object"); + return false; + } + } + + /* add weights */ + gpencil_object_vgroup_calc_from_armature(C, reports, depsgraph, + ob, ob_arm, mode, + DEFAULT_RATIO, DEFAULT_DECAY); + + return true; +} +/* ***************** Generate armature weights ************************** */ +bool gpencil_generate_weights_poll(bContext *C) +{ + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Object *ob = CTX_data_active_object(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); + bGPdata *gpd = (bGPdata *)ob->data; + + if (BLI_listbase_count(&gpd->layers) == 0) { + return false; + } + + /* need some armature in the view layer */ + for (Base *base = view_layer->object_bases.first; base; base = base->next) { + if (base->object->type == OB_ARMATURE) { + return true; + } + } + + return false; +} + +static int gpencil_generate_weights_exec(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_depsgraph(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = CTX_data_active_object(C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); + bGPdata *gpd = (bGPdata *)ob->data; + Object *ob_arm = NULL; + + const int mode = RNA_enum_get(op->ptr, "mode"); + const float ratio = RNA_float_get(op->ptr, "ratio"); + const float decay = RNA_float_get(op->ptr, "decay"); + + /* sanity checks */ + if (ELEM(NULL, ob, gpd)) + return OPERATOR_CANCELLED; + + /* get armature */ + const int arm_idx = RNA_enum_get(op->ptr, "armature"); + if (arm_idx > 0) { + Base *base = BLI_findlink(&view_layer->object_bases, arm_idx - 1); + ob_arm = base->object; + } + else { + /* get armature from modifier */ + GpencilModifierData *md = BKE_gpencil_modifiers_findByType(ob_eval, eGpencilModifierType_Armature); + if (md == NULL) { + BKE_report(op->reports, RPT_ERROR, + "The grease pencil object need an Armature modifier"); + return OPERATOR_CANCELLED; + } + + ArmatureGpencilModifierData *mmd = (ArmatureGpencilModifierData *)md; + if (mmd->object == NULL) { + BKE_report(op->reports, RPT_ERROR, + "Armature modifier is not valid or wrong defined"); + return OPERATOR_CANCELLED; + } + + ob_arm = mmd->object; + } + + if (ob_arm == NULL) { + BKE_report(op->reports, RPT_ERROR, + "No Armature object in the view layer"); + return OPERATOR_CANCELLED; + } + + gpencil_object_vgroup_calc_from_armature(C, op->reports,depsgraph, + ob, ob_arm, mode, ratio, decay); + + /* notifiers */ + DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +/* Dynamically populate an enum of Armatures */ +static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + ViewLayer *view_layer = CTX_data_view_layer(C); + EnumPropertyItem *item = NULL, item_tmp = { 0 }; + int totitem = 0; + int i = 0; + + if (C == NULL) { + return DummyRNA_DEFAULT_items; + } + + /* add default */ + item_tmp.identifier = "DEFAULT"; + item_tmp.name = "Default"; + item_tmp.value = 0; + RNA_enum_item_add(&item, &totitem, &item_tmp); + i++; + + for (Base *base = view_layer->object_bases.first; base; base = base->next) { + Object *ob = base->object; + if (ob->type == OB_ARMATURE) { + item_tmp.identifier = item_tmp.name = ob->id.name + 2; + item_tmp.value = i; + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + i++; + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +void GPENCIL_OT_generate_weights(wmOperatorType *ot) +{ + static const EnumPropertyItem mode_type[] = { + {GP_ARMATURE_NAME, "NAME", 0, "Empty Groups", ""}, + {GP_ARMATURE_AUTO, "AUTO", 0, "Automatic Weights", ""}, + {0, NULL, 0, NULL, NULL} + }; + + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Generate Automatic Weights"; + ot->idname = "GPENCIL_OT_generate_weights"; + ot->description = "Generate automatic weights for armatures (requires armature modifier)"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gpencil_generate_weights_exec; + ot->poll = gpencil_generate_weights_poll; + + ot->prop = RNA_def_enum(ot->srna, "mode", mode_type, 0, "Mode", ""); + + prop = RNA_def_enum(ot->srna, "armature", DummyRNA_DEFAULT_items, 0, "Armature", "Armature to use"); + RNA_def_enum_funcs(prop, gpencil_armatures_enum_itemf); + + RNA_def_float(ot->srna, "ratio", DEFAULT_RATIO, 0.0f, 2.0f, "Ratio", + "Ratio between bone length and influence radius", 0.001f, 1.0f); + + RNA_def_float(ot->srna, "decay", DEFAULT_DECAY, 0.0f, 1.0f, "Decay", + "Factor to reduce influence depending of distance to bone axis", 0.0f, 1.0f); +} |