Remove Item on DataTree,not the entire path, in C#

Hi all,

I can’t find a suiting methode for removing an item on a specific branch.

Am I missing something, how could I do it?

Thanks everybody!

Check the data tree class

Thanks, I had searched there.
Anyway I think my intial problem needs another approch.
Thanks for the advice anyways.

Just skip it with a condition. What would remove mean? Set to null, but you would just have to skip anyways otherwise you would get the classical object reference not set to an instance of an object error.

Skipping is what I did in the meantime.
The biggest problem may be asking the correct question. Or recognizing the root of the problem. Well for me at least.

For everyone, recognizing the root of the problem is always the biggest thing

1 Like

or as a friend always says: the problem sits in front of the screen

Anyhow, to delete an object simply means, to literally set its value to null. In a Linked List for example, you will set the value of a node to null and the free memory immediately, if you are in c++. What null really means is that an object does not have a valid reference any more, this is why in an unmanaged environment you would have to immediately clean your memory. Going back to the Linked List example after freeing memory, you would then change the pointer to reference a valid object.

C# is a managed language, meaning you don’t have to deal with memory yourself. When you set a value to null, the GC will eventually pop it off the heap because null means that something does not hold any valid memory address any longer, meaning the program does not need it. Since Data Tree does not provide this functionality, you should either set the item you want to delete to null (If it’s a reference type) and then always making sure you iterate the tree with a condition to skip the unwanted item. If it’s a reference type, skip it if it is null

The other option is to create a new Data Tree with only the items that you need, now this could be memory consuming and computationally expensive, because by definition to iterate a tree your time complexity would always be O(N^2) . Memory consuming because you would be creating a new tree everytime you want to delete an item. This is why Arrays have such bad performance when you want to delete or add an element, because you have to create a new Array. For example :

int[] numbers = { 1, 3, 4, 9, 2 };
 int numToRemove = 4; 
numbers = numbers.Where(val => val !=numToRemove).ToArray();

This is simply because of how arrays are stored in memory, each item occupies a continuous memory block. For this reason an Array can’t really grow or shrink because its impossible for the Compiled Language Runtime to know if a memory block near the location of the Array will be availabe or not. The opposite is true with dynamic data structures, a List for example. Each element in a List does not recide in continuous blocks of memory, instead they are randomly located in memory. This makes it easier to delete or add items. Same as with the Linked List in fact if I am not mistaken a List implements a Linked List behind scenes.

Hi @rawitscher-torres,

thanks again for taking the time and going so much into detail!

I am trying to practice the Datatree usage as well as the usage of mesh connectivity, so I started recreating a MeshMaze using the recursive backtracking methode.

In order to find the neighborFaces for each current face I am searching on the MeshFaceConnectivityTree with the Path(faceIndex,neighborFaceIndex) on each iteration.

I did not find a better solution, several times when I had this issue, I ended up using it this way.

