svn merge -r40197:40311 ^/trunk/blender
[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;
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
359         //create custom data layer to save polygon idx
360         CustomData_add_layer_named(&em->fdata, CD_RECAST, CD_CALLOC, NULL, 0, "recastData");
361
362         //create verts and faces for detailed mesh
363         for (i=0; i<dmesh->nmeshes; i++)
364         {
365                 int uniquevbase = em->totvert;
366                 unsigned short vbase = dmesh->meshes[4*i+0];
367                 unsigned short ndv = dmesh->meshes[4*i+1];
368                 unsigned short tribase = dmesh->meshes[4*i+2];
369                 unsigned short trinum = dmesh->meshes[4*i+3];
370                 const unsigned short* p = &pmesh->polys[i*pmesh->nvp*2];
371                 int nv = 0;
372                 for (j = 0; j < pmesh->nvp; ++j)
373                 {
374                         if (p[j] == 0xffff) break;
375                         nv++;
376                 }
377                 //create unique verts 
378                 for (j=nv; j<ndv; j++)
379                 {
380                         copy_v3_v3(co, &dmesh->verts[3*(vbase + j)]);
381                         SWAP(float, co[1], co[2]);
382                         addvertlist(em, co, NULL);
383                 }
384
385                 EM_init_index_arrays(em, 1, 0, 0);
386                 
387                 //create faces
388                 for (j=0; j<trinum; j++)
389                 {
390                         unsigned char* tri = &dmesh->tris[4*(tribase+j)];
391                         EditFace* newFace;
392                         for (k=0; k<3; k++)
393                         {
394                                 if (tri[k]<nv)
395                                         face[k] = p[tri[k]]; //shared vertex
396                                 else
397                                         face[k] = uniquevbase+tri[k]-nv; //unique vertex
398                         }
399                         newFace = addfacelist(em, EM_get_vert_for_index(face[0]), EM_get_vert_for_index(face[2]), 
400                                                                         EM_get_vert_for_index(face[1]), NULL, NULL, NULL);
401
402                         //set navigation polygon idx to the custom layer
403                         int* polygonIdx = (int*)CustomData_em_get(&em->fdata, newFace->data, CD_RECAST);
404                         *polygonIdx = i+1; //add 1 to avoid zero idx
405                 }
406                 
407                 EM_free_index_arrays();
408         }
409
410         delete pmesh; pmesh = NULL;
411         delete dmesh; dmesh = NULL;
412
413         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
414         
415         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
416         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
417
418
419         ED_object_exit_editmode(C, EM_FREEDATA); 
420         WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, obedit);
421
422         if (createob)
423         {
424                 obedit->gameflag &= ~OB_COLLISION;
425                 obedit->gameflag |= OB_NAVMESH;
426                 obedit->body_type = OB_BODY_TYPE_NAVMESH;
427                 rename_id((ID *)obedit, "Navmesh");
428         }
429         
430         ModifierData *md= modifiers_findByType(obedit, eModifierType_NavMesh);
431         if (!md)
432         {
433                 ED_object_modifier_add(NULL, bmain, scene, obedit, NULL, eModifierType_NavMesh);
434         }
435
436         return obedit;
437 }
438
439 static int create_navmesh_exec(bContext *C, wmOperator *op)
440 {
441         Scene* scene = CTX_data_scene(C);
442         int nverts, ntris;
443         float* verts;
444         int* tris;
445         rcPolyMesh* pmesh;
446         rcPolyMeshDetail* dmesh;
447         LinkNode* obs = NULL;
448         Base* navmeshBase = NULL;
449         //CTX_DATA_BEGIN(C, Base*, base, selected_editable_bases) //expand macros to avoid error in convertion from void*
450         {
451                 ListBase ctx_data_list;
452                 CollectionPointerLink *ctx_link;
453                 CTX_data_selected_editable_bases(C, &ctx_data_list);
454                 for(ctx_link = (CollectionPointerLink *)ctx_data_list.first; 
455                                 ctx_link; ctx_link = (CollectionPointerLink *)ctx_link->next) {
456                 Base* base= (Base*)ctx_link->ptr.data;
457         {
458                 if (base->object->body_type==OB_BODY_TYPE_NAVMESH)
459                 {
460                         if (!navmeshBase || base==CTX_data_active_base(C))
461                                 navmeshBase = base;
462                 }
463                 else
464                         BLI_linklist_append(&obs, (void*)base->object);
465         }
466         CTX_DATA_END;
467         createVertsTrisData(C, obs, nverts, verts, ntris, tris);
468         BLI_linklist_free(obs, NULL);
469         buildNavMesh(scene->gm.recastData, nverts, verts, ntris, tris, pmesh, dmesh);
470         createRepresentation(C, pmesh, dmesh, navmeshBase);
471
472         return OPERATOR_FINISHED;
473 }
474
475 void OBJECT_OT_create_navmesh(wmOperatorType *ot)
476 {
477         /* identifiers */
478         ot->name= "Create navigation mesh";
479         ot->description= "Create navigation mesh for selected objects";
480         ot->idname= "OBJECT_OT_create_navmesh";
481
482         /* api callbacks */
483         ot->exec= create_navmesh_exec;
484
485         /* flags */
486         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
487 }
488
489 static int assign_navpolygon_poll(bContext *C)
490 {
491         Object *ob= (Object *)CTX_data_pointer_get_type(C, "object", &RNA_Object).data;
492         if (!ob || !ob->data)
493                 return 0;
494         return (((Mesh*)ob->data)->edit_mesh != NULL);
495 }
496
497 static int assign_navpolygon_exec(bContext *C, wmOperator *op)
498 {
499         Object *obedit= CTX_data_edit_object(C);
500         EditMesh *em= BKE_mesh_get_editmesh((Mesh *)obedit->data);
501
502         //do work here
503         int targetPolyIdx = -1;
504         EditFace *ef, *efa;
505         efa = EM_get_actFace(em, 0);
506         if (efa) 
507         {
508                 if (CustomData_has_layer(&em->fdata, CD_RECAST))
509                 {
510                         targetPolyIdx = *(int*)CustomData_em_get(&em->fdata, efa->data, CD_RECAST);
511                         targetPolyIdx = targetPolyIdx>=0? targetPolyIdx : -targetPolyIdx;
512                         if (targetPolyIdx>0)
513                         {
514                                 //set target poly idx to other selected faces
515                                 ef = (EditFace*)em->faces.last;
516                                 while(ef) 
517                                 {
518                                         if((ef->f & SELECT )&& ef!=efa) 
519                                         {
520                                                 int* recastDataBlock = (int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
521                                                 *recastDataBlock = targetPolyIdx;
522                                         }
523                                         ef = ef->prev;
524                                 }
525                         }
526                 }               
527         }
528         
529         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
530         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
531
532         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
533         return OPERATOR_FINISHED;
534 }
535
536 void OBJECT_OT_assign_navpolygon(struct wmOperatorType *ot)
537 {
538         /* identifiers */
539         ot->name= "Assign polygon index";
540         ot->description= "Assign polygon index to face by active face";
541         ot->idname= "OBJECT_OT_assign_navpolygon";
542
543         /* api callbacks */
544         ot->poll = assign_navpolygon_poll;
545         ot->exec= assign_navpolygon_exec;
546
547         /* flags */
548         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
549 }
550
551 static int compare(const void * a, const void * b){  
552         return ( *(int*)a - *(int*)b );
553 }
554 static int findFreeNavPolyIndex(EditMesh* em)
555 {
556         //construct vector of indices
557         int numfaces = em->totface;
558         int* indices = new int[numfaces];
559         EditFace* ef = (EditFace*)em->faces.last;
560         int idx = 0;
561         while(ef) 
562         {
563                 int polyIdx = *(int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
564                 indices[idx] = polyIdx;
565                 idx++;
566                 ef = ef->prev;
567         }
568         qsort(indices, numfaces, sizeof(int), compare);
569         //search first free index
570         int freeIdx = 1;
571         for (int i=0; i<numfaces; i++)
572         {
573                 if (indices[i]==freeIdx)
574                         freeIdx++;
575                 else if (indices[i]>freeIdx)
576                         break;
577         }
578         delete [] indices;
579         return freeIdx;
580 }
581
582 static int assign_new_navpolygon_exec(bContext *C, wmOperator *op)
583 {
584         Object *obedit= CTX_data_edit_object(C);
585         EditMesh *em= BKE_mesh_get_editmesh((Mesh *)obedit->data);
586
587         EditFace *ef;
588         if (CustomData_has_layer(&em->fdata, CD_RECAST))
589         {
590                 int targetPolyIdx = findFreeNavPolyIndex(em);
591                 if (targetPolyIdx>0)
592                 {
593                         //set target poly idx to selected faces
594                         ef = (EditFace*)em->faces.last;
595                         while(ef) 
596                         {
597                                 if(ef->f & SELECT ) 
598                                 {
599                                         int* recastDataBlock = (int*)CustomData_em_get(&em->fdata, ef->data, CD_RECAST);
600                                         *recastDataBlock = targetPolyIdx;
601                                 }
602                                 ef = ef->prev;
603                         }
604                 }
605         }               
606
607         DAG_id_tag_update((ID*)obedit->data, OB_RECALC_DATA);
608         WM_event_add_notifier(C, NC_GEOM|ND_DATA, obedit->data);
609
610         BKE_mesh_end_editmesh((Mesh*)obedit->data, em);
611         return OPERATOR_FINISHED;
612 }
613
614 void OBJECT_OT_assign_new_navpolygon(struct wmOperatorType *ot)
615 {
616         /* identifiers */
617         ot->name= "Assign new polygon index";
618         ot->description= "Assign new polygon index to face";
619         ot->idname= "OBJECT_OT_assign_new_navpolygon";
620
621         /* api callbacks */
622         ot->poll = assign_navpolygon_poll;
623         ot->exec= assign_new_navpolygon_exec;
624
625         /* flags */
626         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
627 }
628 }