Navigation Mesh Modifier:
[blender.git] / source / blender / editors / object / object_navmesh.cpp
1 /**
2 * $Id$
3 *
4 * ***** BEGIN GPL LICENSE BLOCK *****
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * The Original Code is Copyright (C) 2004 by Blender Foundation
21 * All rights reserved.
22 *
23 * The Original Code is: all of this file.
24 *
25 * Contributor(s): none yet.
26 *
27 * ***** END GPL LICENSE BLOCK *****
28 */
29
30 #include <math.h>
31 #include "Recast.h"
32
33 extern "C"
34 {
35 #include "MEM_guardedalloc.h"
36
37 #include "DNA_scene_types.h"
38 #include "DNA_object_types.h"
39 #include "DNA_meshdata_types.h"
40 #include "DNA_modifier_types.h"
41 #include "DNA_ID.h"
42
43 #include "BKE_library.h"
44 #include "BKE_depsgraph.h"
45 #include "BKE_context.h"
46 #include "BKE_mesh.h"
47 #include "BKE_modifier.h"
48 #include "BKE_scene.h"
49 #include "BKE_DerivedMesh.h"
50 #include "BKE_cdderivedmesh.h"
51 #include "BLI_editVert.h"
52 #include "BLI_listbase.h"
53 #include "BLI_utildefines.h"
54 #include "ED_object.h"
55 #include "BLI_math_vector.h"
56
57 #include "RNA_access.h"
58
59 #include "ED_mesh.h"
60
61 /*mesh/mesh_intern.h */
62 extern struct EditVert *addvertlist(EditMesh *em, float *vec, struct EditVert *example);
63 extern struct EditFace *addfacelist(EditMesh *em, struct EditVert *v1, struct EditVert *v2, struct EditVert *v3, struct EditVert *v4, struct EditFace *example, struct EditFace *exampleEdges);
64 extern void free_vertlist(EditMesh *em, ListBase *edve);
65 extern void free_edgelist(EditMesh *em, ListBase *lb);
66 extern void free_facelist(EditMesh *em, ListBase *lb);
67
68 #include "WM_api.h"
69 #include "WM_types.h"
70
71 static void createVertsTrisData(bContext *C, LinkNode* obs, int& nverts, float*& verts, int &ntris, int*& tris)
72 {
73         MVert *mvert;
74         int nfaces = 0, *tri, i, curnverts, basenverts, curnfaces;
75         MFace *mface;
76         float co[3], wco[3];
77         Object *ob;
78         LinkNode *oblink, *dmlink;
79         DerivedMesh *dm;
80         Scene* scene = CTX_data_scene(C);
81         LinkNode* dms = NULL;
82
83         nverts = 0;
84         ntris = 0;
85         //calculate number of verts and tris
86         for (oblink = obs; oblink; oblink = oblink->next) 
87         {
88                 ob = (Object*) oblink->link;    
89                 DerivedMesh *dm = mesh_create_derived_no_virtual(scene, ob, NULL, CD_MASK_MESH);
90                 BLI_linklist_append(&dms, (void*)dm);
91
92                 nverts += dm->getNumVerts(dm);
93                 nfaces = dm->getNumFaces(dm);
94                 ntris += nfaces;
95
96                 //resolve quad faces
97                 mface = dm->getFaceArray(dm);
98                 for (i=0; i<nfaces; i++)
99                 {
100                         MFace* mf = &mface[i];
101                         if (mf->v4)
102                                 ntris+=1;
103                 }
104         }
105
106         //create data
107         verts = (float*) MEM_mallocN(sizeof(float)*3*nverts, "verts");
108         tris = (int*) MEM_mallocN(sizeof(int)*3*ntris, "faces");
109
110         basenverts = 0;
111         tri = tris;
112         for (oblink = obs, dmlink = dms; oblink && dmlink; 
113                         oblink = oblink->next, dmlink = dmlink->next)
114         {
115                 ob = (Object*) oblink->link;
116                 dm = (DerivedMesh*) dmlink->link;
117
118                 curnverts = dm->getNumVerts(dm);
119                 mvert = dm->getVertArray(dm);
120                 //copy verts    
121                 for (i=0; i<curnverts; i++)
122                 {
123                         MVert *v = &mvert[i];
124                         copy_v3_v3(co, v->co);
125                         mul_v3_m4v3(wco, ob->obmat, co);
126                         verts[3*(basenverts+i)+0] = wco[0];
127                         verts[3*(basenverts+i)+1] = wco[2];
128                         verts[3*(basenverts+i)+2] = wco[1];
129                 }
130
131                 //create tris
132                 curnfaces = dm->getNumFaces(dm);
133                 mface = dm->getFaceArray(dm);
134                 for (i=0; i<curnfaces; i++)
135                 {
136                         MFace* mf = &mface[i]; 
137                         tri[0]= basenverts + mf->v1; tri[1]= basenverts + mf->v3;       tri[2]= basenverts + mf->v2; 
138                         tri += 3;
139                         if (mf->v4)
140                         {
141                                 tri[0]= basenverts + mf->v1; tri[1]= basenverts + mf->v4; tri[2]= basenverts + mf->v3; 
142                                 tri += 3;
143                         }
144                 }
145                 basenverts += curnverts;
146         }
147
148         //release derived mesh
149         for (dmlink = dms; dmlink; dmlink = dmlink->next)
150         {
151                 dm = (DerivedMesh*) dmlink->link;
152                 dm->release(dm);
153         }
154         BLI_linklist_free(dms, NULL);
155 }
156
157 static bool buildNavMesh(const RecastData& recastParams, int nverts, float* verts, int ntris, int* tris,
158                                                                  rcPolyMesh*& pmesh, rcPolyMeshDetail*& dmesh)
159 {
160         float bmin[3], bmax[3];
161         rcHeightfield* solid;
162         unsigned char *triflags;
163         rcCompactHeightfield* chf;
164         rcContourSet *cset;
165
166         rcCalcBounds(verts, nverts, bmin, bmax);
167
168         //
169         // Step 1. Initialize build config.
170         //
171         rcConfig cfg;
172         memset(&cfg, 0, sizeof(cfg));
173         {
174 /*
175                 float cellsize = 0.3f;
176                 float cellheight = 0.2f;
177                 float agentmaxslope = M_PI/4;
178                 float agentmaxclimb = 0.9f;
179                 float agentheight = 2.0f;
180                 float agentradius = 0.6f;
181                 float edgemaxlen = 12.0f;
182                 float edgemaxerror = 1.3f;
183                 float regionminsize = 50.f;
184                 float regionmergesize = 20.f;
185                 int vertsperpoly = 6;
186                 float detailsampledist = 6.0f;
187                 float detailsamplemaxerror = 1.0f;
188                 cfg.cs = cellsize;
189                 cfg.ch = cellheight;
190                 cfg.walkableSlopeAngle = agentmaxslope/M_PI*180.f;
191                 cfg.walkableHeight = (int)ceilf(agentheight/ cfg.ch);
192                 cfg.walkableClimb = (int)floorf(agentmaxclimb / cfg.ch);
193                 cfg.walkableRadius = (int)ceilf(agentradius / cfg.cs);
194                 cfg.maxEdgeLen = (int)(edgemaxlen/cellsize);
195                 cfg.maxSimplificationError = edgemaxerror;
196                 cfg.minRegionSize = (int)rcSqr(regionminsize);
197                 cfg.mergeRegionSize = (int)rcSqr(regionmergesize);
198                 cfg.maxVertsPerPoly = vertsperpoly;
199                 cfg.detailSampleDist = detailsampledist< 0.9f ? 0 : cellsize * detailsampledist;
200                 cfg.detailSampleMaxError = cellheight * detailsamplemaxerror;
201 */
202                 cfg.cs = recastParams.cellsize;
203                 cfg.ch = recastParams.cellheight;
204                 cfg.walkableSlopeAngle = recastParams.agentmaxslope/((float)M_PI)*180.f;
205                 cfg.walkableHeight = (int)ceilf(recastParams.agentheight/ cfg.ch);
206                 cfg.walkableClimb = (int)floorf(recastParams.agentmaxclimb / cfg.ch);
207                 cfg.walkableRadius = (int)ceilf(recastParams.agentradius / cfg.cs);
208                 cfg.maxEdgeLen = (int)(recastParams.edgemaxlen/recastParams.cellsize);
209                 cfg.maxSimplificationError = recastParams.edgemaxerror;
210                 cfg.minRegionSize = (int)rcSqr(recastParams.regionminsize);
211                 cfg.mergeRegionSize = (int)rcSqr(recastParams.regionmergesize);
212                 cfg.maxVertsPerPoly = recastParams.vertsperpoly;
213                 cfg.detailSampleDist = recastParams.detailsampledist< 0.9f ? 0 : 
214                                                                 recastParams.cellsize * recastParams.detailsampledist;
215                 cfg.detailSampleMaxError = recastParams.cellheight * recastParams.detailsamplemaxerror;
216
217         }
218
219         // Set the area where the navigation will be build.
220         vcopy(cfg.bmin, bmin);
221         vcopy(cfg.bmax, bmax);
222         rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
223
224         //
225         // Step 2. Rasterize input polygon soup.
226         //
227         // Allocate voxel heightfield where we rasterize our input data to.
228         solid = new rcHeightfield;
229         if (!solid)
230                 return false;
231
232         if (!rcCreateHeightfield(*solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch))
233                 return false;
234
235         // Allocate array that can hold triangle flags.
236         triflags = (unsigned char*) MEM_mallocN(sizeof(unsigned char)*ntris, "triflags");
237         if (!triflags)
238                 return false;
239         // Find triangles which are walkable based on their slope and rasterize them.
240         memset(triflags, 0, ntris*sizeof(unsigned char));
241         rcMarkWalkableTriangles(cfg.walkableSlopeAngle, verts, nverts, tris, ntris, triflags);
242         rcRasterizeTriangles(verts, nverts, tris, triflags, ntris, *solid);
243         MEM_freeN(triflags);
244         MEM_freeN(verts);
245         MEM_freeN(tris);
246
247         //
248         // Step 3. Filter walkables surfaces.
249         //
250         rcFilterLedgeSpans(cfg.walkableHeight, cfg.walkableClimb, *solid);
251         rcFilterWalkableLowHeightSpans(cfg.walkableHeight, *solid);
252
253         //
254         // Step 4. Partition walkable surface to simple regions.
255         //
256
257         chf = new rcCompactHeightfield;
258         if (!chf)
259                 return false;
260         if (!rcBuildCompactHeightfield(cfg.walkableHeight, cfg.walkableClimb, RC_WALKABLE, *solid, *chf))
261                 return false;
262
263         delete solid; 
264
265         // Prepare for region partitioning, by calculating distance field along the walkable surface.
266         if (!rcBuildDistanceField(*chf))
267                 return false;
268
269         // Partition the walkable surface into simple regions without holes.
270         if (!rcBuildRegions(*chf, cfg.walkableRadius, cfg.borderSize, cfg.minRegionSize, cfg.mergeRegionSize))
271                 return false;
272
273         //
274         // Step 5. Trace and simplify region contours.
275         //
276         // Create contours.
277         cset = new rcContourSet;
278         if (!cset)
279                 return false;
280
281         if (!rcBuildContours(*chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset))
282                 return false;
283
284         //
285         // Step 6. Build polygons mesh from contours.
286         //
287         pmesh = new rcPolyMesh;
288         if (!pmesh)
289                 return false;
290         if (!rcBuildPolyMesh(*cset, cfg.maxVertsPerPoly, *pmesh))
291                 return false;
292
293
294         //
295         // Step 7. Create detail mesh which allows to access approximate height on each polygon.
296         //
297
298         dmesh = new rcPolyMeshDetail;
299         if (!dmesh)
300                 return false;
301
302         if (!rcBuildPolyMeshDetail(*pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh))
303                 return false;
304
305         delete chf;
306         delete cset;
307
308         return true;
309 }
310
311 static Object* createRepresentation(bContext *C, rcPolyMesh*& pmesh, rcPolyMeshDetail*& dmesh, Base* base)
312 {
313         float co[3], rot[3];
314         EditMesh *em;
315         int i,j, k, polyverts;
316         unsigned short* v;
317         int face[3];
318         Main *bmain = CTX_data_main(C);
319         Scene *scene= CTX_data_scene(C);
320         Object* obedit;
321         int createob = base==NULL;
322         zero_v3(co);
323         zero_v3(rot);
324         if (createob)
325         {
326                 //create new object
327                 obedit = ED_object_add_type(C, OB_MESH, co, rot, FALSE, 1);
328         }
329         else
330         {
331                 obedit = base->object;
332                 scene_select_base(scene, base);
333                 copy_v3_v3(obedit->loc, co);
334                 copy_v3_v3(obedit->rot, rot);
335         }
336
337         ED_object_enter_editmode(C, EM_DO_UNDO|EM_IGNORE_LAYER);
338         em = BKE_mesh_get_editmesh(((Mesh *)obedit->data));
339
340         if (!createob)
341         {
342                 //clear
343                 if(em->verts.first) free_vertlist(em, &em->verts);
344                 if(em->edges.first) free_edgelist(em, &em->edges);
345                 if(em->faces.first) free_facelist(em, &em->faces);
346                 if(em->selected.first) BLI_freelistN(&(em->selected));
347         }
348
349         //create verts for polygon mesh
350         for(i = 0; i < pmesh->nverts; i++) {
351                 v = &pmesh->verts[3*i];
352                 co[0] = pmesh->bmin[0] + v[0]*pmesh->cs;
353                 co[1] = pmesh->bmin[1] + v[1]*pmesh->ch;
354                 co[2] = pmesh->bmin[2] + v[2]*pmesh->cs;
355                 SWAP(float, co[1], co[2]);
356                 addvertlist(em, co, NULL);
357         }
358         polyverts = pmesh->nverts;
359
360         //create custom data layer to save polygon idx
361         CustomData_add_layer_named(&em->fdata, CD_RECAST, CD_CALLOC, NULL, 0, "recastData");
362
363         //create verts and faces for detailed mesh
364         for (i=0; i<dmesh->nmeshes; i++)
365         {
366                 int uniquevbase = em->totvert;
367                 unsigned short vbase = dmesh->meshes[4*i+0];
368                 unsigned short ndv = dmesh->meshes[4*i+1];
369                 unsigned short tribase = dmesh->meshes[4*i+2];
370                 unsigned short trinum = dmesh->meshes[4*i+3];
371                 const unsigned short* p = &pmesh->polys[i*pmesh->nvp*2];
372                 int nv = 0;
373                 for (j = 0; j < pmesh->nvp; ++j)
374                 {
375                         if (p[j] == 0xffff) break;
376                         nv++;
377                 }
378                 //create unique verts 
379                 for (j=nv; j<ndv; j++)
380                 {
381                         copy_v3_v3(co, &dmesh->verts[3*(vbase + j)]);
382                         SWAP(float, co[1], co[2]);
383                         addvertlist(em, co, NULL);
384                 }
385
386                 EM_init_index_arrays(em, 1, 0, 0);
387                 
388                 //create faces
389                 for (j=0; j<trinum; j++)
390                 {
391                         unsigned char* tri = &dmesh->tris[4*(tribase+j)];
392                         EditFace* newFace;
393                         for (k=0; k<3; k++)
394                         {
395                                 if (tri[k]<nv)
396                                         face[k] = p[tri[k]]; //shared vertex
397                                 else
398                                         face[k] = uniquevbase+tri[k]-nv; //unique vertex
399                         }
400                         newFace = addfacelist(em, EM_get_vert_for_index(face[0]), EM_get_vert_for_index(face[2]), 
401                                                                         EM_get_vert_for_index(face[1]), NULL, NULL, NULL);
402
403                         //set navigation polygon idx to the custom layer
404                         int* polygonIdx = (int*)CustomData_em_get(&em->fdata, newFace->data, CD_RECAST);
405                         *polygonIdx = i+1; //add 1 to avoid zero idx
406                 }
407                 
408                 EM_free_index_arrays();
409         }
410
411         delete pmesh; pmesh = NULL;
412         delete dmesh; dmesh = NULL;
413
414         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
415         
416         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
417         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
418
419
420         ED_object_exit_editmode(C, EM_FREEDATA); 
421         WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, obedit);
422
423         if (createob)
424         {
425                 obedit->gameflag &= ~OB_COLLISION;
426                 obedit->gameflag |= OB_NAVMESH;
427                 obedit->body_type = OB_BODY_TYPE_NAVMESH;
428                 rename_id((ID *)obedit, "Navmesh");
429         }
430         
431         ModifierData *md= modifiers_findByType(obedit, eModifierType_NavMesh);
432         if (!md)
433         {
434                 ED_object_modifier_add(NULL, bmain, scene, obedit, NULL, eModifierType_NavMesh);
435         }
436
437         return obedit;
438 }
439
440 static int create_navmesh_exec(bContext *C, wmOperator *op)
441 {
442         Scene* scene = CTX_data_scene(C);
443         int nverts, ntris;
444         float* verts;
445         int* tris;
446         rcPolyMesh* pmesh;
447         rcPolyMeshDetail* dmesh;
448         LinkNode* obs = NULL;
449         Base* navmeshBase = NULL;
450         //CTX_DATA_BEGIN(C, Base*, base, selected_editable_bases) //expand macros to avoid error in convertion from void*
451         {
452                 ListBase ctx_data_list;
453                 CollectionPointerLink *ctx_link;
454                 CTX_data_selected_editable_bases(C, &ctx_data_list);
455                 for(ctx_link = (CollectionPointerLink *)ctx_data_list.first; 
456                                 ctx_link; ctx_link = (CollectionPointerLink *)ctx_link->next) {
457                 Base* base= (Base*)ctx_link->ptr.data;
458         {
459                 if (base->object->body_type==OB_BODY_TYPE_NAVMESH)
460                 {
461                         if (!navmeshBase || base==CTX_data_active_base(C))
462                                 navmeshBase = base;
463                 }
464                 else
465                         BLI_linklist_append(&obs, (void*)base->object);
466         }
467         CTX_DATA_END;
468         createVertsTrisData(C, obs, nverts, verts, ntris, tris);
469         BLI_linklist_free(obs, NULL);
470         buildNavMesh(scene->gm.recastData, nverts, verts, ntris, tris, pmesh, dmesh);
471         createRepresentation(C, pmesh, dmesh, navmeshBase);
472
473         return OPERATOR_FINISHED;
474 }
475
476 void OBJECT_OT_create_navmesh(wmOperatorType *ot)
477 {
478         /* identifiers */
479         ot->name= "Create navigation mesh";
480         ot->description= "Create navigation mesh for selected objects";
481         ot->idname= "OBJECT_OT_create_navmesh";
482
483         /* api callbacks */
484         ot->exec= create_navmesh_exec;
485
486         /* flags */
487         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
488 }
489
490 static int assign_navpolygon_poll(bContext *C)
491 {
492         Object *ob= (Object *)CTX_data_pointer_get_type(C, "object", &RNA_Object).data;
493         if (!ob || !ob->data)
494                 return 0;
495         return (((Mesh*)ob->data)->edit_mesh != NULL);
496 }
497
498 static int assign_navpolygon_exec(bContext *C, wmOperator *op)
499 {
500         Object *obedit= CTX_data_edit_object(C);
501         EditMesh *em= BKE_mesh_get_editmesh((Mesh *)obedit->data);
502
503         //do work here
504         int targetPolyIdx = -1;
505         EditFace *ef, *efa;
506         efa = EM_get_actFace(em, 0);
507         if (efa) 
508         {
509                 if (CustomData_has_layer(&em->fdata, CD_RECAST))
510                 {
511                         targetPolyIdx = *(int*)CustomData_em_get(&em->fdata, efa->data, CD_RECAST);
512                         targetPolyIdx = targetPolyIdx>=0? targetPolyIdx : -targetPolyIdx;
513                         if (targetPolyIdx>0)
514                         {
515                                 //set target poly idx to other selected faces
516                                 ef = (EditFace*)em->faces.last;
517                                 while(ef) 
518                                 {
519                                         if((ef->f & SELECT )&& ef!=efa) 
520                                         {
521                                                 int* recastDataBlock = (int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
522                                                 *recastDataBlock = targetPolyIdx;
523                                         }
524                                         ef = ef->prev;
525                                 }
526                         }
527                 }               
528         }
529         
530         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
531         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
532
533         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
534         return OPERATOR_FINISHED;
535 }
536
537 void OBJECT_OT_assign_navpolygon(struct wmOperatorType *ot)
538 {
539         /* identifiers */
540         ot->name= "Assign polygon index";
541         ot->description= "Assign polygon index to face by active face";
542         ot->idname= "OBJECT_OT_assign_navpolygon";
543
544         /* api callbacks */
545         ot->poll = assign_navpolygon_poll;
546         ot->exec= assign_navpolygon_exec;
547
548         /* flags */
549         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
550 }
551
552 static int compare(const void * a, const void * b){  
553         return ( *(int*)a - *(int*)b );
554 }
555 static int findFreeNavPolyIndex(EditMesh* em)
556 {
557         //construct vector of indices
558         int numfaces = em->totface;
559         int* indices = new int[numfaces];
560         EditFace* ef = (EditFace*)em->faces.last;
561         int idx = 0;
562         while(ef) 
563         {
564                 int polyIdx = *(int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
565                 indices[idx] = polyIdx;
566                 idx++;
567                 ef = ef->prev;
568         }
569         qsort(indices, numfaces, sizeof(int), compare);
570         //search first free index
571         int freeIdx = 1;
572         for (int i=0; i<numfaces; i++)
573         {
574                 if (indices[i]==freeIdx)
575                         freeIdx++;
576                 else if (indices[i]>freeIdx)
577                         break;
578         }
579         delete indices;
580         return freeIdx;
581 }
582
583 static int assign_new_navpolygon_exec(bContext *C, wmOperator *op)
584 {
585         Object *obedit= CTX_data_edit_object(C);
586         EditMesh *em= BKE_mesh_get_editmesh((Mesh *)obedit->data);
587
588         EditFace *ef;
589         if (CustomData_has_layer(&em->fdata, CD_RECAST))
590         {
591                 int targetPolyIdx = findFreeNavPolyIndex(em);
592                 if (targetPolyIdx>0)
593                 {
594                         //set target poly idx to selected faces
595                         ef = (EditFace*)em->faces.last;
596                         while(ef) 
597                         {
598                                 if(ef->f & SELECT ) 
599                                 {
600                                         int* recastDataBlock = (int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
601                                         *recastDataBlock = targetPolyIdx;
602                                 }
603                                 ef = ef->prev;
604                         }
605                 }
606         }               
607
608         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
609         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
610
611         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
612         return OPERATOR_FINISHED;
613 }
614
615 void OBJECT_OT_assign_new_navpolygon(struct wmOperatorType *ot)
616 {
617         /* identifiers */
618         ot->name= "Assign new polygon index";
619         ot->description= "Assign new polygon index to face";
620         ot->idname= "OBJECT_OT_assign_new_navpolygon";
621
622         /* api callbacks */
623         ot->poll = assign_navpolygon_poll;
624         ot->exec= assign_new_navpolygon_exec;
625
626         /* flags */
627         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
628 }
629 }