The whole code is the following:

 private void RunScript(Mesh mesh, int y, int seed, ref object MazePath, ref object mFTree, ref object A)
  {
    int fC = mesh.Faces.Count;
    int mV = mesh.TopologyVertices.Count;

    List<int> indices = new List<int>(fC);// filling a list with a index per meshFace
    for (int i = 0; i < fC; i++)
    {
      indices.Add(i);
    }

    var MFTree = getMFTree(mesh);// get the MeshFace Connectivity Tree

    Random random = new Random(seed);

    var mazePath = new List<int>();//initialize list for the path

    int nb = 0;//initialize neighbor

    int currInd = random.Next(fC); //initialize currentIndex by picking randomly one cell to start
    mazePath.Add(currInd);//add first position to path

    for (int i = 0; i < y; i++)
    {

      List<int> neighborFaces = getNeighborFaces(currInd, MFTree);// getting all the neigbors for the current cell


      if (neighborFaces.Count >= 1)//if there is at least one neighbor, pick one free cell
      {
        indices.Remove(currInd);// !remove the current from the possible indices!
        nb = neighborFaces[random.Next(neighborFaces.Count)];// get randomly one neighbor
        mazePath.Add(currInd);// add it to the list

        for (int p = 0; p < neighborFaces.Count; p++)// remove for all neighbors of current as as possible neighbor
        {
          MFTree.RemovePath(neighborFaces[p], currInd);
        }
        currInd = nb;// set the neighbor cell as actual cell
      }

      else
      {
        mazePath.Add(currInd);// add id to the list
        currInd = mazePath[mazePath.Count - 1];// go one face back and try again 
      }
    }
    MazePath = mazePath;
    mFTree = MFTree;
  }

  // <Custom additional code> 

  public DataTree<int> getMFTree (Mesh mesh)
  {
    var MFTree = new DataTree<int>();

    for (int i = 0; i < mesh.Faces.Count; i++)
    {
      int[] adj = mesh.Faces.AdjacentFaces(i);
      for (int j = 0; j < adj.Length; j++)
      {
        MFTree.Add(adj[j], new GH_Path(i, adj[j]));
      }

    }
    return MFTree;
  }

  public List<int> getNeighborFaces(int currInd, DataTree<int> MFTree)
  {
    List<int> neighborFaces = new List<int>();
    for (int p = 0; p < MFTree.BranchCount; p++)
    {
      if (MFTree.Paths[p].Indices[0] == currInd)
      {
        neighborFaces.Add(MFTree.Branch(p)[0]);
      }
    }
    return neighborFaces;
  }

It is failling once it has no neighborFace left( the else part is not working)

Maybe you could give some advice to finish it or general improvements? They would be really welcome !
Thanks and have a nice eveneing!
File:20200509_Maze3.gh (9.3 KB)

EDIT: I just finished it

BTW: this is not the way to cut the Maze mustard.

BTW: I have various Maze things (+ real-time Maze Solvers) but the practice is closed (untill the end of days I guess). So … you must wait (until the end of days).


Moral: hope dies last.

Maybe not te way to cut the mustard but a way to skin the cat I assume.

Was looking for some exercise to do with mesh connectivity.
If you have some ideas for something sticked more to the architectural practice, any ideas are welcome!

Er … hard to imagine a practice doing Maze.

On the other hand here’s the thing of things: given a mesh (wait: in fact a mesh List) do a W truss. This is architecture (and the most challenging task to be honest). Then comes the real pain: the envelope. Then the client rejects the concept and opts for a liquid nonsence.

BTW: Obviously a truss without connectivity is a Ducati without 200++ ponies.

sounds as always.

I had a try on creating steelnodes on a mesh(like on cutty sark from grimshaw architects), it was a nice exercise.As architect I also would try to design something simpler(not every node different), but these projects are nice to learn coding and some logic.

Go for the hot (and rational) stuff. Forget the liquid madness (naive and pointless to the max).

Roadmap:

  1. Work on mesh Lists … meaning that all the trees (connectivity) MUST have min 2 dimensions (some must have 3-4).
  2. For each mesh (triangulated for max rigitidy) we have 2 node Lists : the base (i.e. mesh topo vertices meaning … TopologyVertices connectivity) and the top (i.e. points at face center + face center normal * W value … meaning Faces connectivity).
  3. For each mesh we have 3 strut Lists (the base, the W and the top). Some times the W and the base struts differ … so do things by the book and deal with the general case.
  4. We must relate nodes to struts and nodes to nodes.Plus trusses arrive on site pre assempled in “segments” (as big as a truck can carry and a crane to lift).
  5. We must check the angles (clash situations) as well: for each node test all the combos towards nodes on the same “layer” and the other. If you skip this … go do some other thing. This is tricky since involves sorting as well (there’s a Topology Vertices Method that does it).
  6. Each strut must be a void assembly of 2 sleeves, 2 cones and the strut itself (obvioulsy Instance Definitions). The R output file must be 100% compatible with a given Assemply/Component schema as found in the serious AEC BIM aps (Revit is not among them) … so check what STEP 214 can do.

If you can deal with this (meaning on the fly mods in nodes that yield clash situations etc etc) … well … there’s nothing that you can’t do.

Moral: long is the path (and hilly)

Hi @PeterFotiadis,
thanks for the exercise!
Could you upload some clearer images maybe or a baked model?
I assume the 3. strut is just the meshedge?

