code cleanup: better use of BLI_array_* (grow in larger steps where possible), includ...
[blender.git] / source / blender / bmesh / operators / bmo_extrude.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Joseph Eagar.
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/bmesh/operators/bmo_extrude.c
24  *  \ingroup bmesh
25  */
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_math.h"
30 #include "BLI_array.h"
31
32 #include "bmesh.h"
33
34 #include "intern/bmesh_operators_private.h" /* own include */
35
36 enum {
37         EXT_INPUT   = 1,
38         EXT_KEEP    = 2,
39         EXT_DEL     = 4
40 };
41
42 #define VERT_MARK 1
43 #define EDGE_MARK 1
44 #define FACE_MARK 1
45 #define VERT_NONMAN 2
46 #define EDGE_NONMAN 2
47
48 void bmo_extrude_face_indiv_exec(BMesh *bm, BMOperator *op)
49 {
50         BMOIter siter;
51         BMIter liter, liter2;
52         BMFace *f, *f2, *f3;
53         BMLoop *l, *l2, *l3, *l4, *l_tmp;
54         BMEdge **edges = NULL, *e, *laste;
55         BMVert *v, *lastv, *firstv;
56         BLI_array_declare(edges);
57         int i;
58
59         BMO_ITER (f, &siter, bm, op, "faces", BM_FACE) {
60                 BLI_array_empty(edges);
61                 BLI_array_growitems(edges, f->len);
62
63                 i = 0;
64                 firstv = lastv = NULL;
65                 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
66                         v = BM_vert_create(bm, l->v->co, l->v);
67
68                         /* skip on the first iteration */
69                         if (lastv) {
70                                 e = BM_edge_create(bm, lastv, v, l->e, FALSE);
71                                 edges[i++] = e;
72                         }
73
74                         lastv = v;
75                         laste = l->e;
76                         if (!firstv) firstv = v;
77                 }
78
79                 /* this fits in the array because we skip one in the loop above */
80                 e = BM_edge_create(bm, v, firstv, laste, FALSE);
81                 edges[i++] = e;
82
83                 BMO_elem_flag_enable(bm, f, EXT_DEL);
84
85                 f2 = BM_face_create_ngon(bm, firstv, BM_edge_other_vert(edges[0], firstv), edges, f->len, FALSE);
86                 if (!f2) {
87                         BMO_error_raise(bm, op, BMERR_MESH_ERROR, "Extrude failed; could not create face");
88                         BLI_array_free(edges);
89                         return;
90                 }
91                 
92                 BMO_elem_flag_enable(bm, f2, EXT_KEEP);
93                 BM_elem_attrs_copy(bm, bm, f, f2);
94
95                 l2 = BM_iter_new(&liter2, bm, BM_LOOPS_OF_FACE, f2);
96                 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
97                         BM_elem_attrs_copy(bm, bm, l, l2);
98
99                         l3 = l->next;
100                         l4 = l2->next;
101
102                         f3 = BM_face_create_quad_tri(bm, l3->v, l4->v, l2->v, l->v, f, FALSE);
103
104                         l_tmp = BM_FACE_FIRST_LOOP(f3);
105
106                         BM_elem_attrs_copy(bm, bm, l->next, l_tmp);  l_tmp = l_tmp->next;
107                         BM_elem_attrs_copy(bm, bm, l->next, l_tmp);  l_tmp = l_tmp->next;
108                         BM_elem_attrs_copy(bm, bm, l, l_tmp);        l_tmp = l_tmp->next;
109                         BM_elem_attrs_copy(bm, bm, l, l_tmp);
110
111                         l2 = BM_iter_step(&liter2);
112                 }
113         }
114
115         BLI_array_free(edges);
116
117         BMO_op_callf(bm, "del geom=%ff context=%i", EXT_DEL, DEL_ONLYFACES);
118         BMO_slot_buffer_from_enabled_flag(bm, op, "faceout", BM_FACE, EXT_KEEP);
119 }
120
121 /**
122  * \brief Copy the loop pair from an adjacent face to both sides of this quad.
123  *
124  * The face is assumed to be a quad, created by extruding.
125  * This function won't crash if its not but won't work right either.
126  * \a e_b is the new edge.
127  *
128  * \note this function could be exposed as an api call if other areas need it,
129  * so far only extrude does.
130  */
131 static void bm_extrude_copy_face_loop_attributes(BMesh *bm, BMFace *f, BMEdge *e_a, BMEdge *e_b)
132 {
133         /* 'a' is the starting edge #e, 'b' is the final edge #newedge */
134         BMLoop *l_dst_a = BM_face_edge_share_loop(f, e_a);
135         BMLoop *l_dst_b = BM_face_edge_share_loop(f, e_b);
136         /* we could only have a face on one-or the other edges,
137          * check if either side of the face has an adjacent face */
138         BMLoop *l_src_1;
139         BMLoop *l_src_2;
140
141         /* there is no l_src_b */
142
143         /* sanity */
144         BLI_assert(l_dst_a->f == l_dst_b->f);
145
146         if (l_dst_a != l_dst_a->radial_next) {
147                 l_src_1 = l_dst_a->radial_next;
148                 l_src_2 = l_src_1->next;
149         }
150         else if (l_dst_b != l_dst_b->radial_next) {
151                 l_src_2 = l_dst_b->radial_next;
152                 l_src_1 = l_src_2->next;
153         }
154         else {
155                 /* no new faces on either edge, nothing to copy from */
156                 return;
157         }
158
159         BM_elem_attrs_copy(bm, bm, l_src_1->f, l_dst_a->f);
160         BM_elem_flag_disable(f, BM_ELEM_HIDDEN); /* possibly we copy from a hidden face */
161
162         /* copy data */
163         BM_elem_attrs_copy(bm, bm, l_src_2, l_dst_a);
164         BM_elem_attrs_copy(bm, bm, l_src_2, l_dst_b->next);
165
166         BM_elem_attrs_copy(bm, bm, l_src_1, l_dst_a->next);
167         BM_elem_attrs_copy(bm, bm, l_src_1, l_dst_b);
168 }
169
170 void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op)
171 {
172         BMOIter siter;
173         BMOperator dupeop;
174         BMVert *v1, *v2, *v3, *v4;
175         BMEdge *e, *e2;
176         BMFace *f;
177         
178         BMO_ITER (e, &siter, bm, op, "edges", BM_EDGE) {
179                 BMO_elem_flag_enable(bm, e, EXT_INPUT);
180                 BMO_elem_flag_enable(bm, e->v1, EXT_INPUT);
181                 BMO_elem_flag_enable(bm, e->v2, EXT_INPUT);
182         }
183
184         BMO_op_initf(bm, &dupeop, "dupe geom=%fve", EXT_INPUT);
185         BMO_op_exec(bm, &dupeop);
186
187         for (e = BMO_iter_new(&siter, bm, &dupeop, "boundarymap", 0); e; e = BMO_iter_step(&siter)) {
188                 e2 = BMO_iter_map_value(&siter);
189                 e2 = *(BMEdge **)e2;
190
191                 if (e->l && e->v1 != e->l->v) {
192                         v1 = e->v1;
193                         v2 = e->v2;
194                         v3 = e2->v2;
195                         v4 = e2->v1;
196                 }
197                 else {
198                         v1 = e2->v1;
199                         v2 = e2->v2;
200                         v3 = e->v2;
201                         v4 = e->v1;
202                 }
203                 /* not sure what to do about example face, pass NULL for now */
204                 f = BM_face_create_quad_tri(bm, v1, v2, v3, v4, NULL, FALSE);
205                 bm_extrude_copy_face_loop_attributes(bm, f, e, e2);
206                 
207                 if (BMO_elem_flag_test(bm, e, EXT_INPUT))
208                         e = e2;
209                 
210                 BMO_elem_flag_enable(bm, f, EXT_KEEP);
211                 BMO_elem_flag_enable(bm, e, EXT_KEEP);
212                 BMO_elem_flag_enable(bm, e->v1, EXT_KEEP);
213                 BMO_elem_flag_enable(bm, e->v2, EXT_KEEP);
214                 
215         }
216
217         BMO_op_finish(bm, &dupeop);
218
219         BMO_slot_buffer_from_enabled_flag(bm, op, "geomout", BM_ALL, EXT_KEEP);
220 }
221
222 void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op)
223 {
224         BMOIter siter;
225         BMVert *v, *dupev;
226         BMEdge *e;
227
228         for (v = BMO_iter_new(&siter, bm, op, "verts", BM_VERT); v; v = BMO_iter_step(&siter)) {
229                 dupev = BM_vert_create(bm, v->co, v);
230
231                 e = BM_edge_create(bm, v, dupev, NULL, FALSE);
232
233                 BMO_elem_flag_enable(bm, e, EXT_KEEP);
234                 BMO_elem_flag_enable(bm, dupev, EXT_KEEP);
235         }
236
237         BMO_slot_buffer_from_enabled_flag(bm, op, "vertout", BM_VERT, EXT_KEEP);
238         BMO_slot_buffer_from_enabled_flag(bm, op, "edgeout", BM_EDGE, EXT_KEEP);
239 }
240
241 void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op)
242 {
243         BMOperator dupeop, delop;
244         BMOIter siter;
245         BMIter iter, fiter, viter;
246         BMEdge *e, *newedge;
247         BMVert *verts[4], *v, *v2;
248         BMFace *f;
249         int found, fwd, delorig = FALSE;
250
251         /* initialize our sub-operators */
252         BMO_op_init(bm, &dupeop, "dupe");
253         
254         BMO_slot_buffer_flag_enable(bm, op, "edgefacein", BM_EDGE|BM_FACE, EXT_INPUT);
255         
256         /* if one flagged face is bordered by an un-flagged face, then we delete
257          * original geometry unless caller explicitly asked to keep it. */
258         if (!BMO_slot_bool_get(op, "alwayskeeporig")) {
259                 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
260
261                         int edge_face_tot;
262
263                         if (!BMO_elem_flag_test(bm, e, EXT_INPUT)) {
264                                 continue;
265                         }
266
267                         found = FALSE; /* found a face that isn't input? */
268                         edge_face_tot = 0; /* edge/face count */
269
270                         BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
271                                 if (!BMO_elem_flag_test(bm, f, EXT_INPUT)) {
272                                         found = TRUE;
273                                         delorig = TRUE;
274                                         break;
275                                 }
276
277                                 edge_face_tot++;
278                         }
279
280                         if ((edge_face_tot > 1) && (found == FALSE)) {
281                                 /* edge has a face user, that face isn't extrude input */
282                                 BMO_elem_flag_enable(bm, e, EXT_DEL);
283                         }
284                 }
285         }
286
287         /* calculate verts to delete */
288         BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
289                 found = FALSE;
290
291                 BM_ITER_ELEM (e, &viter, v, BM_EDGES_OF_VERT) {
292                         if (!BMO_elem_flag_test(bm, e, EXT_INPUT) || !BMO_elem_flag_test(bm, e, EXT_DEL)) {
293                                 found = TRUE;
294                                 break;
295                         }
296                 }
297
298                 /* avoid an extra loop */
299                 if (found == TRUE) {
300                         BM_ITER_ELEM (f, &viter, v, BM_FACES_OF_VERT) {
301                                 if (!BMO_elem_flag_test(bm, f, EXT_INPUT)) {
302                                         found = TRUE;
303                                         break;
304                                 }
305                         }
306                 }
307
308                 if (found == FALSE) {
309                         BMO_elem_flag_enable(bm, v, EXT_DEL);
310                 }
311         }
312         
313         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
314                 if (BMO_elem_flag_test(bm, f, EXT_INPUT)) {
315                         BMO_elem_flag_enable(bm, f, EXT_DEL);
316                 }
317         }
318
319         if (delorig == TRUE) {
320                 BMO_op_initf(bm, &delop, "del geom=%fvef context=%i",
321                              EXT_DEL, DEL_ONLYTAGGED);
322         }
323
324         BMO_slot_copy(op, &dupeop, "edgefacein", "geom");
325         BMO_op_exec(bm, &dupeop);
326
327         if (bm->act_face && BMO_elem_flag_test(bm, bm->act_face, EXT_INPUT))
328                 bm->act_face = BMO_slot_map_ptr_get(bm, &dupeop, "facemap", bm->act_face);
329
330         if (delorig) {
331                 BMO_op_exec(bm, &delop);
332         }
333         
334         /* if not delorig, reverse loops of original face */
335         if (!delorig) {
336                 BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
337                         if (BMO_elem_flag_test(bm, f, EXT_INPUT)) {
338                                 BM_face_normal_flip(bm, f);
339                         }
340                 }
341         }
342         
343         BMO_slot_copy(&dupeop, op, "newout", "geomout");
344
345         for (e = BMO_iter_new(&siter, bm, &dupeop, "boundarymap", 0); e; e = BMO_iter_step(&siter)) {
346
347                 /* this should always be wire, so this is mainly a speedup to avoid map lookup */
348                 if (BM_edge_is_wire(e) && BMO_slot_map_contains(bm, op, "exclude", e)) {
349                         /* The original edge was excluded,
350                          * this would result in a standalone wire edge - see [#30399] */
351                         BM_edge_kill(bm, e);
352
353                         continue;
354                 }
355
356                 newedge = *(BMEdge **)BMO_iter_map_value(&siter);
357
358                 if (!newedge) {
359                         continue;
360                 }
361
362                 /* orient loop to give same normal as a loop of newedge
363                  * if it exists (will be an extruded face),
364                  * else same normal as a loop of e, if it exists */
365                 if (!newedge->l)
366                         fwd = !e->l || !(e->l->v == e->v1);
367                 else
368                         fwd = (newedge->l->v == newedge->v1);
369
370                 
371                 if (fwd) {
372                         verts[0] = e->v1;
373                         verts[1] = e->v2;
374                         verts[2] = newedge->v2;
375                         verts[3] = newedge->v1;
376                 }
377                 else {
378                         verts[3] = e->v1;
379                         verts[2] = e->v2;
380                         verts[1] = newedge->v2;
381                         verts[0] = newedge->v1;
382                 }
383
384                 /* not sure what to do about example face, pass NULL for now */
385                 f = BM_face_create_quad_tri_v(bm, verts, 4, NULL, FALSE);
386                 bm_extrude_copy_face_loop_attributes(bm, f, e, newedge);
387         }
388
389         /* link isolated vert */
390         for (v = BMO_iter_new(&siter, bm, &dupeop, "isovertmap", 0); v; v = BMO_iter_step(&siter)) {
391                 v2 = *((void **)BMO_iter_map_value(&siter));
392                 BM_edge_create(bm, v, v2, v->e, TRUE);
393         }
394
395         /* cleanup */
396         if (delorig) BMO_op_finish(bm, &delop);
397         BMO_op_finish(bm, &dupeop);
398 }
399
400 /*
401  * Compute higher-quality vertex normals used by solidify.
402  * Only considers geometry in the marked solidify region.
403  * Note that this does not work so well for non-manifold
404  * regions.
405  */
406 static void calc_solidify_normals(BMesh *bm)
407 {
408         BMIter viter, eiter, fiter;
409         BMVert *v;
410         BMEdge *e;
411         BMFace *f, *f1, *f2;
412         float edge_normal[3];
413         int i;
414
415         /* can't use BM_edge_face_count because we need to count only marked faces */
416         int *edge_face_count = MEM_callocN(sizeof(int) * bm->totedge, __func__);
417
418         BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
419                 BM_elem_flag_enable(v, BM_ELEM_TAG);
420         }
421
422         BM_mesh_elem_index_ensure(bm, BM_EDGE);
423
424         BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
425                 if (!BMO_elem_flag_test(bm, f, FACE_MARK)) {
426                         continue;
427                 }
428
429                 BM_ITER_ELEM (e, &eiter, f, BM_EDGES_OF_FACE) {
430
431                         /* And mark all edges and vertices on the
432                          * marked faces */
433                         BMO_elem_flag_enable(bm, e, EDGE_MARK);
434                         BMO_elem_flag_enable(bm, e->v1, VERT_MARK);
435                         BMO_elem_flag_enable(bm, e->v2, VERT_MARK);
436                         edge_face_count[BM_elem_index_get(e)]++;
437                 }
438         }
439
440         BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
441                 if (!BMO_elem_flag_test(bm, e, EDGE_MARK)) {
442                         continue;
443                 }
444
445                 i = edge_face_count[BM_elem_index_get(e)]++;
446
447                 if (i == 0 || i > 2) {
448                         /* Edge & vertices are non-manifold even when considering
449                          * only marked faces */
450                         BMO_elem_flag_enable(bm, e, EDGE_NONMAN);
451                         BMO_elem_flag_enable(bm, e->v1, VERT_NONMAN);
452                         BMO_elem_flag_enable(bm, e->v2, VERT_NONMAN);
453                 }
454         }
455         MEM_freeN(edge_face_count);
456         edge_face_count = NULL; /* don't re-use */
457
458         BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
459                 if (!BM_vert_is_manifold(v)) {
460                         BMO_elem_flag_enable(bm, v, VERT_NONMAN);
461                         continue;
462                 }
463
464                 if (BMO_elem_flag_test(bm, v, VERT_MARK)) {
465                         zero_v3(v->no);
466                 }
467         }
468
469         BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
470
471                 /* If the edge is not part of a the solidify region
472                  * its normal should not be considered */
473                 if (!BMO_elem_flag_test(bm, e, EDGE_MARK)) {
474                         continue;
475                 }
476
477                 /* If the edge joins more than two marked faces high
478                  * quality normal computation won't work */
479                 if (BMO_elem_flag_test(bm, e, EDGE_NONMAN)) {
480                         continue;
481                 }
482
483                 f1 = f2 = NULL;
484
485                 BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
486                         if (BMO_elem_flag_test(bm, f, FACE_MARK)) {
487                                 if (f1 == NULL) {
488                                         f1 = f;
489                                 }
490                                 else {
491                                         BLI_assert(f2 == NULL);
492                                         f2 = f;
493                                 }
494                         }
495                 }
496
497                 BLI_assert(f1 != NULL);
498
499                 if (f2 != NULL) {
500                         const float angle = angle_normalized_v3v3(f1->no, f2->no);
501
502                         if (angle > 0.0f) {
503                                 /* two faces using this edge, calculate the edge normal
504                                  * using the angle between the faces as a weighting */
505                                 add_v3_v3v3(edge_normal, f1->no, f2->no);
506                                 normalize_v3(edge_normal);
507                                 mul_v3_fl(edge_normal, angle);
508                         }
509                         else {
510                                 /* can't do anything useful here!
511                                  * Set the face index for a vert in case it gets a zero normal */
512                                 BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
513                                 BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
514                                 continue;
515                         }
516                 }
517                 else {
518                         /* only one face attached to that edge */
519                         /* an edge without another attached- the weight on this is
520                          * undefined, M_PI / 2 is 90d in radians and that seems good enough */
521                         copy_v3_v3(edge_normal, f1->no);
522                         mul_v3_fl(edge_normal, M_PI / 2);
523                 }
524
525                 add_v3_v3(e->v1->no, edge_normal);
526                 add_v3_v3(e->v2->no, edge_normal);
527         }
528
529         /* normalize accumulated vertex normal */
530         BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
531                 if (!BMO_elem_flag_test(bm, v, VERT_MARK)) {
532                         continue;
533                 }
534
535                 if (BMO_elem_flag_test(bm, v, VERT_NONMAN)) {
536                         /* use standard normals for vertices connected to non-manifold edges */
537                         BM_vert_normal_update(v);
538                 }
539                 else if (normalize_v3(v->no) == 0.0f && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
540                         /* exceptional case, totally flat. use the normal
541                          * of any marked face around the vertex */
542                         BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
543                                 if (BMO_elem_flag_test(bm, f, FACE_MARK)) {
544                                         break;
545                                 }
546                         }
547                         copy_v3_v3(v->no, f->no);
548                 }
549         }
550 }
551
552 static void solidify_add_thickness(BMesh *bm, const float dist)
553 {
554         BMFace *f;
555         BMVert *v;
556         BMLoop *l;
557         BMIter iter, loopIter;
558         float *vert_angles = MEM_callocN(sizeof(float) * bm->totvert * 2, "solidify"); /* 2 in 1 */
559         float *vert_accum = vert_angles + bm->totvert;
560         int i, index;
561
562         /* array for passing verts to angle_poly_v3 */
563         float **verts = NULL;
564         BLI_array_staticdeclare(verts, BM_NGON_STACK_SIZE);
565         /* array for receiving angles from angle_poly_v3 */
566         float *face_angles = NULL;
567         BLI_array_staticdeclare(face_angles, BM_NGON_STACK_SIZE);
568
569         BM_mesh_elem_index_ensure(bm, BM_VERT);
570
571         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
572                 if (!BMO_elem_flag_test(bm, f, FACE_MARK)) {
573                         continue;
574                 }
575
576                 BLI_array_growitems(verts, f->len);
577                 BM_ITER_ELEM_INDEX (l, &loopIter, f, BM_LOOPS_OF_FACE, i) {
578                         verts[i] = l->v->co;
579                 }
580
581                 BLI_array_growitems(face_angles, f->len);
582                 angle_poly_v3(face_angles, (const float **)verts, f->len);
583
584                 i = 0;
585                 BM_ITER_ELEM (l, &loopIter, f, BM_LOOPS_OF_FACE) {
586                         v = l->v;
587                         index = BM_elem_index_get(v);
588                         vert_accum[index] += face_angles[i];
589                         vert_angles[index] += shell_angle_to_dist(angle_normalized_v3v3(v->no, f->no)) * face_angles[i];
590                         i++;
591                 }
592
593                 BLI_array_empty(verts);
594                 BLI_array_empty(face_angles);
595         }
596
597         BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
598                 index = BM_elem_index_get(v);
599                 if (vert_accum[index]) { /* zero if unselected */
600                         madd_v3_v3fl(v->co, v->no, dist * (vert_angles[index] / vert_accum[index]));
601                 }
602         }
603
604         MEM_freeN(vert_angles);
605
606         BLI_array_free(verts);
607         BLI_array_free(face_angles);
608 }
609
610 void bmo_solidify_face_region_exec(BMesh *bm, BMOperator *op)
611 {
612         BMOperator extrudeop;
613         BMOperator reverseop;
614         float thickness;
615
616         thickness = BMO_slot_float_get(op, "thickness");
617
618         /* Flip original faces (so the shell is extruded inward) */
619         BMO_op_init(bm, &reverseop, "reversefaces");
620         BMO_slot_copy(op, &reverseop, "geom", "faces");
621         BMO_op_exec(bm, &reverseop);
622         BMO_op_finish(bm, &reverseop);
623
624         /* Extrude the region */
625         BMO_op_initf(bm, &extrudeop, "extrude_face_region alwayskeeporig=%b", TRUE);
626         BMO_slot_copy(op, &extrudeop, "geom", "edgefacein");
627         BMO_op_exec(bm, &extrudeop);
628
629         /* Push the verts of the extruded faces inward to create thickness */
630         BMO_slot_buffer_flag_enable(bm, &extrudeop, "geomout", BM_FACE, FACE_MARK);
631         calc_solidify_normals(bm);
632         solidify_add_thickness(bm, thickness);
633
634         BMO_slot_copy(&extrudeop, op, "geomout", "geomout");
635
636         BMO_op_finish(bm, &extrudeop);
637 }