|  | @@ -212,11 +212,11 @@ int OPINV_Select(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |      if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "toggle")){
 | 
	
		
			
				|  |  |          if(mo && mo->Base.Type==TNS_OBJECT_MESH && mo->Mode==TNS_MESH_EDIT_MODE){
 | 
	
		
			
				|  |  |              if(tnsMMeshAnySelected(mo)) tnsMMeshDeselectAll(mo); else tnsMMeshSelectAll(mo);
 | 
	
		
			
				|  |  | -            tnsInvaliateMeshBatch(mo);
 | 
	
		
			
				|  |  | +            tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  |          }else{
 | 
	
		
			
				|  |  |              if(tnsAnyObjectsSelected(root)) tnsDeselectAllObjects(root); else tnsSelectAllObjects(root);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world");
 | 
	
		
			
				|  |  | +        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world", "Toggle selection",mo->Mode==TNS_MESH_EDIT_MODE?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM);
 | 
	
		
			
				|  |  |          return LA_FINISHED;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      
 | 
	
	
		
			
				|  | @@ -235,8 +235,8 @@ int OPINV_Select(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |          int id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH)-1;
 | 
	
		
			
				|  |  |          void* p; if(id>=0 && id<sd->next){ p=sd->Refs[id]; }
 | 
	
		
			
				|  |  |          la_DoMeshSelect(mo, p, ex->SelectMode, DeselectAll, 1, 1); tnsMMeshEnsureSelection(mo,ex->SelectMode);
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(mo);
 | 
	
		
			
				|  |  | -        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world");
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  | +        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Mesh selection",TNS_HINT_GEOMETRY);
 | 
	
		
			
				|  |  |      }else{
 | 
	
		
			
				|  |  |          la_PopulateSelectDataObjects(sd,root,ex->ViewingCamera);
 | 
	
		
			
				|  |  |          if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "box")){
 | 
	
	
		
			
				|  | @@ -247,7 +247,7 @@ int OPINV_Select(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |          int id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH*2);
 | 
	
		
			
				|  |  |          if(id && id<sd->next){ la_DoObjectSelect(root, sd->Refs[id], ex, DeselectAll, 1, 1);  }
 | 
	
		
			
				|  |  |          else{ la_DoObjectSelect(root, 0, ex, DeselectAll, 1, 1); }
 | 
	
		
			
				|  |  | -        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world");
 | 
	
		
			
				|  |  | +        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Object selection",TNS_HINT_TRANSFORM);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      la_FreeSelectData(sd);
 | 
	
	
		
			
				|  | @@ -280,7 +280,7 @@ int OPMOD_Select(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |                  la_DoMeshSelect(mo, p, ex->SelectMode, 0, !Remove, 0);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              tnsMMeshEnsureSelection(mo,ex->SelectMode);
 | 
	
		
			
				|  |  | -            tnsInvaliateMeshBatch(mo);
 | 
	
		
			
				|  |  | +            tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  |          }else{
 | 
	
		
			
				|  |  |              la_DoObjectSelect(se->root, 0, ex, DeselectAll, 0, 0);
 | 
	
		
			
				|  |  |              int len; int* ids=la_SelectGetBox(se->sd, ex->ClickedX, ex->ClickedY, e->x-ui->L, e->y-ui->U, &len);
 | 
	
	
		
			
				|  | @@ -288,7 +288,7 @@ int OPMOD_Select(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |                  int id=ids[i]; if(id && id<se->sd->next){ la_DoObjectSelect(se->root, se->sd->Refs[id], ex, 0, !Remove, 0);  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world");
 | 
	
		
			
				|  |  | +        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Box selection",mo->Mode==TNS_MESH_EDIT_MODE?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM);
 | 
	
		
			
				|  |  |          ex->DrawCursor=0;
 | 
	
		
			
				|  |  |          la_FreeSelectData(se->sd);
 | 
	
		
			
				|  |  |          laRedrawCurrentPanel();
 | 
	
	
		
			
				|  | @@ -326,7 +326,7 @@ STRUCTURE(MTransformData){
 | 
	
		
			
				|  |  |      void* Originals; int next,max;
 | 
	
		
			
				|  |  |      int mode;
 | 
	
		
			
				|  |  |      int LockAxis[3];
 | 
	
		
			
				|  |  | -    int Local;
 | 
	
		
			
				|  |  | +    int UseLocal;
 | 
	
		
			
				|  |  |      real DeltaVal, UserDeltaVal;
 | 
	
		
			
				|  |  |      laStringEdit* Entry; int UseUserDelta;
 | 
	
		
			
				|  |  |  };
 | 
	
	
		
			
				|  | @@ -400,9 +400,9 @@ void la_ApplyTranslation(MTransformData* td, int x, int y){
 | 
	
		
			
				|  |  |      if(td->LockAxis[0]>0){ use_delta[1]=use_delta[2]=0; real l=fabs(use_delta[0]); use_delta[0]=l?use_delta[0]*len/l:1e-7; }
 | 
	
		
			
				|  |  |      if(td->LockAxis[1]>0){ use_delta[0]=use_delta[2]=0; real l=fabs(use_delta[1]); use_delta[1]=l?use_delta[1]*len/l:1e-7; }
 | 
	
		
			
				|  |  |      if(td->LockAxis[2]>0){ use_delta[0]=use_delta[1]=0; real l=fabs(use_delta[2]); use_delta[2]=l?use_delta[2]*len/l:1e-7; }
 | 
	
		
			
				|  |  | -    if(td->LockAxis[0]<0){ use_delta[0]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l:0); }
 | 
	
		
			
				|  |  | -    if(td->LockAxis[1]<0){ use_delta[1]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l:0); }
 | 
	
		
			
				|  |  | -    if(td->LockAxis[2]<0){ use_delta[2]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l:0); }
 | 
	
		
			
				|  |  | +    if(td->LockAxis[0]<0){ use_delta[0]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
 | 
	
		
			
				|  |  | +    if(td->LockAxis[1]<0){ use_delta[1]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
 | 
	
		
			
				|  |  | +    if(td->LockAxis[2]<0){ use_delta[2]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
 | 
	
		
			
				|  |  |      td->DeltaVal=tnsLength3d(use_delta);
 | 
	
		
			
				|  |  |      if(td->UseUserDelta){
 | 
	
		
			
				|  |  |          tnsVectorMultiSelf3d(use_delta,1/tnsLength3d(use_delta)*td->UserDeltaVal);
 | 
	
	
		
			
				|  | @@ -414,14 +414,15 @@ void la_ApplyTranslation(MTransformData* td, int x, int y){
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){
 | 
	
		
			
				|  |  |              MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject)); memcpy(to->o->GlobalTransform, to->Global,sizeof(tnsMatrix44d));
 | 
	
		
			
				|  |  |              if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
 | 
	
		
			
				|  |  | -            tnsGlobalMatrixChanged(to->o, 0); tnsMoveObjectGlobal(to->o, LA_COLOR3(use_delta));
 | 
	
		
			
				|  |  | +            tnsGlobalMatrixChanged(to->o, 0);
 | 
	
		
			
				|  |  | +            if(td->UseLocal) tnsMoveObjectLocal(to->o, LA_COLOR3(use_delta)); else tnsMoveObjectGlobal(to->o, LA_COLOR3(use_delta));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }else{
 | 
	
		
			
				|  |  |          tnsMakeTranslationMatrix44d(trans, LA_COLOR3(use_delta));
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
 | 
	
		
			
				|  |  | -            tnsApplyTransform43d(gp, trans, to->p); tnsApplyTransform43d(to->mv->p, td->obmatinv, gp);
 | 
	
		
			
				|  |  | +            tnsApplyTransform43d(gp, trans, to->p); if(!td->UseLocal) tnsApplyTransform43d(to->mv->p, td->obmatinv, gp); else tnsVectorCopy3d(gp, to->mv->p);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(td->mo);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(td->mo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void la_ApplyScale(MTransformData* td, int uix, int uiy){
 | 
	
	
		
			
				|  | @@ -441,7 +442,7 @@ void la_ApplyScale(MTransformData* td, int uix, int uiy){
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
 | 
	
		
			
				|  |  |              tnsApplyTransform43d(gp, final, to->p); tnsApplyTransform43d(to->mv->p, td->obmatinv, gp);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(td->mo);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(td->mo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void la_ApplyRotation(MTransformData* td, int uix, int uiy){
 | 
	
	
		
			
				|  | @@ -465,7 +466,7 @@ void la_ApplyRotation(MTransformData* td, int uix, int uiy){
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
 | 
	
		
			
				|  |  |              tnsApplyTransform43d(gp, final, to->p); tnsApplyTransform43d(to->mv->p, td->obmatinv, gp);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(td->mo);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(td->mo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void la_CancelTransformObjects(MTransformData* td){
 | 
	
	
		
			
				|  | @@ -477,17 +478,18 @@ void la_CancelTransformObjects(MTransformData* td){
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }else{
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert)); tnsVectorCopy3d(to->origp,to->mv->p); }
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(td->mo);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(td->mo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void la_RecordTransformDifferences(MTransformData* td){
 | 
	
		
			
				|  |  |      if(!td->mo){
 | 
	
		
			
				|  |  |          for(int i=0;i<td->next;i++){ MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject));
 | 
	
		
			
				|  |  |              laRecordInstanceDifferences(to->o, "tns_object");
 | 
	
		
			
				|  |  | -        } laPushDifferences("Object Transformation", TNS_HINT_TRANSFORM);
 | 
	
		
			
				|  |  | +        } laPushDifferences(td->mode==LA_TRANSFORM_MODE_GRAB?"Moved objects":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotated objects":"Scaled objects", TNS_HINT_TRANSFORM);
 | 
	
		
			
				|  |  |      }else{
 | 
	
		
			
				|  |  | -        laRecordInstanceDifferences(td->mo, "tns_mesh_object"); laPushDifferences("Mesh transformation", TNS_HINT_GEOMETRY);
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(td->mo);
 | 
	
		
			
				|  |  | +        laRecordInstanceDifferences(td->mo, "tns_mesh_object");
 | 
	
		
			
				|  |  | +        laPushDifferences(td->mode==LA_TRANSFORM_MODE_GRAB?"Moved primitives":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotated primitives":"Scaled primitives", TNS_HINT_GEOMETRY);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(td->mo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void la_FreeTransformData(MTransformData* td){
 | 
	
	
		
			
				|  | @@ -500,8 +502,9 @@ void la_MakeTransformOperatorHint(laOperator* a, MTransformData* td){
 | 
	
		
			
				|  |  |      strSafePrint(&a->RuntimeHint, "%s  ",
 | 
	
		
			
				|  |  |          td->mode==LA_TRANSFORM_MODE_GRAB?"Grab":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotate":td->mode==LA_TRANSFORM_MODE_SCALE?"Scale":"");
 | 
	
		
			
				|  |  |      char* entry=strGetEditString(td->Entry,0);
 | 
	
		
			
				|  |  | -    strSafePrint(&a->RuntimeHint, "Delta: %.3lf [🔢 %s]  🆇🆈🆉 Lock axis: %s  ", td->DeltaVal, (entry&&entry[0])?entry:"Type...",
 | 
	
		
			
				|  |  | -        td->LockAxis[0]?"X":td->LockAxis[1]?"Y":td->LockAxis[2]?"Z":"None");
 | 
	
		
			
				|  |  | +    strSafePrint(&a->RuntimeHint, "Delta: %.3lf [🔢 %s]  🆇🆈🆉 Lock axis: %s  🈳 %s  ", td->DeltaVal, (entry&&entry[0])?entry:"Type...",
 | 
	
		
			
				|  |  | +        td->LockAxis[0]?"X":td->LockAxis[1]?"Y":td->LockAxis[2]?"Z":"None",
 | 
	
		
			
				|  |  | +        td->UseLocal?"Global/[Local]":"[Global]/Local");
 | 
	
		
			
				|  |  |      free(entry);
 | 
	
		
			
				|  |  |      if(td->mode==LA_TRANSFORM_MODE_GRAB){ strSafePrint(&a->RuntimeHint, "🡅🆇🆈🆉 Reverse: %s  ",
 | 
	
		
			
				|  |  |          (td->LockAxis[0]<0||td->LockAxis[1]<0||td->LockAxis[2]<0)?"Yes":"No"); }
 | 
	
	
		
			
				|  | @@ -554,7 +557,7 @@ int OPMOD_Transformation(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |      MTransformData* td=a->CustomData;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (e->Input=='x'||e->Input=='y'||e->Input=='z'||e->Input=='g'||e->Input=='s'||e->Input=='r'||
 | 
	
		
			
				|  |  | -        e->Input=='X'||e->Input=='Y'||e->Input=='Z'){ /*pass*/ }
 | 
	
		
			
				|  |  | +        e->Input=='X'||e->Input=='Y'||e->Input=='Z'||e->Input==' '){ /*pass*/ }
 | 
	
		
			
				|  |  |      else{ la_ProcessTextEdit(e, td->Entry, 0); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      char* entered;
 | 
	
	
		
			
				|  | @@ -571,6 +574,7 @@ int OPMOD_Transformation(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |          if(e->key=='g' && td->mode!=LA_TRANSFORM_MODE_GRAB){ td->mode=LA_TRANSFORM_MODE_GRAB; ex->DrawCursor=0; }
 | 
	
		
			
				|  |  |          if(e->key=='s' && td->mode!=LA_TRANSFORM_MODE_SCALE){ td->mode=LA_TRANSFORM_MODE_SCALE; la_GetTransformInitialScale(td,ui,e->x,e->y);ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
 | 
	
		
			
				|  |  |          if(e->key=='r' && td->mode!=LA_TRANSFORM_MODE_ROTATE){ td->mode=LA_TRANSFORM_MODE_ROTATE; la_GetTransformInitialRotation(td,ui,e->x,e->y);ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
 | 
	
		
			
				|  |  | +        if(e->key==' '){ td->UseLocal=!td->UseLocal; }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if(e->Type==LA_MOUSEMOVE || e->Type==LA_KEY_DOWN){
 | 
	
	
		
			
				|  | @@ -765,22 +769,27 @@ void la_ExtrudeMakeDuplication(MExtrudeExtra* ee){
 | 
	
		
			
				|  |  |          ee->df[i].omf->flags&=(~TNS_MESH_FLAG_SELECTED);
 | 
	
		
			
				|  |  |          ee->df[i].nmf->flags|=TNS_MESH_FLAG_SELECTED; }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -void la_ReconnectFaces(MExtrudeExtra* ee){
 | 
	
		
			
				|  |  | +void la_RemoveOriginalFaces(MExtrudeExtra* ee){
 | 
	
		
			
				|  |  |      tnsMeshObject* mo=ee->mo;
 | 
	
		
			
				|  |  |      if(ee->RemoveOriginalFaces){
 | 
	
		
			
				|  |  |          for(int i=0;i<ee->nextf;i++){ tnsMMeshRemoveFaceOnly(mo, ee->df[i].omf); }
 | 
	
		
			
				|  |  |          for(int i=0;i<ee->nexte;i++){ if(ee->de[i].IsBorder) continue; tnsMMeshRemoveEdgeFace(mo, ee->de[i].ome); }
 | 
	
		
			
				|  |  |          for(int i=0;i<ee->nextv;i++){ if(ee->dv[i].IsBorder) continue; tnsMMeshRemoveVertEdgeFace(mo, ee->dv[i].omv); }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void la_ReconnectFaces(MExtrudeExtra* ee){
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=ee->mo;
 | 
	
		
			
				|  |  |      for(int i=0;i<ee->nexte;i++){ 
 | 
	
		
			
				|  |  |          if(!ee->de[i].IsBorder) continue; MEDupEdge*de=&ee->de[i];
 | 
	
		
			
				|  |  |          tnsMMeshMakeFace4v(mo, de->ome->vl, de->ome->vr, de->nme->vr, de->nme->vl);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -void la_FinishExtrude(MExtrudeExtra* ee){
 | 
	
		
			
				|  |  | +void la_FinishExtrude(MExtrudeExtra* ee, int PushDifferences){
 | 
	
		
			
				|  |  |      tnsMMeshRefreshIndex(ee->mo);
 | 
	
		
			
				|  |  | -    tnsInvaliateMeshBatch(ee->mo);
 | 
	
		
			
				|  |  | -    laRecordInstanceDifferences(ee->mo, "tns_mesh_object"); laPushDifferences("Extruded", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +    tnsInvalidateMeshBatch(ee->mo);
 | 
	
		
			
				|  |  | +    if(PushDifferences){
 | 
	
		
			
				|  |  | +        laRecordInstanceDifferences(ee->mo, "tns_mesh_object"); laPushDifferences("Extruded", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |      free(ee->dv); free(ee->de); free(ee->df); memFree(ee);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  int OPINV_Extrude(laOperator *a, laEvent *e){
 | 
	
	
		
			
				|  | @@ -796,13 +805,14 @@ int OPINV_Extrude(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |      la_ExtrudeMakeDuplication(ee);
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |      if(strSame(strGetArgumentString(a->ExtraInstructionsP,"duplicate_only"), "true")){
 | 
	
		
			
				|  |  | -        la_FinishExtrude(ee);
 | 
	
		
			
				|  |  | +        la_FinishExtrude(ee, 1);
 | 
	
		
			
				|  |  |          if(la_InitTransform(a, e, LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    la_RemoveOriginalFaces(ee);
 | 
	
		
			
				|  |  |      la_ReconnectFaces(ee);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    la_FinishExtrude(ee);
 | 
	
		
			
				|  |  | +    la_FinishExtrude(ee, 1);
 | 
	
		
			
				|  |  |      if(la_InitTransform(a, e, LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return LA_FINISHED;
 | 
	
	
		
			
				|  | @@ -910,7 +920,7 @@ int OPINV_Delete(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          tnsMMeshDeselectAll(mo);
 | 
	
		
			
				|  |  |          tnsMMeshRefreshIndex(mo);
 | 
	
		
			
				|  |  | -        tnsInvaliateMeshBatch(mo);
 | 
	
		
			
				|  |  | +        tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  |          laRecordInstanceDifferences(mo, "tns_mesh_object"); laPushDifferences("Deleted primitives", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1044,13 +1054,146 @@ int OPINV_Make(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      tnsMMeshRefreshIndex(mo);
 | 
	
		
			
				|  |  |      tnsMMeshEnsureSelection(mo,ex->SelectMode);
 | 
	
		
			
				|  |  | -    tnsInvaliateMeshBatch(mo);
 | 
	
		
			
				|  |  | +    tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  |      if(laRecordInstanceDifferences(mo, "tns_mesh_object")) laPushDifferences("Make primitives", TNS_HINT_GEOMETRY);
 | 
	
		
			
				|  |  |      laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |      return LA_FINISHED;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +int OPINV_Subdiv(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  | +    if(!a->This || !a->This->EndInstance){ return 0; }
 | 
	
		
			
				|  |  | +    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
 | 
	
		
			
				|  |  | +    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return 0;
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=root->Active;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laListHandle pending={0}; for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(me->flags&TNS_MESH_FLAG_SELECTED) lstAppendPointer(&pending, me); }
 | 
	
		
			
				|  |  | +    if(!pending.pFirst) return LA_FINISHED;
 | 
	
		
			
				|  |  | +    tnsMEdge* me; while(me=lstPopPointer(&pending)){ tnsMVert* mv=tnsMMeshEdgeInsertVertAt(mo,me,0.5,0,0,0); mv->flags|=TNS_MESH_FLAG_SELECTED; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    tnsMMeshRefreshIndex(mo);
 | 
	
		
			
				|  |  | +    tnsMMeshEnsureSelection(mo,ex->SelectMode);
 | 
	
		
			
				|  |  | +    tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  | +    if(laRecordInstanceDifferences(mo, "tns_mesh_object")) laPushDifferences("Subdivide edges", TNS_HINT_GEOMETRY);
 | 
	
		
			
				|  |  | +    laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    return LA_FINISHED;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +int OPINV_Add(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  | +    if(!a->This || !a->This->EndInstance){ return 0; }
 | 
	
		
			
				|  |  | +    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
 | 
	
		
			
				|  |  | +    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return 0;
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=root->Active; int ran=0; tnsObject* no=0;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){
 | 
	
		
			
				|  |  | +        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"PLANE")){ tnsDeselectAllObjects(root); 
 | 
	
		
			
				|  |  | +            no=tnsCreateMeshPlane(root, "Plane",0,0,0,10); no->Flags|=TNS_OBJECT_FLAGS_SELECTED; memAssignRef(root,&root->Active,no); ran=1; }
 | 
	
		
			
				|  |  | +        else{ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
 | 
	
		
			
				|  |  | +        if(ran){ laRecordAndPush(0,"tns.world","Add object",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world"); }
 | 
	
		
			
				|  |  | +    }else{
 | 
	
		
			
				|  |  | +        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"PLANE")){
 | 
	
		
			
				|  |  | +            tnsMMeshDeselectAll(mo); tnsAddMMeshPlane(mo, 10); tnsMMeshEnsureSelection(mo,ex->SelectMode); ran=1;
 | 
	
		
			
				|  |  | +        }else{ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
 | 
	
		
			
				|  |  | +        if(ran){
 | 
	
		
			
				|  |  | +            tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo); 
 | 
	
		
			
				|  |  | +            laRecordInstanceDifferences(mo, "tns_mesh_object"); laPushDifferences("Add primitives", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return LA_FINISHED;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void laui_Add(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
 | 
	
		
			
				|  |  | +    laColumn* c=laFirstColumn(uil);
 | 
	
		
			
				|  |  | +    laShowItemFull(uil,c,pp,"_this_M_add",0,"mode=PLANE;text=Plane",0,0);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +int OPINV_Separate(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  | +    if(!a->This || !a->This->EndInstance){ return 0; }
 | 
	
		
			
				|  |  | +    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
 | 
	
		
			
				|  |  | +    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED;
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=root->Active; int ran=0;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){
 | 
	
		
			
				|  |  | +        return LA_CANCELED;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if(!tnsMMeshAnySelected(mo)) return LA_CANCELED;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    MExtrudeExtra* ee=la_InitExtrude(mo);
 | 
	
		
			
				|  |  | +    la_ExtrudeMakeDuplication(ee);
 | 
	
		
			
				|  |  | +    ee->RemoveOriginalFaces=1;la_RemoveOriginalFaces(ee);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    tnsMeshObject* no=tnsCreateMeshEmpty(mo->Base.ParentObject?mo->Base.ParentObject:mo->Base.InRoot, mo->Base.Name->Ptr, 0,0,0);
 | 
	
		
			
				|  |  | +    tnsCopyObjectTransformationsLocal(no,mo);
 | 
	
		
			
				|  |  | +    no->Mode=TNS_MESH_EDIT_MODE;
 | 
	
		
			
				|  |  | +    tnsMVert* nmv; for(tnsMVert* mv=mo->mv.pFirst;mv;mv=nmv){ nmv=mv->Item.pNext; if(!(mv->flags&TNS_MESH_FLAG_SELECTED))continue; 
 | 
	
		
			
				|  |  | +        lstRemoveItem(&mo->mv, mv); lstAppendItem(&no->mv, mv); no->totmv++; mo->totmv--; }
 | 
	
		
			
				|  |  | +    tnsMEdge* nme; for(tnsMEdge* me=mo->me.pFirst;me;me=nme){ nme=me->Item.pNext; if(!(me->flags&TNS_MESH_FLAG_SELECTED))continue; 
 | 
	
		
			
				|  |  | +        lstRemoveItem(&mo->me, me); lstAppendItem(&no->me, me); no->totme++; mo->totme--; }
 | 
	
		
			
				|  |  | +    tnsMFace* nmf; for(tnsMFace* mf=mo->mf.pFirst;mf;mf=nmf){ nmf=mf->Item.pNext; if(!(mf->flags&TNS_MESH_FLAG_SELECTED))continue; 
 | 
	
		
			
				|  |  | +        lstRemoveItem(&mo->mf, mf); lstAppendItem(&no->mf, mf); no->totmf++; mo->totmf--; }
 | 
	
		
			
				|  |  | +    tnsMMeshRefreshIndex(no); tnsMeshLeaveEditMode(no);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    la_FinishExtrude(ee, 0);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  | +    laRecordAndPush(0,"tns.world","Separate mesh parts",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    return LA_FINISHED;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void la_PopulateSelectedMeshObjects(tnsObject* root, laListHandle* l){
 | 
	
		
			
				|  |  | +    if(root->Type==TNS_OBJECT_MESH && root->Flags&TNS_OBJECT_FLAGS_SELECTED){ lstAppendPointer(l,root); }
 | 
	
		
			
				|  |  | +    for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  | +        la_PopulateSelectedMeshObjects(lip->p, l);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int OPINV_Combine(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  | +    if(!a->This || !a->This->EndInstance){ return 0; }
 | 
	
		
			
				|  |  | +    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
 | 
	
		
			
				|  |  | +    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED;
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=root->Active; int ran=0;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode==TNS_MESH_EDIT_MODE){ return LA_CANCELED; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laListHandle pending={0}; la_PopulateSelectedMeshObjects(root,&pending);
 | 
	
		
			
				|  |  | +    tnsMeshObject* o; while(o=lstPopPointer(&pending)){ if(o==mo || o->Mode==TNS_MESH_EDIT_MODE) continue;
 | 
	
		
			
				|  |  | +        if(tnsMergeMeshObjects(mo, o)) ran++;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if(ran){
 | 
	
		
			
				|  |  | +        tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo);
 | 
	
		
			
				|  |  | +        laRecordAndPush(0,"tns.world","Merge mesh objects",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    return LA_FINISHED;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +int OPINV_Duplicate(laOperator *a, laEvent *e){
 | 
	
		
			
				|  |  | +    if(!a->This || !a->This->EndInstance){ return 0; }
 | 
	
		
			
				|  |  | +    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
 | 
	
		
			
				|  |  | +    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED;
 | 
	
		
			
				|  |  | +    tnsMeshObject* mo=root->Active; int ran=0;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode==TNS_MESH_EDIT_MODE){ return LA_CANCELED; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    laListHandle pending={0}; la_PopulateSelectedMeshObjects(root,&pending);
 | 
	
		
			
				|  |  | +    tnsMeshObject* o; tnsMeshObject* no;while(o=lstPopPointer(&pending)){ if(o->Mode==TNS_MESH_EDIT_MODE) continue;
 | 
	
		
			
				|  |  | +        if(no=tnsDuplicateMeshObjects(o)){ no->Base.Flags|=TNS_OBJECT_FLAGS_SELECTED; o->Base.Flags&=(~TNS_OBJECT_FLAGS_SELECTED); 
 | 
	
		
			
				|  |  | +            if(mo==o){ memAssignRef(root,&root->Active,no); } ran++; 
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    if(ran){
 | 
	
		
			
				|  |  | +        laRecordAndPush(0,"tns.world","Merge mesh objects",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
 | 
	
		
			
				|  |  | +        if(la_InitTransform(a,e,LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    return LA_FINISHED;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void la_RegisterModellingOperators(){
 | 
	
		
			
				|  |  |      laPropContainer *pc; laProp *p;
 | 
	
		
			
				|  |  |      laOperatorType *at;
 | 
	
	
		
			
				|  | @@ -1070,5 +1213,10 @@ void la_RegisterModellingOperators(){
 | 
	
		
			
				|  |  |      at=laCreateOperatorType("M_delete", "Delete", "Delete parts of the mesh", 0, 0, 0, OPINV_Delete, OPMOD_FinishOnData, 0, 0);
 | 
	
		
			
				|  |  |      at->UiDefine=laui_Delete;
 | 
	
		
			
				|  |  |      laCreateOperatorType("M_make", "Make", "Make mesh primitive from selected ones", 0, 0, 0, OPINV_Make, 0, 0, 0);
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +    laCreateOperatorType("M_subdiv", "Subdiv", "Subdivide edges", 0, 0, 0, OPINV_Subdiv, 0, 0, 0);
 | 
	
		
			
				|  |  | +    at=laCreateOperatorType("M_add", "Add", "Add mesh or primitives", 0, 0, 0, OPINV_Add, OPMOD_FinishOnData, 0, 0);
 | 
	
		
			
				|  |  | +    at->UiDefine=laui_Add;
 | 
	
		
			
				|  |  | +    laCreateOperatorType("M_separate", "Separate", "Separate mesh parts", 0, 0, 0, OPINV_Separate, 0, 0, 0);
 | 
	
		
			
				|  |  | +    laCreateOperatorType("M_combine", "Combine", "Combine mesh objects", 0, 0, 0, OPINV_Combine, 0, 0, 0);
 | 
	
		
			
				|  |  | +    laCreateOperatorType("M_duplicate", "Duplicate", "Duplicate objects", 0, 0, 0, OPINV_Duplicate, OPMOD_Transformation, 0, 0);
 | 
	
		
			
				|  |  |  }
 |