I am not sure if it’s rebuilt correctly:


It will need exception handling for the naked edges I think.
I extract the Topology trees but I construct the lines in a loop directly and not over the tree.

 DataTree<Line> baseStruts = new DataTree<Line>();
    DataTree<Point3d> centers = new DataTree<Point3d>();
    for (int i = 0; i < mesh.Faces.Count; i++)
    {
      Point3d center = mesh.Faces.GetFaceCenter(i);
      Vector3d normal = mesh.FaceNormals[i];
      center += normal * h;
      centers.Add(center, new GH_Path(i));
      int[] topoVerts = mesh.Faces.GetTopologicalVertices(i);
      for (int t = 0; t < topoVerts.Length; t++)
      {
        Line line = new Line(center, mesh.TopologyVertices[topoVerts[t]]);
        baseStruts.Add(line, new GH_Path(i, topoVerts[t]));
      }
    }


    DataTree<Line> secondStruts = new DataTree<Line>();
    for (int i = 0;i < mesh.TopologyEdges.Count;i++)
    {
      int[] connFaces = mesh.TopologyEdges.GetConnectedFaces(i);
      if (connFaces.Length == 2)
      {
        Line line = new Line(centers.Branch(connFaces[0])[0], centers.Branch(connFaces[1])[0]);
        secondStruts.Add(line, new GH_Path(connFaces[0], connFaces[1]));
      }
    }

Probably a stupid question: the angles are from the interior meshedges, in a plane which is a average of all the face frames which belong to that node ?

Forget angles for the moment and stick to the basic things.

In trusses we sample the items in question (edges, nodes) in Lists and then we use connectivity for accessing anything.

So for your single truss you need 5 Lists (nodes: base, top + edges : base, W, top ). Obviously if you deal with mesh Lists (or a disjoined mesh) you’ll need a way to manage the Lists: Trees, that is.

So work with a mesh List and do the 5 item Trees and the required connectivity (VV, VE, EV).

Note: a viz C# (I.e. see what we have on a per node basis) is critical since later on we need to check the angles and spot areas where clash situations happen.

Note: Spot the fact that the attached is a 2Mb file that contains only Lines (the axis). Imagine what happens if we invite the real things to the party. In fact we don’t need these since R is not a BIM app (only an amateur could attempt to use R for doing real-life BIM) … but this means that you should test what to export (coordinate systems in fact) and what your BIM app can read. This means that real-life is a quite complex thingy.

Moral: NEVER attempt to do anything similar without Instance Definitions .

Mess_toTruss_UAE_34B.3dm (2.1 MB)

BTW: in // with the above you’ll need some things like these (for the min angle clash checks … depending upon what member sizes you deal with):

Hey Peter,
thanks for the file and the explanation!
Unfortunately I still have some ambiguities:

  1. doing the 5 lists 4 lists are clear to me(I think,hehe):
    -baseNode->Path(mesh,Topovertex)
    -topNodes->Path(mesh,Topovertex)
    -bottomStruts->Path(mesh,TopoEdge)
    -wStruts->Path(mesh,Topovertex)
    -but the 5. ,if it was a list(like for one mesh you said) how would I be able to identify each line, as I understand it should lie on a path like (mesh,vertexFrom,VertexTo)
    Edit: now I see: it’s->Path(mesh,TopoEdge)

That’s also how I got it in GH:

With the connectivity trees I also have a (essential it is)question:

  1. dimension is the mesh
  2. dimension is the vertex(until here no questions)
  3. dimension: how can a vertex be part of bottom, top or w? I thought it’s the same vertex.How to detemine which int the path gets at this dimension(which Vertex gets w?)
  4. the same as 3.

Probably I completly misunderstand that trees.
I would have done:
1.dimension mesh
2.vertex index

hope dies last, haha

In fact managing nested collections via GH DataTrees is not a good thing since the C# core (the structure) would be associated to that internal to GH structure.

I.e a bad thing because the goal is to use R/GH as a rather small part in a big arsenal of BIM aps (main and verticals) … meaning that the code must be as GH neutral as possible: calling R geometry Methods is one thing (meaning that the code is portable (kinda) to other aps) … but what matters here is the management of the collections.

