*/}}
Browse Source

Mesh operators mostly okay

Yiming Wu 2 years ago
parent
commit
55574f6685

+ 12 - 7
source/lagui/la_data.c

@@ -3701,11 +3701,13 @@ void la_FreeDBProp(laDBProp* dbp){
     if(dbp->p->PropertyType==LA_PROP_SUB){
         if((((laSubProp*)dbp->p)->ListHandleOffset||dbp->p->UDFNoCreate||dbp->p->UDFIsSingle)&&(!dbp->p->UDFIsRefer)){
             laDBSubProp* dsp=dbp; laDBInst* si;
-            printf("fdbp %s %x %x %x\n",dbp->p->Identifier,dsp->Instances.pFirst,dsp->Instances.pLast,((laListItem*)dsp->Instances.pFirst)->pNext);
+            //printf("fdbp %s %x %x %x\n",dbp->p->Identifier,dsp->Instances.pFirst,dsp->Instances.pLast,((laListItem*)dsp->Instances.pFirst)->pNext);
             while(si=lstPopItem(&dsp->Instances)){ la_FreeDBInst(si,dbp->p->UDFNoCreate||(!dbp->p->OffsetIsPointer)); }
         } // prevent freeing the data;
     }elif(dbp->p->PropertyType==LA_PROP_STRING && ((laStringProp*)dbp->p)->IsSafeString){
         strSafeSet(&dbp->Data,0);
+    }elif(dbp->p->PropertyType==LA_PROP_RAW){
+        free(dbp->Data);
     }else{
         memFree(dbp->Data);
     }
@@ -3848,8 +3850,8 @@ int la_AddRawDBProp(laDBInst* dbi, laDBRawProp* dbp, laDiff* diff, laPropPack *p
     Data=laGetRaw(pp,&s,&IsCopy); if(diff&&dbp){
         if(dbp->DataSize!=s || (!dbp->Data&&Data) || (!Data&&dbp->Data) || (dbp->Data&&Data&&memcmp(dbp->Data, Data, s))){
             printf("s%d %x %d \n",s,Data,dbp->DataSize);
-            void* NewData=s?calloc(1,s):0;
-            if(s)memcpy(NewData, Data, s);
+            void* NewData=(s&&Data)?calloc(1,s):0;
+            if(s&&Data)memcpy(NewData, Data, s);
             laDiffCommandRaw* dcr=la_GiveDiffCommand(diff, dbi, p, dbp->Data);
             dcr->DataSize=dbp->DataSize; dbp->Data=NewData; dbp->DataSize=s; ret=1;
         }
@@ -4352,15 +4354,18 @@ int laRecordInstanceDifferences(void* instance, const char* container){
 
     return success;
 }
-void laRecordAndPush(laPropPack* base, char* path){
-    char buf[256]="Changes in ";;
+void laRecordAndPush(laPropPack* base, char* path, char* description, uint64_t hint){
+    if(laRecordDifferences(base, path)){ laPushDifferences(description, hint); }
+}
+void laRecordAndPushProp(laPropPack* base, char* path){
+    char buf[256]={0};
     if(laRecordDifferences(base, path)){
-        la_GetPropPackFullPath(base,&buf[strlen(buf)]);if(path){sprintf(&buf[strlen(buf)],".%s",path);}
+        la_GetPropPackFullPath(base,&buf[strlen(buf)]);if(path){sprintf(&buf[strlen(buf)],"%s%s",base?".":"",path);}
         laPushDifferences(buf, 0);
     }
 }
 void laRecordEverythingAndPush(){
     for(laDBRecordedProp* rp=MAIN.DBRecordedProps.pFirst;rp;rp=rp->Item.pNext){
-        laRecordAndPush(0, rp->OriginalPath->Ptr);
+        laRecordAndPushProp(0, rp->OriginalPath->Ptr);
     }
 }

+ 2 - 1
source/lagui/la_data.h

@@ -887,7 +887,8 @@ void laAddRootDBInst(char* path);
 void laPushDifferences(char* Description, u64bit hint);
 int laRecordDifferences(laPropPack* base, char* path);
 int laRecordInstanceDifferences(void* instance, const char* container);
-void laRecordAndPush(laPropPack* base, char* path);
+void laRecordAndPush(laPropPack* base, char* path, char* description, uint64_t hint);
+void laRecordAndPushProp(laPropPack* base, char* path);
 void laRecordEverythingAndPush();
 
 void laUndo();

+ 2 - 2
source/lagui/la_kernel.c

@@ -4090,7 +4090,7 @@ int la_UpdateUiListRecursive(laUiList *uil, int U, int L, int R, int B, int Fast
             if (ui->PP.LastPs && ui->PP.LastPs->p->PropertyType == LA_PROP_OPERATOR){
                 laOperatorProp *ap = ui->PP.LastPs->p;
                 if (!ap->OperatorType) ap->OperatorType = laGetOperatorType(ap->OperatorID);
-                if (ap->OperatorType->ExtraInstructions) strMakeInstructions(&ui->Instructions, ap->OperatorType->ExtraInstructions);
+                if (ap->OperatorType&&ap->OperatorType->ExtraInstructions) strMakeInstructions(&ui->Instructions, ap->OperatorType->ExtraInstructions);
             }
         }
 
@@ -6085,13 +6085,13 @@ int la_UpdateOperatorHints(laWindow* w){
     laSafeString* ss=0;
     for(laOperator* o=w->Operators.pFirst;o;o=o->Item.pNext){
         if(o->RuntimeHint&&o->RuntimeHint->Ptr){ strSafePrint(&ss, "%s | %s  ", o->Type->Name, o->RuntimeHint->Ptr);}
-        strSafeDestroy(&o->RuntimeHint);
     }
     if((w->OperatorHints&&ss&&strSame(ss->Ptr,w->OperatorHints->Ptr))||(!w->OperatorHints&&!ss)){ //pass
     }else{
         if(ss){ strSafeSet(&w->OperatorHints,ss->Ptr); } else { strSafeDestroy(&w->OperatorHints); }
         laNotifyUsers("la.windows.operator_hints");//printf("op hint\n");
     }
+    strSafeDestroy(&ss);
 }
 int la_HandleSingleEvent(laEvent *e, laListHandle *Operators){
     laOperator *a, *NextA = 0;

+ 20 - 1
source/lagui/la_tns.h

@@ -929,6 +929,9 @@ double tnsGetLineZ(tnsVector3d L, tnsVector3d R, real Ratio);
 double tnsGetLineZPoint(tnsVector3d L, tnsVector3d R, tnsVector3d FromL);
 double tnsGetRatio3d(tnsVector3d L, tnsVector3d R, tnsVector3d FromL);
 double tnsGetRatiod(real L, real R, real FromL);
+real tnsInterpolate(real L, real R, real T);
+void tnsInterpolate2dv(real *L, real *R, real T, real *Result);
+void tnsInterpolate3dv(real *L, real *R, real T, real *Result);
 void tnsInterpolateTripple2d(tnsVector2d v1, tnsVector2d v2, tnsVector2d v3, real ratio, tnsVector2d result);
 void tnsVectorMinus2d(tnsVector2d result, tnsVector2d l, tnsVector2d r);
 void tnsVectorMinus3d(tnsVector3d result, tnsVector3d l, tnsVector3d r);
@@ -951,6 +954,10 @@ real tnsDirectionToRad(tnsVector2d Dir);
 void tnsConvert44df(tnsMatrix44d from, tnsMatrix44f to);
 int tnsTrangleLineBoundBoxTest(tnsRenderTriangle *rt, tnsRenderLine *rl);
 
+#define tnsVectorSet3(to, x,y,z)\
+    {to[0]=x;to[1]=y;to[2]=z;}
+#define tnsVectorSet3v(to, from)\
+    tnsVectorSet3(to,from[0],from[1],from[2])
 
 real tnsDistIdv2(real x1, real y1, real x2, real y2);
 real tnsDist3dv(tnsVector3d l, tnsVector3d r);
@@ -979,6 +986,7 @@ void tnsMakeTranslationMatrix44d(tnsMatrix44d mTrans, real x, real y, real z);
 void tnsMakeRotationMatrix44d(tnsMatrix44d m, real angle_rad, real x, real y, real z);
 void tnsMakeScaleMatrix44d(tnsMatrix44d m, real x, real y, real z);
 void tnsMakeViewportMatrix44d(tnsMatrix44d m, real w, real h, real Far, real Near);
+void tnsInverse44d(tnsMatrix44d inverse, tnsMatrix44d mat);
 void tnsMultiply44d(tnsMatrix44d result, tnsMatrix44d l, tnsMatrix44d r);
 void tnsMakeRotationXMatrix44d(tnsMatrix44d m, real angle_rad);
 void tnsMakeRotationYMatrix44d(tnsMatrix44d m, real angle_rad);
@@ -1021,6 +1029,8 @@ int tnsLoadExchange(char *FileName);
 
 tnsObject *tnsFindObject(char *Name, tnsObject *From);
 
+void tnsCopyObjectTransformationsLocal(tnsObject* to, tnsObject* from);
+void tnsCopyObjectTransformationsGlobal(tnsObject* to, tnsObject* from);
 void tnsRotateObjectGlobal(tnsObject *o, real x, real y, real z, real angle, real cx,real cy,real cz);
 void tnsRotateObjectLocal(tnsObject *o, real x, real y, real z, real angle, real gcx,real gcy,real gcz);
 void tnsRotateObjectLocalValues(tnsObject *o, real x, real y, real z);
@@ -1050,8 +1060,15 @@ tnsCamera *tnsCreateCamera(tnsObject *under, char *Name, real FOV,
                               real FocusDistance);
 tnsObject *tnsCreateEmpty(tnsObject *under, char *Name, real AtX, real AtY, real AtZ);
 tnsLight *tnsCreateLight(tnsObject *under, char *Name, real AtX, real AtY, real AtZ, real Strength, int UniDirectional);
+tnsMeshObject *tnsCreateMeshEmpty(tnsObject *under, char *Name, real AtX, real AtY, real AtZ);
 tnsMeshObject *tnsCreateMeshPlane(tnsObject *under, char *Name, real AtX, real AtY, real AtZ, real size);
 
+int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo);
+tnsMeshObject* tnsDuplicateMeshObjects(tnsMeshObject* from);
+
+void tnsInitMeshPlane(tnsMeshObject* mo, real size);
+void tnsAddMMeshPlane(tnsMeshObject* mo, real size);
+
 int tnsAnyObjectsSelected(tnsObject* parent);
 void tnsDeselectAllObjects(tnsObject* parent);
 void tnsSelectAllObjects(tnsObject* parent);
@@ -1062,6 +1079,7 @@ tnsMEdge* tnsMMeshNewEdge(tnsMeshObject* mo);
 tnsMVert* tnsMMeshNewVert(tnsMeshObject* mo);
 void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2);
 tnsMEdge* tnsMMeshVertShareEdge(tnsMVert* mv0, tnsMVert* mv1);
+int tnsMMeshEdgeHasVert(tnsMEdge* me, tnsMVert* MV);
 tnsMVert* tnsMMeshEdgeShareVert(tnsMEdge* me0, tnsMEdge* me1);
 tnsMVert* tnsMMeshEdgeAnotherVert(tnsMEdge* me, tnsVert* v);
 tnsMVert* tnsMMeshEdgeStartingVert(tnsMEdge* me0, tnsMEdge* me1);
@@ -1072,6 +1090,7 @@ tnsMFace* tnsMMeshMakeFaceN(tnsMeshObject* mo, int count, laListHandle* vip, tns
 tnsMFace* tnsMMeshMakeFace4v(tnsMeshObject* mo, tnsVert* v1,tnsVert* v2,tnsVert* v3,tnsVert* v4);
 int tnsMMeshLoopIsInverted(laListItemPointer* l);
 int tnsMMeshEdgeInsertVert(tnsMeshObject* mo, tnsMEdge* me, tnsMVert* mv, tnsMVert* ref_e1v_optional, tnsMEdge** r_e1, tnsMEdge** r_e2);
+tnsMVert* tnsMMeshEdgeInsertVertAt(tnsMeshObject* mo, tnsMEdge* me, real at, tnsMVert* ref_e1v_optional, tnsMEdge** r_e1, tnsMEdge** r_e2);
 void tnsMMeshRemoveFaceOnly(tnsMeshObject* mo, tnsMFace* mf);
 void tnsMMeshRemoveEdgeFace(tnsMeshObject* mo, tnsMEdge* me);
 void tnsMMeshRemoveVertEdgeFace(tnsMeshObject* mo, tnsMVert* mv);
@@ -1090,7 +1109,7 @@ void tnsMMeshEnsureSelectionFromVerts(tnsMeshObject* mo);
 void tnsMMeshEnsureSelectionFromEdges(tnsMeshObject* mo);
 void tnsMMeshEnsureSelection(tnsMeshObject* mo, int SelectMode);
 
-void tnsInvaliateMeshBatch(tnsMeshObject* mo);
+void tnsInvalidateMeshBatch(tnsMeshObject* mo);
 void tnsRegenerateMeshBatch(tnsMeshObject* mo);
 void tnsEnsureMeshBatch(tnsMeshObject* mo);
 void tnsDrawMeshObject(tnsMeshObject* mo, int DrawAsObjectSelection, int MeshSelectionMode, tnsMeshObject* Active);

+ 0 - 13
source/lagui/la_tns_curve.c

@@ -35,19 +35,6 @@ Send feedback to la_support@nicksbest.com
 
 extern tnsMain *T;
 
-real tnsInterpolate(real L, real R, real T){
-    return tnsLinearItp(L, R, T);
-}
-void tnsInterpolate2dv(real *L, real *R, real T, real *Result){
-    Result[0] = tnsLinearItp(L[0], R[0], T);
-    Result[1] = tnsLinearItp(L[1], R[1], T);
-}
-void tnsInterpolate3dv(real *L, real *R, real T, real *Result){
-    Result[0] = tnsLinearItp(L[0], R[0], T);
-    Result[1] = tnsLinearItp(L[1], R[1], T);
-    Result[2] = tnsLinearItp(L[2], R[2], T);
-}
-
 //z3 = 1/( linearintp(1/z1),(1/z2),t )
 //L,R is GLocation
 void tnsInterpolatePerspective4dv(tnsVector4d LG, tnsVector4d RG, tnsVector4d L, tnsVector4d R, real T, tnsVector3d Result){

+ 34 - 5
source/lagui/la_tns_kernel.c

@@ -699,6 +699,19 @@ double tnsGetRatiod(real L, real R, real FromL){
     return r;
 }
 
+real tnsInterpolate(real L, real R, real T){
+    return tnsLinearItp(L, R, T);
+}
+void tnsInterpolate2dv(real *L, real *R, real T, real *Result){
+    Result[0] = tnsLinearItp(L[0], R[0], T);
+    Result[1] = tnsLinearItp(L[1], R[1], T);
+}
+void tnsInterpolate3dv(real *L, real *R, real T, real *Result){
+    Result[0] = tnsLinearItp(L[0], R[0], T);
+    Result[1] = tnsLinearItp(L[1], R[1], T);
+    Result[2] = tnsLinearItp(L[2], R[2], T);
+}
+
 void tnsInterpolateTripple2d(tnsVector2d v1, tnsVector2d v2, tnsVector2d v3, real ratio, tnsVector2d result){
     tnsVector2d i1,i2;
     tnsInterpolate2dv(v1,v2,ratio,i1);
@@ -3185,13 +3198,25 @@ void tnsParentObject(tnsObject *child, tnsObject *parent, int KeepTransform){
 }
 void tnsUnparentObject(tnsObject *o, int KeepTransform){
     if (!o || !o->ParentObject) return;
-    lstRemovePointer(&o->ParentObject->ChildObjects, o);
+    lstRemovePointerLeave(&o->ParentObject->ChildObjects, o);
     lstAppendPointer(&o->InRoot->ChildObjects, o);
     memAssignRef(o, &o->ParentObject, 0);
 
     if(KeepTransform) tnsGlobalMatrixChanged(o,1);
     else tnsSelfMatrixChanged(o,1);
 }
+void tnsCopyObjectTransformationsLocal(tnsObject* to, tnsObject* from){
+    tnsVectorCopy3d(from->Location, to->Location);
+    tnsVectorCopy3d(from->Rotation, to->Rotation); to->RotationMode=from->RotationMode;
+    to->Scale = from->Scale;
+    tnsSelfTransformValueChanged(to);
+}
+void tnsCopyObjectTransformationsGlobal(tnsObject* to, tnsObject* from){
+    tnsVectorCopy3d(from->GLocation, to->GLocation);
+    tnsVectorCopy3d(from->GRotation, to->GRotation); to->RotationMode=from->RotationMode;
+    to->GScale = from->GScale;
+    tnsGlobalTransformValueChanged(to);
+}
 void tnsRotateObjectLocalValues(tnsObject *o, real x, real y, real z){
     o->Rotation[0] += x; o->Rotation[1] += y; o->Rotation[2] += z;
     tnsSelfTransformValueChanged(o);
@@ -3310,9 +3335,9 @@ void tnsDestroyRootObject(tnsObject *root){
     w->ActiveRoot = root->Item.pPrev ? root->Item.pPrev : root->Item.pNext ? root->Item.pNext : 0;
 
     lstRemoveItem(&w->RootObjects, root);
-    while (lstPopPointer(&root->ChildObjects));
+    while (lstPopPointerLeave(&root->ChildObjects));
     strSafeDestroy(&root->Name);
-    memFree(root);
+    memLeave(root);
 }
 void tnsDestroyObject(tnsObject *o){
     if(o->Type==TNS_OBJECT_ROOT){ tnsDestroyRootObject(o); return; }
@@ -3322,10 +3347,14 @@ void tnsDestroyObject(tnsObject *o){
 
     lstRemoveItem(&T->World.AllObjects, o);
 
-    if(o->Type==TNS_OBJECT_MESH){ tnsInvaliateMeshBatch(o); }
+    if(o->Type==TNS_OBJECT_MESH){ tnsMeshObject* mo=o;
+        if(mo->v) arrFree(&mo->v, &mo->maxv); if(mo->e) arrFree(&mo->e, &mo->maxe); if(mo->f) arrFree(&mo->f, &mo->maxf);
+        mo->totv=mo->tote=mo->totf=0;
+        tnsInvalidateMeshBatch(o);
+    }
 
     strSafeDestroy(&o->Name);
-    memFree(o);
+    memLeave(o);
 }
 tnsCamera *tnsCreateCamera(tnsObject *under, char *Name, real FOV,
                         real AtX, real AtY, real AtZ,

+ 74 - 16
source/lagui/la_tns_mesh.c

@@ -61,6 +61,39 @@ tnsFace* tnsFillFace(tnsMeshObject* mo, int vertcount, ...){
 }
 void tnsFillFaceLoop(tnsFace* f, int i, int v){ f->loop[i]=v; }
 
+int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo){
+    if(into->Base.Type!=TNS_OBJECT_MESH||mo->Base.Type!=TNS_OBJECT_MESH) return 0;
+    if(!mo->totv){ tnsDestroyObject(mo); return 1; }
+    tnsVector3d v,vf; tnsMatrix44d inv; tnsInverse44d(inv, into->Base.GlobalTransform);
+    for(int i=0;i<mo->totv;i++){
+        tnsVectorSet3v(v,mo->v[i].p); tnsApplyTransform43d(vf, mo->Base.GlobalTransform, v); 
+        tnsApplyTransform43d(v, inv, vf); tnsVectorSet3v(mo->v[i].p,v);
+    }
+    for(int i=0;i<mo->tote;i++){ mo->e[i].l+=into->totv; mo->e[i].r+=into->totv; }
+    for(int i=0;i<mo->totf;i++){ for(int l=0;l<mo->f[i].looplen;l++){ mo->f[i].loop[l]+=into->totv; } }
+    int origv=into->totv, orige=into->tote, origf=into->totf;
+    into->totv+=mo->totv; into->tote+=mo->tote; into->totf+=mo->totf;
+    arrEnsureLength(&into->v, into->totv, &into->maxv, sizeof(tnsVert)); if(mo->totv) memcpy(&into->v[origv],mo->v,sizeof(tnsVert)*mo->totv);
+    arrEnsureLength(&into->e, into->tote, &into->maxe, sizeof(tnsEdge)); if(mo->tote) memcpy(&into->e[orige],mo->e,sizeof(tnsEdge)*mo->tote);
+    arrEnsureLength(&into->f, into->totf, &into->maxf, sizeof(tnsFace)); if(mo->totf) memcpy(&into->f[origf],mo->f,sizeof(tnsFace)*mo->totf);
+    tnsDestroyObject(mo); return 1;
+}
+tnsMeshObject* tnsDuplicateMeshObjects(tnsMeshObject* from){
+    if(from->Base.Type!=TNS_OBJECT_MESH) return 0;
+    tnsMeshObject* to = memAcquireHyper(sizeof(tnsMeshObject));
+    tnsInitObjectBase(to, from->Base.ParentObject?from->Base.ParentObject:from->Base.InRoot, from->Base.Name, TNS_OBJECT_MESH,0,0,0,0,0,0,0,0,1);
+    tnsCopyObjectTransformationsLocal(to,from);
+    if(!from->totv){ return to; }
+    to->totv=from->totv; to->tote=from->tote; to->totf=from->totf;
+    arrInitLength(&to->v, to->totv, &to->maxv, sizeof(tnsVert)); if(from->totv) memcpy(to->v,from->v,sizeof(tnsVert)*from->totv);
+    arrInitLength(&to->e, to->tote, &to->maxe, sizeof(tnsEdge)); if(from->tote) memcpy(to->e,from->e,sizeof(tnsEdge)*from->tote);
+    arrInitLength(&to->f, to->totf, &to->maxf, sizeof(tnsFace)); if(from->totf) memcpy(to->f,from->f,sizeof(tnsFace)*from->totf);
+    for(int i=0;i<to->totf;i++){ to->f[i].loop=0; arrInitLength(&to->f[i].loop, to->f[i].looplen, &to->f[i].looplen, sizeof(int));
+        for(int l=0;l<to->f[i].looplen;l++){ to->f[i].loop[l]=from->f[i].loop[l]; }
+    }
+    return to;
+}
+
 void tnsInitMeshPlane(tnsMeshObject* mo, real size){
     tnsInitMesh(mo, 4,0,1);
     tnsFillVert(mo, size, size,0); tnsFillVert(mo,-size, size,0);
@@ -72,6 +105,14 @@ void tnsInitMeshPlane(tnsMeshObject* mo, real size){
     mo->v[3].flags|=TNS_MESH_FLAG_SELECTED;
     tnsMMeshEnsureSelectionFromVerts(mo);
 }
+void tnsAddMMeshPlane(tnsMeshObject* mo, real size){
+    tnsMVert *mv1,*mv2,*mv3,*mv4;
+    mv1=tnsMMeshNewVert(mo); mv1->p[0]=size;  mv1->p[1]=size;  mv1->flags|=TNS_MESH_FLAG_SELECTED;
+    mv2=tnsMMeshNewVert(mo); mv2->p[0]=-size; mv2->p[1]=size;  mv2->flags|=TNS_MESH_FLAG_SELECTED;
+    mv3=tnsMMeshNewVert(mo); mv3->p[0]=-size; mv3->p[1]=-size; mv3->flags|=TNS_MESH_FLAG_SELECTED;
+    mv4=tnsMMeshNewVert(mo); mv4->p[0]=size;  mv4->p[1]=-size; mv4->flags|=TNS_MESH_FLAG_SELECTED;
+    tnsMMeshMakeFace4v(mo,mv1,mv2,mv3,mv4);
+}
 
 void tnsTrangulateFaceSimple(tnsMeshObject* mo, tnsFace* f, int* ebuf){
     for(int i=0;i<f->looplen-2;i++){
@@ -146,7 +187,7 @@ int* tnsGetEdgeBatch(tnsMeshObject* mo){
     return ebuf;
 }
 
-void tnsInvaliateMeshBatch(tnsMeshObject* mo){
+void tnsInvalidateMeshBatch(tnsMeshObject* mo){
     if(mo->Base.Type!=TNS_OBJECT_MESH) return;
     if(mo->Batch) tnsDeleteBatch(mo->Batch); mo->Batch=0;
 }
@@ -225,6 +266,9 @@ void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2){
 tnsMEdge* tnsMMeshVertShareEdge(tnsMVert* mv0, tnsMVert* mv1){
     for(laListItemPointer*lip=mv0->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* me=lip->p; if(tnsMMeshEdgeAnotherVert(me, mv0)==mv1) return me; } return 0;
 }
+int tnsMMeshEdgeHasVert(tnsMEdge* me, tnsMVert* mv){
+    if(me->vl==mv || me->vr==mv) return 1; return 0;
+}
 tnsMVert* tnsMMeshEdgeShareVert(tnsMEdge* me0, tnsMEdge* me1){
     if(me0->vl==me1->vl || me0->vl==me1->vr) return me0->vl;
     if(me0->vr==me1->vl || me0->vr==me1->vr) return me0->vr;
@@ -255,8 +299,10 @@ int tnsMMeshSplitFace(tnsMeshObject* mo, tnsMFace* mf, tnsMEdge* me, tnsMFace**
     tnsMEdge* NextE; laListItemPointer* NextLip, *StartLip=0, *EndLip=0; int guard=0;
     for(laListItemPointer*lip=mf->l.pFirst;lip;lip=NextLip){
         NextLip=lip->pNext?lip->pNext:mf->l.pFirst; NextE=NextLip->p;
-        if(tnsMMeshEdgeShareVert(me,tnsMMeshEdgeShareVert(NextE,lip->p))){ if(!StartLip) StartLip=lip; else{EndLip=lip; break;} }
-        guard++; if(guard>mf->looplen) return 0; // ve is not across mf.
+        if(tnsMMeshEdgeHasVert(me,tnsMMeshEdgeShareVert(NextE,lip->p))){
+            if(!StartLip) StartLip=NextLip;
+            else{EndLip=NextLip; break;} }
+        guard++; if(guard>mf->looplen) return 0; // me is not across mf.
     }
     tnsMFace* f1=tnsMMeshNewFace(mo);
     for(laListItemPointer*lip=StartLip;lip;lip=NextLip){ NextLip=lip->pNext?lip->pNext:mf->l.pFirst; 
@@ -335,20 +381,26 @@ int tnsMMeshEdgeInsertVert(tnsMeshObject* mo, tnsMEdge* me, tnsMVert* mv, tnsMVe
     if(mv->elink.pFirst||mv->elink.pLast||me->vl==mv||me->vl==mv) return 0;
     tnsMEdge* me1=tnsMMeshNewEdge(mo),*me2=tnsMMeshNewEdge(mo);
     me1->fl=me2->fl=me->fl; me1->fr=me2->fr=me->fr;
-    me1->vl=me->vl; me1->vr=mv; me2->vl=mv; me2->vr=me->vr;
+    tnsMMeshEdgeAssignVerts(me1,me->vl,mv); tnsMMeshEdgeAssignVerts(me2,mv,me->vr);
     laListItemPointer* lipa1=memAcquireSimple(sizeof(laListItemPointer)),*lipa2=memAcquireSimple(sizeof(laListItemPointer)); lipa1->p=me1; lipa2->p=me2;
     laListItemPointer* lipb1=memAcquireSimple(sizeof(laListItemPointer)),*lipb2=memAcquireSimple(sizeof(laListItemPointer)); lipb1->p=me1; lipb2->p=me2;
-    for(laListItemPointer* lip=me->fl->l.pFirst;lip;lip=lip->pNext){
+    if(me->fl) for(laListItemPointer* lip=me->fl->l.pFirst;lip;lip=lip->pNext){
         tnsMEdge*ie=lip->p; if(ie!=me) continue; if(tnsMMeshLoopIsInverted(lip)){ LA_SWAP(laListItemPointer*,lipa1,lipa2); }
         lstInsertItemBefore(&me->fl->l,lipa1,lip); lstInsertItemBefore(&me->fl->l,lipa2,lip); lstRemoveItem(&me->fl->l, lip); memLeave(lip); me->fl->looplen++;
     }
-    for(laListItemPointer* lip=me->fr->l.pFirst;lip;lip=lip->pNext){
+    if(me->fr) for(laListItemPointer* lip=me->fr->l.pFirst;lip;lip=lip->pNext){
         tnsMEdge*ie=lip->p; if(ie!=me) continue; if(tnsMMeshLoopIsInverted(lip)){ LA_SWAP(laListItemPointer*,lipb1,lipb2); }
         lstInsertItemBefore(&me->fr->l,lipb1,lip); lstInsertItemBefore(&me->fr->l,lipb2,lip); lstRemoveItem(&me->fr->l, lip); memLeave(lip); me->fr->looplen++;
     }
-    if(tnsMMeshEdgeShareVert(me1, ref_e1v_optional)){ *r_e1=me1; *r_e2=me2; }else{ *r_e1=me2; *r_e2=me1; }
+    me->fl=me->fr=0; tnsMMeshRemoveEdgeFace(mo,me);
+    if(ref_e1v_optional&&tnsMMeshEdgeShareVert(me1, ref_e1v_optional)){ if(r_e1)*r_e1=me1; if(r_e2)*r_e2=me2; }else{ if(r_e1)*r_e1=me2; if(r_e2)*r_e2=me1; }
     return 1;
 }
+tnsMVert* tnsMMeshEdgeInsertVertAt(tnsMeshObject* mo, tnsMEdge* me, real at, tnsMVert* ref_e1v_optional, tnsMEdge** r_e1, tnsMEdge** r_e2){
+    if(!me||at<=0||at>=1) return 0; tnsMVert* mv=tnsMMeshNewVert(mo);
+    tnsInterpolate3dv(me->vl->p, me->vr->p, at, mv->p);
+    if(tnsMMeshEdgeInsertVert(mo,me,mv,ref_e1v_optional,r_e1,r_e2)) return mv; return 0;
+}
 void tnsMMeshRemoveFaceOnly(tnsMeshObject* mo, tnsMFace* mf){
     if(!mf) return; tnsMEdge* me;
     while(me=lstPopPointerLeave(&mf->l)){ if(me->fl==mf) me->fl=0; elif(me->fr==mf) me->fr=0; }
@@ -388,6 +440,7 @@ void tnsClearMMesh(tnsMeshObject* mo){
 }
 
 void tnsMMeshFromMesh(tnsMeshObject* mo){
+    tnsClearMMesh(mo);
     tnsEdgeHash* eh=tnsCreateEdgeHash(mo->totv); //mo->totmv=mo->totv; mo->totme=mo->tote; mo->totmf=mo->totf;
     for(int i=0;i<mo->totf;i++){
         tnsFace* f=&mo->f[i];
@@ -424,20 +477,24 @@ void tnsMeshFromMMesh(tnsMeshObject* mo){
         }
     }
     mo->totv=mo->totmv; mo->totf=mo->totmf;
+    mo->maxv=mo->totv; mo->maxe=mo->tote; mo->maxf=mo->totf;
+    if((!mo->maxv) && mo->v) arrFree(&mo->v, &mo->maxv);
+    if((!mo->maxe) && mo->e) arrFree(&mo->e, &mo->maxe);
+    if((!mo->maxf) && mo->f) arrFree(&mo->f, &mo->maxf);
     tnsClearMMesh(mo);
 }
 
 void tnsMeshEnterEditMode(tnsMeshObject* mo){
-    if(mo->Mode==TNS_MESH_EDIT_MODE || mo->mv.pFirst) return;
+    if(mo->Mode==TNS_MESH_EDIT_MODE) return;
     tnsMMeshFromMesh(mo);
     mo->Mode = TNS_MESH_EDIT_MODE;
-    tnsInvaliateMeshBatch(mo);
+    tnsInvalidateMeshBatch(mo);
 }
 void tnsMeshLeaveEditMode(tnsMeshObject* mo){
     if(mo->Mode==TNS_MESH_OBJECT_MODE) return;
     tnsMeshFromMMesh(mo);
     mo->Mode = TNS_MESH_OBJECT_MODE;
-    tnsInvaliateMeshBatch(mo);
+    tnsInvalidateMeshBatch(mo);
 }
 
 int tnsMMeshAnySelected(tnsMeshObject* mo){
@@ -493,14 +550,15 @@ void tnsMMeshEnsureSelection(tnsMeshObject* mo, int SelectMode){
     elif(SelectMode==LA_CANVAS_SELECT_MODE_EDGES){ tnsMMeshEnsureSelectionFromEdges(mo); }
 }
 
-tnsMeshObject *tnsCreateMeshPlane(tnsObject *under, char *Name, real AtX, real AtY, real AtZ, real size){
-    tnsMeshObject *mo; tnsWorld *w = &T->World;
-
-    mo = memAcquireHyper(sizeof(tnsMeshObject));
+tnsMeshObject *tnsCreateMeshEmpty(tnsObject *under, char *Name, real AtX, real AtY, real AtZ){
+    tnsMeshObject *mo = memAcquireHyper(sizeof(tnsMeshObject));
     tnsInitObjectBase(&mo->Base, under, Name, TNS_OBJECT_MESH, AtX, AtY, AtZ, 0, 0, 0, 1.0f, TNS_ROTATION_XYZ_EULER, 1.0f);
+    return mo;
+}
+tnsMeshObject *tnsCreateMeshPlane(tnsObject *under, char *Name, real AtX, real AtY, real AtZ, real size){
+    tnsMeshObject *mo=tnsCreateMeshEmpty(under, Name, AtX, AtY, AtZ);
     tnsInitMeshPlane(mo, size);
-    tnsInvaliateMeshBatch(mo);
-
+    tnsInvalidateMeshBatch(mo);
     return mo;
 }
 

+ 2 - 2
source/lagui/la_util.c

@@ -133,12 +133,12 @@ void* arrElement(void* head, int i, int size){
     return ((char*)head+size*i);
 }
 int arrEnsureLength(void** head, int next, int* max, size_t ElementSize){
-    int UseMax=*max;
+    int UseMax=*max; int nocopy=(!UseMax);
     if(next>=UseMax){
         if(!UseMax){ UseMax=50; }
         int AllocMax=next>(UseMax*2)?next:(UseMax*2);
         void* data = CreateNew_Size(ElementSize* AllocMax);
-        if((*head) || next){ memcpy(data, *head, ElementSize*UseMax); }
+        if(((*head) || next)&&(!nocopy)){ memcpy(data, *head, ElementSize*UseMax); }
         if(*head) free(*head);
         *head=data;
         *max= AllocMax;

+ 180 - 32
source/lagui/resources/la_modelling.c

@@ -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);
 }

+ 5 - 4
source/lagui/resources/la_properties.c

@@ -677,7 +677,7 @@ void tnspropagate_Object(tnsObject* o, laUDF* udf, int force){
 void tnstouched_Object(tnsObject *o, int hint){
     printf("touched\n");
     if(o->Type==TNS_OBJECT_MESH && (hint&TNS_HINT_GEOMETRY)){
-        tnsInvaliateMeshBatch(o);
+        tnsInvalidateMeshBatch(o);
     }
     laNotifyUsers("tns.world");
 }
@@ -685,6 +685,7 @@ void tnstouched_Object(tnsObject *o, int hint){
 int tnsget_MeshObjectVertSize(tnsMeshObject* o){ return o->totv*sizeof(tnsVert);/*can't use maxv*/ }
 int tnsget_MeshObjectEdgeSize(tnsMeshObject* o){ return o->tote*sizeof(tnsEdge);/*can't use maxv*/ }
 void* tnsget_MeshObjectFaceRaw(tnsMeshObject* o, int* r_size, int* r_is_copy){
+    if(o->Mode==TNS_MESH_EDIT_MODE) return;
     int* arr=0; int next=0,max=0; int i=0;
     arrEnsureLength(&arr, i, &max, sizeof(int));
     arr[i]=o->totf; i++;
@@ -701,10 +702,10 @@ void* tnsget_MeshObjectFaceRaw(tnsMeshObject* o, int* r_size, int* r_is_copy){
     return arr; 
 }
 void tnsset_MeshObjectFaceRaw(tnsMeshObject* o, int* data, int s){
-    int totf=data[0]; int i=1;
+    if(!data) return; int totf=data[0]; int i=1;
     if(o->f){
         for(int fi=0;fi<o->totf;fi++){
-            if(o->f[fi].loop) free(o->f[fi].loop);
+            if(o->f[fi].loop){ free(o->f[fi].loop); o->f[fi].loop=0; }
         }
         arrFree(&o->f, &o->maxf); o->totf=0; 
     }
@@ -866,7 +867,7 @@ void la_RegisterInternalProps(){
 
         p = laAddPropertyContainer("la_main", "LA Root", "LA Root Structure", L'🖴', 0, sizeof(LA), 0, 0, 2|LA_PROP_OTHER_ALLOC);{
             laAddSubGroup(p, "logs", "Logs", "Application logs", "la_log",0, 0, laui_LogItem, -1, 0, 0, 0, 0, 0, 0, offsetof(LA, Logs), LA_UDF_IGNORE|LA_READ_ONLY);
-            laAddSubGroup(p, "differences", "Differences", "Difference stack (for undo/redo)", "la_difference",0, 0, 0, -1, 0, 0, 0, 0, 0, 0, offsetof(LA, Differences), LA_UDF_IGNORE|LA_READ_ONLY);
+            laAddSubGroup(p, "differences", "Differences", "Difference stack (for undo/redo)", "la_difference",0, 0, 0, offsetof(LA, HeadDifference), 0, 0, 0, 0, 0, 0, offsetof(LA, Differences), LA_UDF_IGNORE|LA_READ_ONLY);
             
             _LA_PROP_WINDOW=laAddSubGroup(p, "windows", "Windows", "All Windows Under LA Control", "ui_window",0, 0, 0, offsetof(LA, CurrentWindow), laget_FirstWindow, 0, laget_ListNext, 0, 0, 0, offsetof(LA, Windows), 0);
             la_UDFAppendSharedTypePointer("_LA_PROP_WINDOW", _LA_PROP_WINDOW);

+ 16 - 16
source/lagui/resources/la_widgets.c

@@ -1714,7 +1714,7 @@ int OPMOD_IntArrayHorizon(laOperator *a, laEvent *e){
             if (ui->Extra->On){
                 ui->Extra->On = 0;
                 laConfirmInt(a, uit->TargetIndexVali, LA_CONFIRM_DATA);
-                laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+                laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             }
         }else if(ui->Extra->On){
             int SelectOn=(IsVertical ? la_DetectRow(ui, e->y) : la_DetectColumn(ui, e->x, Len)) + 1; 
@@ -1738,7 +1738,7 @@ int OPMOD_IntArrayHorizon(laOperator *a, laEvent *e){
             char* buf=strEndEdit(&uit->Edit, 0);
             int Result; sscanf(buf, "%d", &Result); free(buf);
             laSetIntArraySingle(&ui->PP, ui->Extra->On - 1, Result);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             ui->State = LA_UI_NORMAL;
             uit->Dragging = 0;
             uit->On = 0;
@@ -1834,7 +1834,7 @@ int OPMOD_FloatArrayHorizon(laOperator *a, laEvent *e){
             if (ui->Extra->On){
                 ui->Extra->On = 0;
                 laConfirmFloat(a, uit->TargetIndexValf, LA_CONFIRM_DATA);
-                laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+                laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             }
         }else if(ui->Extra->On){
             int SelectOn=(IsVertical ? la_DetectRow(ui, e->y) : la_DetectColumn(ui, e->x, Len)) + 1; 
@@ -1858,7 +1858,7 @@ int OPMOD_FloatArrayHorizon(laOperator *a, laEvent *e){
             char* buf=strEndEdit(&uit->Edit, 0);
             real Result; sscanf(buf, "%lf", &Result); free(buf);
             laSetFloatArraySingle(&ui->PP, ui->Extra->On - 1, Result);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             uit->Dragging = 0;
             uit->On = 0;
             laRedrawCurrentPanel();
@@ -2069,13 +2069,13 @@ int OPMOD_EnumSelector(laOperator *a, laEvent *e){
             ei = ei->Item.pNext?ei->Item.pNext:((laEnumProp*)ui->PP.LastPs->p)->Items.pFirst;
             laSetEnumArrayIndexed(&ui->PP, ArrTarget, ei->Index);
             laConfirmInt(a,EnumTarget,LA_CONFIRM_DATA);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
         }else{
             if(ArrTarget>=ArrLen){ArrTarget=ArrLen-1;} 
             if(EnumTarget>=EnumLen){EnumTarget=EnumLen-1;}
             laSetEnumArrayIndexedExternal(&ui->PP, ArrTarget, EnumTarget);
             laConfirmInt(a,EnumTarget,LA_CONFIRM_DATA);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             return LA_RUNNING_PASS;
         }
         return LA_RUNNING;
@@ -2245,7 +2245,7 @@ int OPMOD_SingleLineString(laOperator *a, laEvent *e){
         }else if ((e->Type & LA_MOUSEDOWN) == LA_MOUSEDOWN){
             buf=strEndEdit(&uit->Edit, 0);
             laSetString(&ui->PP, buf); free(buf);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             ui->State = LA_UI_NORMAL;
             laRedrawCurrentPanel();
             return LA_FINISHED_PASS;
@@ -2264,7 +2264,7 @@ int OPMOD_SingleLineString(laOperator *a, laEvent *e){
         if (e->key == LA_KEY_ENTER){
             buf=strEndEdit(&uit->Edit, 0);
             laSetString(&ui->PP, buf); free(buf);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             ui->State = LA_UI_NORMAL;
             laRedrawCurrentPanel();
             return LA_FINISHED_PASS;
@@ -2326,7 +2326,7 @@ int OPMOD_MultiString(laOperator *a, laEvent *e){
     if (!laIsInUiItem(ui, e->x, e->y) && !es->Dragging){
         if ((e->Type & LA_MOUSEDOWN) == LA_MOUSEDOWN){
             buf=strGetEditString(es->Edit, 0); laSetString(&ui->PP, buf); free(buf);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             ui->State = LA_UI_NORMAL;
             laRedrawCurrentPanel();
             return LA_FINISHED_PASS;
@@ -2346,7 +2346,7 @@ int OPMOD_MultiString(laOperator *a, laEvent *e){
         if (e->key == LA_KEY_ESCAPE){
             if(ui->State != LA_UI_NORMAL){
                 buf=strGetEditString(es->Edit, 0); laSetString(&ui->PP, buf); free(buf);
-                laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+                laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
                 ui->State = LA_UI_NORMAL; laRedrawCurrentPanel();la_SetMultistringViewRange(ui,es->Edit,bt);
             }
             if(es->Dragging){ es->Dragging=0; es->HeightCoeff=es->TargetIndexVali; laRecalcCurrentPanel();la_SetMultistringViewRange(ui,es->Edit,bt); }
@@ -2492,7 +2492,7 @@ int OPMOD_Collection(laOperator *a, laEvent *e){
                 laRedrawCurrentPanel();
                 laPropPack PP={0}; la_CopyPropPack(&ui->PP, &PP); //needed because layout can be switched after set active.
                 laSetActiveInstance(ui->PP.LastPs->p, ui->PP.LastPs->UseInstance, uil->Instance);
-                laRecordAndPush(&PP,0); laMarkPropChanged(&PP);
+                laRecordAndPushProp(&PP,0); laMarkPropChanged(&PP);
                 laConfirmInt(a, 0, LA_CONFIRM_DATA);
                 return LA_RUNNING_PASS;
             }
@@ -2546,9 +2546,9 @@ int OPMOD_CollectionSelector(laOperator *a, laEvent *e){
         laPanel *p;
         int GX = ui->L, GY = ui->B, GR = ui->R, t = 0;
 
-        if (e->x >= TNS_MAX2(ui->R - LA_RH,(ui->R+ui->L)/2) && e->x < ui->R){
+        if ((!laIsPropertyReadOnly(&ui->PP)) && e->x >= TNS_MAX2(ui->R - LA_RH,(ui->R+ui->L)/2) && e->x < ui->R){
             laSetActiveInstance(ui->PP.LastPs->p, ui->PP.LastPs->UseInstance, 0);
-            laRecordAndPush(&ui->PP,0); laMarkPropChanged(&ui->PP);
+            laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
             return LA_FINISHED;
         }
 
@@ -2652,11 +2652,11 @@ int OPMOD_NodeSocket(laOperator *a, laEvent *e){
         if(pc == LA_PC_SOCKET_IN){
             if(!uit->Ptr2){ if(!tui){ laNodeInSocket* s=ui->PP.EndInstance; s->Source=0; return LA_RUNNING; }
                 laNodeInSocket* s=ui->PP.EndInstance; s->Source=0; memAssignRef(s,&s->Source,soc);
-                s->ColorId=MAIN.NextWireColor; laRecordAndPush(&ui->PP, 0); laMarkPropChanged(&ui->PP);
+                s->ColorId=MAIN.NextWireColor; laRecordAndPushProp(&ui->PP, 0); laMarkPropChanged(&ui->PP);
             }else{
                 if(!tui){
                     laNodeInSocket* s=ui->PP.EndInstance; s->Source=uit->Ptr2; memAssignRef(s,&s->Source,0);
-                    laRecordAndPush(&ui->PP, 0); laMarkPropChanged(&ui->PP);
+                    laRecordAndPushProp(&ui->PP, 0); laMarkPropChanged(&ui->PP);
                 }else{
                     laNodeInSocket* s=soc; if(s->Source){ uit->Ptr2=0; return LA_RUNNING; }
                     memAssignRef(s,&s->Source,uit->Ptr2); s->ColorId=((laNodeInSocket*)ui->PP.EndInstance)->ColorId;
@@ -2668,7 +2668,7 @@ int OPMOD_NodeSocket(laOperator *a, laEvent *e){
         else{
             if(!tui){ return LA_RUNNING; }
             laNodeInSocket* s=soc; s->Source=0; memAssignRef(s,&s->Source,ui->PP.EndInstance);
-            s->ColorId=MAIN.NextWireColor; laRecordAndPush(&tui->PP, 0); laMarkPropChanged(&tui->PP);
+            s->ColorId=MAIN.NextWireColor; laRecordAndPushProp(&tui->PP, 0); laMarkPropChanged(&tui->PP);
         }
         
         uit->Ptr2=0;

+ 41 - 5
source/lagui/resources/la_widgets_viewers.c

@@ -424,9 +424,12 @@ void laDefault3DViewOverlay(laUiItem *ui){
     if (!(uil = ui->Subs.pFirst)) uil = laAddTabPage(ui, "New Group");
 
     c = laFirstColumn(uil);
-    laSplitColumn(uil, c, 0.7);
+    laSplitColumn(uil, c, 0.8);
     cl = laLeftColumn(c, 0);
     cr = laRightColumn(c, 0);
+    laSplitColumn(uil, cl, 0.25);
+    cll = laLeftColumn(cl, 0);
+    clr = laRightColumn(cl, 0);
 
     laShowColumnAdjuster(uil, c);
 
@@ -435,10 +438,25 @@ void laDefault3DViewOverlay(laUiItem *ui){
     laShowItem(uil,cl,&ui->ExtraPP,"select_through");
     laEndRow(uil,b);
 
-    b=laBeginRow(uil,cl,0,0);
-    laShowLabel(uil,cl,"Active Object: ",0,0);
-    laShowItem(uil,cl,&ui->PP,"identifier")->Flags|=LA_UI_FLAGS_PLAIN;
-    laEndRow(uil,b);
+    g = laMakeFoldableGroup(uil, cll, "Tools", 0, 1);{
+        gu = g->Page; gc = laFirstColumn(gu);
+        laSplitColumn(gu, gc, 0.35); gcl = laLeftColumn(gc, 0); gcr = laRightColumn(gc, 0);
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_delete");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_make_parent");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_unparent");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_add");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_grab");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_scale");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_rotate");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_clear_transformations");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_extrude");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_make");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_subdiv");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_add");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_separate");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_combine");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_duplicate");
+    }
 
     g = laMakeFoldableGroup(uil, cr, "Scene Info", 0, 1);{
         gu = g->Page;
@@ -972,6 +990,19 @@ void la_RegisterUiTypesViewerWidgets(){
         laAddOperatorProperty(pc, "_this_M_delete", "Delete", "Delete parts of mesh", "M_delete", 0, 0);
         laAddOperatorProperty(pc, "_this_M_make_parent", "Make parent", "Parent objects to active objects or unparent selected ones", "M_make_parent", 0, 0);
         laAddOperatorProperty(pc, "_this_M_unparent", "Unparent", "Unparent selected objects", "M_unparent", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_add", "Add", "Add objects/primitives", "M_add", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_grab", "Grab", "Grab things and move around", "M_grab", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_scale", "Scale", "Scale selected things", "M_scale", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_rotate", "Rotate", "Rotation selected things", "M_rotate", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_clear_transformations", "Clear Transformations", "Clear object transformation values", "M_clear_transformations", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_extrude", "Extrude", "Extrude parts of the mesh", "M_extrude", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_delete", "Delete", "Delete parts of the mesh", "M_delete", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_make", "Make", "Make mesh primitive from selected ones", "M_make", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_subdiv", "Subdiv", "Subdivide edges", "M_subdiv", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_add", "Add", "Add objects/primitives", "M_add", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_separate", "Separate", "Separate mesh parts", "M_separate", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_combine", "Combine", "Combine mesh objects", "M_combine", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_duplicate", "Duplicate", "Duplicate objects", "M_duplicate", 0, 0);
     }
 
     km = &ct->KeyMapper;
@@ -1002,4 +1033,9 @@ void la_RegisterUiTypesViewerWidgets(){
     laAssignNewKey(km, 0, "M_extrude", LA_KM_SEL_UI_EXTRA, LA_KEY_SHIFT, LA_KEY_DOWN, 'd', "duplicate_only=true;text=Duplicate;");
     laAssignNewKey(km, 0, "M_delete", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'x', 0);
     laAssignNewKey(km, 0, "M_make", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'f', 0);
+    laAssignNewKey(km, 0, "M_subdiv", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'w', 0);
+    laAssignNewKey(km, 0, "M_add", LA_KM_SEL_UI_EXTRA, LA_KEY_SHIFT, LA_KEY_DOWN, 'a', 0);
+    laAssignNewKey(km, 0, "M_separate", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'p', 0);
+    laAssignNewKey(km, 0, "M_combine", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'j', 0);
+    laAssignNewKey(km, 0, "M_duplicate", LA_KM_SEL_UI_EXTRA, LA_KEY_SHIFT, LA_KEY_DOWN, 'd', 0);
 }