Merge branch 'blender-v2.81-release'
[blender.git] / source / blender / editors / transform / transform_gizmo_extrude_3d.c
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
17 /** \file
18  * \ingroup edmesh
19  */
20
21 #include "BLI_utildefines.h"
22 #include "BLI_array_utils.h"
23 #include "BLI_math.h"
24 #include "BLI_listbase.h"
25
26 #include "BKE_context.h"
27 #include "BKE_global.h"
28
29 #include "RNA_access.h"
30 #include "RNA_define.h"
31
32 #include "WM_api.h"
33 #include "WM_types.h"
34 #include "WM_message.h"
35 #include "WM_toolsystem.h"
36
37 #include "ED_screen.h"
38 #include "ED_transform.h"
39 #include "ED_view3d.h"
40 #include "ED_gizmo_library.h"
41 #include "ED_gizmo_utils.h"
42
43 #include "UI_resources.h"
44
45 #include "MEM_guardedalloc.h"
46
47 /* -------------------------------------------------------------------- */
48 /** \name Extrude Gizmo
49  * \{ */
50
51 enum {
52   EXTRUDE_AXIS_NORMAL = 0,
53   EXTRUDE_AXIS_XYZ = 1,
54 };
55
56 static const float extrude_button_scale = 0.15f;
57 static const float extrude_button_offset_scale = 1.5f;
58 static const float extrude_arrow_scale = 1.0f;
59 static const float extrude_arrow_xyz_axis_scale = 1.0f;
60 static const float extrude_arrow_normal_axis_scale = 1.0f;
61 static const float extrude_dial_scale = 0.2;
62
63 static const uchar shape_plus[] = {
64     0x5f, 0xfb, 0x40, 0xee, 0x25, 0xda, 0x11, 0xbf, 0x4,  0xa0, 0x0,  0x80, 0x4,  0x5f, 0x11, 0x40,
65     0x25, 0x25, 0x40, 0x11, 0x5f, 0x4,  0x7f, 0x0,  0xa0, 0x4,  0xbf, 0x11, 0xda, 0x25, 0xee, 0x40,
66     0xfb, 0x5f, 0xff, 0x7f, 0xfb, 0xa0, 0xee, 0xbf, 0xda, 0xda, 0xbf, 0xee, 0xa0, 0xfb, 0x80, 0xff,
67     0x6e, 0xd7, 0x92, 0xd7, 0x92, 0x90, 0xd8, 0x90, 0xd8, 0x6d, 0x92, 0x6d, 0x92, 0x27, 0x6e, 0x27,
68     0x6e, 0x6d, 0x28, 0x6d, 0x28, 0x90, 0x6e, 0x90, 0x6e, 0xd7, 0x80, 0xff, 0x5f, 0xfb, 0x5f, 0xfb,
69 };
70
71 typedef struct GizmoExtrudeGroup {
72
73   /* XYZ & normal. */
74   wmGizmo *invoke_xyz_no[4];
75   /* Constrained & unconstrained (arrow & circle). */
76   wmGizmo *adjust[2];
77   int adjust_axis;
78
79   /* Copied from the transform operator,
80    * use to redo with the same settings. */
81   struct {
82     float orient_matrix[3][3];
83     bool constraint_axis[3];
84     float value[4];
85   } redo_xform;
86
87   /* Depends on object type. */
88   int normal_axis;
89
90   struct {
91     float normal_mat3[3][3]; /* use Z axis for normal. */
92     int orientation_type;
93   } data;
94
95   wmOperatorType *ot_extrude;
96   PropertyRNA *gzgt_axis_type_prop;
97 } GizmoExtrudeGroup;
98
99 static void gizmo_mesh_extrude_orientation_matrix_set(struct GizmoExtrudeGroup *ggd,
100                                                       const float mat[3][3])
101 {
102   for (int i = 0; i < 3; i++) {
103     mul_v3_v3fl(ggd->invoke_xyz_no[i]->matrix_offset[3],
104                 mat[i],
105                 (extrude_arrow_xyz_axis_scale * extrude_button_offset_scale) /
106                     extrude_button_scale);
107   }
108 }
109
110 static void gizmo_mesh_extrude_orientation_matrix_set_for_adjust(struct GizmoExtrudeGroup *ggd,
111                                                                  const float mat[3][3])
112 {
113   /* Set orientation without location. */
114   for (int j = 0; j < 3; j++) {
115     copy_v3_v3(ggd->adjust[0]->matrix_basis[j], mat[j]);
116   }
117   /* nop when (i == 2). */
118   swap_v3_v3(ggd->adjust[0]->matrix_basis[ggd->adjust_axis], ggd->adjust[0]->matrix_basis[2]);
119 }
120
121 static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
122 {
123   struct GizmoExtrudeGroup *ggd = MEM_callocN(sizeof(GizmoExtrudeGroup), __func__);
124   gzgroup->customdata = ggd;
125
126   const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true);
127   const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_button_2d", true);
128   const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
129
130   ggd->adjust[0] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
131   ggd->adjust[1] = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
132   for (int i = 0; i < 4; i++) {
133     ggd->invoke_xyz_no[i] = WM_gizmo_new_ptr(gzt_move, gzgroup, NULL);
134     ggd->invoke_xyz_no[i]->flag |= WM_GIZMO_DRAW_OFFSET_SCALE;
135   }
136
137   {
138     PropertyRNA *prop = RNA_struct_find_property(ggd->invoke_xyz_no[3]->ptr, "shape");
139     for (int i = 0; i < 4; i++) {
140       RNA_property_string_set_bytes(
141           ggd->invoke_xyz_no[i]->ptr, prop, (const char *)shape_plus, ARRAY_SIZE(shape_plus));
142     }
143   }
144
145   {
146     const char *op_idname = NULL;
147     /* grease pencil does not use obedit */
148     /* GPXX: Remove if OB_MODE_EDIT_GPENCIL is merged with OB_MODE_EDIT */
149     const Object *obact = CTX_data_active_object(C);
150     if (obact->type == OB_GPENCIL) {
151       op_idname = "GPENCIL_OT_extrude_move";
152     }
153     else if (obact->type == OB_MESH) {
154       op_idname = "MESH_OT_extrude_context_move";
155       ggd->normal_axis = 2;
156     }
157     else if (obact->type == OB_ARMATURE) {
158       op_idname = "ARMATURE_OT_extrude_move";
159       ggd->normal_axis = 1;
160     }
161     else if (obact->type == OB_CURVE) {
162       op_idname = "CURVE_OT_extrude_move";
163       ggd->normal_axis = 2;
164     }
165     else {
166       BLI_assert(0);
167     }
168     ggd->ot_extrude = WM_operatortype_find(op_idname, true);
169     ggd->gzgt_axis_type_prop = RNA_struct_type_find_property(gzgroup->type->srna, "axis_type");
170   }
171
172   for (int i = 0; i < 3; i++) {
173     UI_GetThemeColor3fv(TH_AXIS_X + i, ggd->invoke_xyz_no[i]->color);
174   }
175   UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->invoke_xyz_no[3]->color);
176   for (int i = 0; i < 2; i++) {
177     UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->adjust[i]->color);
178   }
179
180   for (int i = 0; i < 4; i++) {
181     WM_gizmo_set_scale(ggd->invoke_xyz_no[i], extrude_button_scale);
182   }
183   WM_gizmo_set_scale(ggd->adjust[0], extrude_arrow_scale);
184   WM_gizmo_set_scale(ggd->adjust[1], extrude_dial_scale);
185   ggd->adjust[1]->line_width = 2.0f;
186
187   /* XYZ & normal axis extrude. */
188   for (int i = 0; i < 4; i++) {
189     PointerRNA *ptr = WM_gizmo_operator_set(ggd->invoke_xyz_no[i], 0, ggd->ot_extrude, NULL);
190     {
191       bool constraint[3] = {0, 0, 0};
192       constraint[(i < 3) ? i : ggd->normal_axis] = true;
193       PointerRNA macroptr = RNA_pointer_get(ptr, "TRANSFORM_OT_translate");
194       RNA_boolean_set(&macroptr, "release_confirm", true);
195       RNA_boolean_set_array(&macroptr, "constraint_axis", constraint);
196     }
197   }
198
199   /* Adjust extrude. */
200   for (int i = 0; i < 2; i++) {
201     wmGizmo *gz = ggd->adjust[i];
202     PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ggd->ot_extrude, NULL);
203     PointerRNA macroptr = RNA_pointer_get(ptr, "TRANSFORM_OT_translate");
204     RNA_boolean_set(&macroptr, "release_confirm", true);
205     wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
206     gzop->is_redo = true;
207   }
208 }
209
210 static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup)
211 {
212   GizmoExtrudeGroup *ggd = gzgroup->customdata;
213
214   for (int i = 0; i < 4; i++) {
215     WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, true);
216   }
217   for (int i = 0; i < 2; i++) {
218     WM_gizmo_set_flag(ggd->adjust[i], WM_GIZMO_HIDDEN, true);
219   }
220
221   if (G.moving) {
222     return;
223   }
224
225   Scene *scene = CTX_data_scene(C);
226
227   int axis_type;
228   {
229     PointerRNA ptr;
230     bToolRef *tref = WM_toolsystem_ref_from_context((bContext *)C);
231     WM_toolsystem_ref_properties_ensure_from_gizmo_group(tref, gzgroup->type, &ptr);
232     axis_type = RNA_property_enum_get(&ptr, ggd->gzgt_axis_type_prop);
233   }
234
235   ggd->data.orientation_type = scene->orientation_slots[SCE_ORIENT_DEFAULT].type;
236   const bool use_normal = ((ggd->data.orientation_type != V3D_ORIENT_NORMAL) ||
237                            (axis_type == EXTRUDE_AXIS_NORMAL));
238   const int axis_len_used = use_normal ? 4 : 3;
239
240   struct TransformBounds tbounds;
241
242   if (use_normal) {
243     struct TransformBounds tbounds_normal;
244     if (!ED_transform_calc_gizmo_stats(C,
245                                        &(struct TransformCalcParams){
246                                            .orientation_type = V3D_ORIENT_NORMAL + 1,
247                                        },
248                                        &tbounds_normal)) {
249       unit_m3(tbounds_normal.axis);
250     }
251     copy_m3_m3(ggd->data.normal_mat3, tbounds_normal.axis);
252   }
253
254   /* TODO(campbell): run second since this modifies the 3D view, it should not. */
255   if (!ED_transform_calc_gizmo_stats(C,
256                                      &(struct TransformCalcParams){
257                                          .orientation_type = ggd->data.orientation_type + 1,
258                                      },
259                                      &tbounds)) {
260     return;
261   }
262
263   /* Main axis is normal. */
264   if (!use_normal) {
265     copy_m3_m3(ggd->data.normal_mat3, tbounds.axis);
266   }
267
268   /* Offset the add icon. */
269   mul_v3_v3fl(ggd->invoke_xyz_no[3]->matrix_offset[3],
270               ggd->data.normal_mat3[ggd->normal_axis],
271               (extrude_arrow_normal_axis_scale * extrude_button_offset_scale) /
272                   extrude_button_scale);
273
274   /* Adjust current operator. */
275   /* Don't use 'WM_operator_last_redo' because selection actions will be ignored. */
276   wmOperator *op = CTX_wm_manager(C)->operators.last;
277   bool has_redo = (op && op->type == ggd->ot_extrude);
278   wmOperator *op_xform = has_redo ? op->macro.last : NULL;
279
280   bool adjust_is_flip = false;
281   wmGizmo *gz_adjust = NULL;
282
283   if (has_redo) {
284     gz_adjust = ggd->adjust[1];
285     /* We can't access this from 'ot->last_properties'
286      * because some properties use skip-save. */
287     RNA_float_get_array(op_xform->ptr, "orient_matrix", &ggd->redo_xform.orient_matrix[0][0]);
288     RNA_boolean_get_array(op_xform->ptr, "constraint_axis", ggd->redo_xform.constraint_axis);
289     RNA_float_get_array(op_xform->ptr, "value", ggd->redo_xform.value);
290
291     /* Set properties for redo. */
292     for (int i = 0; i < 3; i++) {
293       if (ggd->redo_xform.constraint_axis[i]) {
294         adjust_is_flip = ggd->redo_xform.value[i] < 0.0f;
295         ggd->adjust_axis = i;
296         gz_adjust = ggd->adjust[0];
297         break;
298       }
299     }
300   }
301
302   /* Needed for normal orientation. */
303   gizmo_mesh_extrude_orientation_matrix_set(ggd, tbounds.axis);
304
305   /* Location. */
306   for (int i = 0; i < axis_len_used; i++) {
307     WM_gizmo_set_matrix_location(ggd->invoke_xyz_no[i], tbounds.center);
308   }
309   /* Un-hide. */
310   for (int i = 0; i < axis_len_used; i++) {
311     WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, false);
312   }
313
314   if (has_redo) {
315     if (gz_adjust == ggd->adjust[0]) {
316       gizmo_mesh_extrude_orientation_matrix_set_for_adjust(ggd, ggd->redo_xform.orient_matrix);
317       if (adjust_is_flip) {
318         negate_v3(ggd->adjust[0]->matrix_basis[2]);
319       }
320     }
321     WM_gizmo_set_matrix_location(gz_adjust, tbounds.center);
322     WM_gizmo_set_flag(gz_adjust, WM_GIZMO_HIDDEN, false);
323   }
324
325   /* Redo with current settings. */
326   if (has_redo) {
327     for (int i = 0; i < 4; i++) {
328       RNA_enum_set(ggd->invoke_xyz_no[i]->ptr,
329                    "draw_options",
330                    ((gz_adjust == ggd->adjust[0]) &&
331                     dot_v3v3(ggd->adjust[0]->matrix_basis[2],
332                              ggd->invoke_xyz_no[i]->matrix_offset[3]) > 0.98f) ?
333                        0 :
334                        ED_GIZMO_BUTTON_SHOW_HELPLINE);
335     }
336   }
337   else {
338     for (int i = 0; i < 4; i++) {
339       RNA_enum_set(ggd->invoke_xyz_no[i]->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_HELPLINE);
340     }
341   }
342
343   /* TODO: skip calculating axis which wont be used (above). */
344   switch (axis_type) {
345     case EXTRUDE_AXIS_NORMAL:
346       for (int i = 0; i < 3; i++) {
347         WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, true);
348       }
349       break;
350     case EXTRUDE_AXIS_XYZ:
351       WM_gizmo_set_flag(ggd->invoke_xyz_no[3], WM_GIZMO_HIDDEN, true);
352       break;
353   }
354 }
355
356 static void gizmo_mesh_extrude_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
357 {
358   GizmoExtrudeGroup *ggd = gzgroup->customdata;
359   switch (ggd->data.orientation_type) {
360     case V3D_ORIENT_VIEW: {
361       RegionView3D *rv3d = CTX_wm_region_view3d(C);
362       float mat[3][3];
363       copy_m3_m4(mat, rv3d->viewinv);
364       normalize_m3(mat);
365       gizmo_mesh_extrude_orientation_matrix_set(ggd, mat);
366       break;
367     }
368   }
369
370   /* Basic ordering for drawing only. */
371   {
372     RegionView3D *rv3d = CTX_wm_region_view3d(C);
373     LISTBASE_FOREACH (wmGizmo *, gz, &gzgroup->gizmos) {
374       gz->temp.f = dot_v3v3(rv3d->viewinv[2], gz->matrix_offset[3]);
375     }
376     BLI_listbase_sort(&gzgroup->gizmos, WM_gizmo_cmp_temp_fl_reverse);
377
378     if ((ggd->adjust[1]->flag & WM_GIZMO_HIDDEN) == 0) {
379       copy_v3_v3(ggd->adjust[1]->matrix_basis[0], rv3d->viewinv[0]);
380       copy_v3_v3(ggd->adjust[1]->matrix_basis[1], rv3d->viewinv[1]);
381       copy_v3_v3(ggd->adjust[1]->matrix_basis[2], rv3d->viewinv[2]);
382     }
383   }
384 }
385
386 static void gizmo_mesh_extrude_invoke_prepare(const bContext *UNUSED(C),
387                                               wmGizmoGroup *gzgroup,
388                                               wmGizmo *gz,
389                                               const wmEvent *UNUSED(event))
390 {
391   GizmoExtrudeGroup *ggd = gzgroup->customdata;
392   if (ELEM(gz, ggd->adjust[0], ggd->adjust[1])) {
393     /* Set properties for redo. */
394     wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
395     PointerRNA macroptr = RNA_pointer_get(&gzop->ptr, "TRANSFORM_OT_translate");
396     if (gz == ggd->adjust[0]) {
397       RNA_boolean_set_array(&macroptr, "constraint_axis", ggd->redo_xform.constraint_axis);
398       RNA_float_set_array(&macroptr, "orient_matrix", &ggd->redo_xform.orient_matrix[0][0]);
399       RNA_enum_set(&macroptr, "orient_type", V3D_ORIENT_NORMAL);
400     }
401     RNA_float_set_array(&macroptr, "value", ggd->redo_xform.value);
402   }
403   else {
404     /* Workaround for extrude action modifying normals. */
405     const int i = BLI_array_findindex(ggd->invoke_xyz_no, ARRAY_SIZE(ggd->invoke_xyz_no), &gz);
406     BLI_assert(i != -1);
407     bool use_normal_matrix = false;
408     if (i == 3) {
409       use_normal_matrix = true;
410     }
411     else if (ggd->data.orientation_type == V3D_ORIENT_NORMAL) {
412       use_normal_matrix = true;
413     }
414     if (use_normal_matrix) {
415       wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
416       PointerRNA macroptr = RNA_pointer_get(&gzop->ptr, "TRANSFORM_OT_translate");
417       RNA_float_set_array(&macroptr, "orient_matrix", &ggd->data.normal_mat3[0][0]);
418       RNA_enum_set(&macroptr, "orient_type", V3D_ORIENT_NORMAL);
419     }
420   }
421 }
422
423 static void gizmo_mesh_extrude_message_subscribe(const bContext *C,
424                                                  wmGizmoGroup *gzgroup,
425                                                  struct wmMsgBus *mbus)
426 {
427   GizmoExtrudeGroup *ggd = gzgroup->customdata;
428   ARegion *ar = CTX_wm_region(C);
429
430   /* Subscribe to view properties */
431   wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = {
432       .owner = ar,
433       .user_data = gzgroup->parent_gzmap,
434       .notify = WM_gizmo_do_msg_notify_tag_refresh,
435   };
436
437   {
438     WM_msg_subscribe_rna_anon_prop(
439         mbus, TransformOrientationSlot, type, &msg_sub_value_gz_tag_refresh);
440   }
441
442   WM_msg_subscribe_rna_params(mbus,
443                               &(const wmMsgParams_RNA){
444                                   .ptr =
445                                       (PointerRNA){
446                                           .type = gzgroup->type->srna,
447                                       },
448                                   .prop = ggd->gzgt_axis_type_prop,
449                               },
450                               &msg_sub_value_gz_tag_refresh,
451                               __func__);
452 }
453
454 void VIEW3D_GGT_xform_extrude(struct wmGizmoGroupType *gzgt)
455 {
456   gzgt->name = "3D View Extrude";
457   gzgt->idname = "VIEW3D_GGT_xform_extrude";
458
459   gzgt->flag = WM_GIZMOGROUPTYPE_3D;
460
461   gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
462   gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
463
464   gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool;
465   gzgt->setup = gizmo_mesh_extrude_setup;
466   gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
467   gzgt->refresh = gizmo_mesh_extrude_refresh;
468   gzgt->draw_prepare = gizmo_mesh_extrude_draw_prepare;
469   gzgt->invoke_prepare = gizmo_mesh_extrude_invoke_prepare;
470   gzgt->message_subscribe = gizmo_mesh_extrude_message_subscribe;
471
472   static const EnumPropertyItem axis_type_items[] = {
473       {EXTRUDE_AXIS_NORMAL, "NORMAL", 0, "Regular", "Only show normal axis"},
474       {EXTRUDE_AXIS_XYZ, "XYZ", 0, "XYZ", "Follow scene orientation"},
475       {0, NULL, 0, NULL, NULL},
476   };
477   RNA_def_enum(gzgt->srna, "axis_type", axis_type_items, 0, "Axis Type", "");
478 }
479
480 /** \} */