Anyway I’ll prepare an entry level take on these 5 Lists/Trees matter (minus internal tricky stuff). All that if the laptop works and if there’s no wind and if the social Dist thingy is the correct one (I doubt).

Screen Shot 097

Moral: hope should die first.

UPDATE:

There’s wind … but I found some screenshots related with that matter.

First you should use as many public stuff as possible (avoid locomotive long Method calls - we use that kind of approach in recursion as well):

Then use mini Methods to address each task separately:

Nice! Thanks!

Will prepare my file this weekend,most seems straight forward until now.

Hi Peter,
my laptop broke and it was a bit difficult to get the sparepart in mexico in corona times.

But finally I started to work on the excercise and I think your tree indexing is now clearer to me.Nevertheless I still have some questions:

  1. top egdes ->If I iterate over the faces, how can the(obvious)duplicates be avoided?
  2. sort method-> why is the verteIndex as input for this function? Could not find a use for it.
  3. aList -> is this the a faceArea list? I tried to implement it, but it still fails(It is really hard to debug without debugger)

That is what I was able to do so far:

 private void RunScript(List<Mesh> meshes, int wMode, double wDepth, double wMin, double wMax, ref object VB, ref object VT, ref object EB, ref object EW, ref object ET, ref object VV)
  {

    initTrees();

    for (int i = 0; i < meshes.Count; i++)
    {
      mIndex = i;
      MTV = meshes[i].TopologyVertices;
      MF = meshes[i].Faces;
      MTE = meshes[i].TopologyEdges;
      initLists();


      getBottomVertices(meshes[i]);
      vBTree.AddRange(vB, new GH_Path(mIndex));

      List<double>  aList = getAList(meshes[i]);
      getTopVertices(meshes[i],aList, wMode, wDepth, wMin, wMax);
      vTTree.AddRange(vT, new GH_Path(i));

      getBottomEdges();
      eBTree.AddRange(eB, new GH_Path(i));

      getWAndTopEdges(meshes[i]);
      eWTree.AddRange(eW, new GH_Path(i));
      eTTree.AddRange(eT, new GH_Path(i));

    }

    VB = vBTree;
    VT = vTTree;
    EB = eBTree;
    EW = eWTree;
    ET = eTTree;
    VV = vVConn;
  }

  // <Custom additional code> 

  //::::::::::setting up lists and trees::::::::::

  //declaring variables
  public int mIndex;


  //declaring the lists
  public List<Line> eB; // edges Bottom
  public List<Line> eW; // edges W
  public List<Line> eT; // edges Top
  public List<Point3d> vB; // vertices Bottom
  public List<Point3d> vT; // vertices Top
  public Rhino.Geometry.Collections.MeshTopologyVertexList MTV ;
  public Rhino.Geometry.Collections.MeshTopologyEdgeList MTE;
  public Rhino.Geometry.Collections.MeshFaceList MF ;


  //declaring the trees
  DataTree<Line> eBTree; // edges Bottom
  DataTree<Line> eWTree;// edges W
  DataTree<Line> eTTree;// edges Top
  DataTree<Point3d> vBTree; // vertices Bottom
  DataTree<Point3d> vTTree; // vertices Top
  DataTree<int> vVConn = new DataTree<int>();
  DataTree<int> eVConn = new DataTree<int>();
  DataTree<int> vEConn = new DataTree<int>();



  //::::::::::geometrical methods::::::::::

  //BottomVertices
  public void getBottomVertices(Mesh mesh)
  {
    for (int i = 0; i < MTV.Count; i++)
    {
      vB.Add(MTV[i]);

      int[] adjV = MTV.ConnectedTopologyVertices(i, true);
      vVConn.AddRange(adjV, new GH_Path(mIndex, i, 0));

      int[] adjF = MTV.ConnectedFaces(i);
      sortFaceIndices(ref adjV, adjF);
      vVConn.AddRange(adjF, new GH_Path(mIndex, i, 0, 1));
    }
  }


  //TopVertices
  public void getTopVertices(Mesh mesh,List<double>aList ,int wMode, double w, double wMin, double wMax)
  {
    for (int i = 0; i < MF.Count; i++)
    {
      Point3d center = mesh.Faces.GetFaceCenter(i);
      Vector3d normal = mesh.FaceNormals[i];

      if(wMode == 1) center += normal * w;
      else center += normal * remap(aList[i], aList.Min(), aList.Max(), wMin, wMax) * w;

      vT.Add(center);

      int[] adjF = MF.AdjacentFaces(i);
      vVConn.AddRange(adjF, new GH_Path(mIndex, i, 1));

      int[] adjV = MTV.IndicesFromFace(i);
      vVConn.AddRange(adjV, new GH_Path(mIndex, i, 1, 0));
    }
  }


  //BottomEdges
  public void getBottomEdges()
  {
    for (int i = 0; i < MTE.Count; i++)
    {
      eB.Add(MTE.EdgeLine(i));
      IndexPair neighbourVtx = MTE.GetTopologyVertices(i);
      eVConn.Add(neighbourVtx.I, new GH_Path(mIndex, i, 0));
      eVConn.Add(neighbourVtx.J, new GH_Path(mIndex, i, 0));

      vEConn.Add(i, new GH_Path(neighbourVtx.I, 0));
      vEConn.Add(i, new GH_Path(neighbourVtx.J, 0));
    }

  }



  public List<double> getAList(Mesh mesh)
  {
    List<double> aList = new List<double>();
    for (int f = 0; f < mesh.Faces.Count; f++)
    {
      var newmesh = new Mesh();
      newmesh.Vertices.Add(mesh.Vertices[mesh.Faces[f].A]);
      newmesh.Vertices.Add(mesh.Vertices[mesh.Faces[f].B]);
      newmesh.Vertices.Add(mesh.Vertices[mesh.Faces[f].C]);
      if(mesh.Faces[f].IsQuad) newmesh.Vertices.Add(mesh.Vertices[mesh.Faces[f].D]);

      if(mesh.Faces[f].IsTriangle) newmesh.Faces.AddFace(0, 1, 2);
      if(mesh.Faces[f].IsQuad) newmesh.Faces.AddFace(0, 1, 2, 3);
      var amp = AreaMassProperties.Compute(newmesh);
      aList.Add(amp.Area);
    }
    return aList;
  }

  public void getWAndTopEdges(Mesh mesh)
  {
    for (int i = 0; i < MF.Count; i++)
    {
      Point3d center = vT[i];
      List<int> fv = vVConn.Branch(mIndex, i, 1, 0);//vertices on this face
      for (int v = 0; v < fv.Count; v++)
      {
        eW.Add(new Line(center, MTV[fv[v]]));
      }

      List<int> nf = vVConn.Branch(mIndex, i, 1);//neighborFaces
      for (int n = 0; n < nf.Count; n++)
      {
        eT.Add(new Line(center, vT[nf[n]]));
      }
    }
  }

  //::::::::::non-geometrical methods::::::::::

  //SortFaceIndices
  public void sortFaceIndices(ref int[] adjF, int[] adjV)
  {
    int vC = adjV.Length;
    int fC = adjF.Length;

    int vCount = vC;
    if(vC > fC) vCount--;

    Array.Sort(adjV, adjF);
  }

  //Remap
  public double remap(double oldValue, double oldMin, double oldMax, double newMin, double newMax)
  {
    return -(oldValue - oldMin) / ((oldMax - oldMin) * (newMin - newMax)) + newMin;
  }

  // Initialize Lists
  public void initLists()
  {
    eB = new List<Line>();
    eW = new List<Line>();
    eT = new List<Line>();
    vB = new List<Point3d>();
    vT = new List<Point3d>();    
  }

  // Initialize Trees
  public void initTrees()
  {
    eBTree = new DataTree<Line>(); // edges Bottom
    eWTree = new DataTree<Line>();// edges W
    eTTree = new DataTree<Line>();// edges Top
    vBTree = new DataTree<Point3d>(); // vertices Bottom
    vTTree = new DataTree<Point3d>(); // vertices Top
  }

Thanks again for taking the time, really appreciated.

File:20200523_3dTruss.gh (15.7 KB)