Distinct XYZ

Distinct XYZ

MGO-Norsyn
Advocate Advocate
4,063 Views
8 Replies
Message 1 of 9

Distinct XYZ

MGO-Norsyn
Advocate
Advocate

Hi

 

I have a list of XYZ points (obtained from MEP connectors).

How to clean it of duplicates (e.g. XYZs with same coordinates)?

 

I am trying to:

 

var distinctElementConnectors = MyMepUtils.GetALLConnectorsFromElements(elements)
                                          .Where(c => c.IsConnected)
                                          .Distinct(c => c.Origin)
                                          .ToHashSet();

The .Distinct(c => c.Origin) doesn't work because .Distinct doesn't know how to compare XYZs (or does it?).

 

Please help.

0 Likes
Accepted solutions (3)
4,064 Views
8 Replies
Replies (8)
Message 2 of 9

jeremytammik
Autodesk
Autodesk

Dear Michail,

 

Thank you for your very valid query.

 

You are absolutely correct:

 

The .NET API does not have any built-in mechanism to compare the Revit API XYZ objects.

 

However, it is easy to implement, and I have done so several times in different discussion published by The Building Coder.

 

Here are the first and last mentions so far:

 

 

While writing this, I note that The Building Coder samples define the class XyzEqualityComparer in three different modules:

 

 

I hope this helps and suits your needs.

 

Best regards,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 3 of 9

BobbyC.Jones
Advocate
Advocate

Another direction to go, assuming you implemented your own Point3D and Vector3D wrapper classes like we previously discussed is to have those classes implement the IEquatable interface.  Here's a shell of the Point3D class showing my implementation:

 

 

    public class Point3D : IEquatable<Point3D>
    {
        public Point3D(XYZ revitXyz)
        {
            XYZ = revitXyz;
        }

        public XYZ XYZ { get; }

        public double X => XYZ.X;
        public double Y => XYZ.Y;
        public double Z => XYZ.Z;
        
        public bool Equals(Point3D other)
        {
            if (ReferenceEquals(other, null)) return false;
            if (ReferenceEquals(this, other)) return true;

            return X.IsAlmostEqualTo(other.X) &&
                   Y.IsAlmostEqualTo(other.Y) &&
                   Z.IsAlmostEqualTo(other.Z);
        }

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;

return Equals((Point3D) obj);
} public override int GetHashCode() { return Tuple.Create(Math.Round(X, 10), Math.Round(Y, 10), Math.Round(Z, 10)). GetHashCode(); } }

 

Storing Point3D instances in a hashset will give you your distinct set of points.

 

var distinctElementConnectors = MyMepUtils.GetALLConnectorsFromElements(elements)
                                          .Where(c => c.IsConnected)
                                          .Select(c => c.Origin.ToPoint3D())
                                          .ToHashSet();

 

--
Bobby C. Jones
0 Likes
Message 4 of 9

MGO-Norsyn
Advocate
Advocate

Thank you for your answer. As always, the solution is already implemented somewhere...

 

But more specifically I was thinking of a way to implement a routine to handle this comparison with LINQ.

0 Likes
Message 5 of 9

MGO-Norsyn
Advocate
Advocate

Thank you for replying.

 

Regrettably, I haven't had time to look into your wrapper classes, but I notice that you get a collection of distinct points, while I am interested in getting a collection of distinct connectors where distinction is based on if the origins coincide. 😉

0 Likes
Message 6 of 9

jeremytammik
Autodesk
Autodesk
Accepted solution

I define a comparer class that does the job:

 

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/U...

 

 

    /// <summary>
    /// Compare Connector objects based on their location point.
    /// </summary>
    public class ConnectorXyzComparer : IEqualityComparer<Connector>
    {
      public bool Equals( Connector x, Connector y )
      {
        return null != x
          && null != y
          && IsEqual( x.Origin, y.Origin );
      }

      public int GetHashCode( Connector x )
      {
        return HashString( x.Origin ).GetHashCode();
      }
    }

    /// <summary>
    /// Get distinct connectors from a set of MEP elements.
    /// </summary>
    public static HashSet<Connector> GetDistinctConnectors(
      List<Connector> cons )
    {
      return cons.Distinct( new ConnectorXyzComparer() )
.ToHashSet(); }

I implemented that in The Building Coder samples release 2018.0.134.1:

 

https://github.com/jeremytammik/the_building_coder_samples/releases/tag/2018.0.134.1

 

Here is the diff to the previous release:

 

https://github.com/jeremytammik/the_building_coder_samples/compare/2018.0.134.0...2018.0.134.1

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 7 of 9

jeremytammik
Autodesk
Autodesk
Accepted solution

Edited and summarised for posterity:

 

http://thebuildingcoder.typepad.com/blog/2017/08/birthday-post-on-the-xyz-class.html

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 8 of 9

Anonymous
Not applicable

this is how i do to sort and distinct list of XYZ:

 

Debug.Print("All Points");
i = 0;
foreach (XYZ xyz in lstxyzPoints) {
i++;
Debug.Print(i.ToString() + " - " + xyz.ToString());
}
List<XYZ> lstxyzPointsDistinct = GetDistinctPoints(lstxyzPoints);
Debug.Print("Distinct Points");
i = 0;
foreach (XYZ xyz in lstxyzPointsDistinct) {
i++;
Debug.Print(i.ToString() + " - " + xyz.ToString());
}

 

 

private List<XYZ> GetDistinctPoints(List<XYZ> lstxyz) {
List<XYZ> lstxyzRound = new List<XYZ>();
int i = 1, j = 1;

//round the points to 6 decimal places (you may not need this)
foreach (XYZ xyz in lstxyz) {
lstxyzRound.Add(new XYZ(Math.Round(xyz.X, 6), Math.Round(xyz.Y, 6), Math.Round(xyz.Z, 6)));
}

//order by Z,X,Y (depends on your need)
lstxyzRound = lstxyzRound.OrderBy(p => p.Y).ToList();
lstxyzRound = lstxyzRound.OrderBy(p => p.X).ToList();
lstxyzRound = lstxyzRound.OrderBy(p => p.Z).ToList();

//remove points from list if duplicates
bool blnDuplicate = true;
while (blnDuplicate) {
blnDuplicate = false;
for (i = j; i < lstxyzRound.Count; i++) {
if (lstxyzRound[i - 1].DistanceTo(lstxyzRound[i]) < 0.0001) {
blnDuplicate = true;
j = i;
break;
}
}
if (blnDuplicate) {
lstxyzRound.RemoveAt(j);
}
}
return lstxyzRound;
}

0 Likes
Message 9 of 9

MGO-Norsyn
Advocate
Advocate
Accepted solution

While implementing Jeremy's ConnectorXYZComparer, I've discovered that some connector groups can have much larger tolerances than the 1.0e-9 that Jeremy uses, which gives false negatives. For details see my post in another thred.

0 Likes