SelDup equivalent in python

Hi Will - here are a few answers. I got curious and had to do a little investigating…

  1. Don’t worry about it. All the numbers in Rhino are floats, that’s not a problem necessarily.

  2. Fractions are tricky for computers. This isn’t a python issue, anytime you are storing fractional value as a series of 1s and 0s it gets complicated. https://en.wikipedia.org/wiki/Floating-point_arithmetic is a good start. If you really want to get into it try: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

  3. Rhino has to worry about this issue too - that’s why each document has an “absolute tolerance” defined. If you give Rhino two points (each created by a different crazy series of trig transformations) and ask Rhino if they are the same (say, with selDup) then Rhino uses the document tolerance to answer. Say you create one point at (1.0mm,2.0mm,3.0mm) and one point at (1.0mm,2.0mm,3.00001mm) - if your document tolerance is set to 0.1mm then selDup will classify them as coincident and select one. If your document tolerance is set to 0.0000000001mm then they will be seen as distinct.

  4. Rhinoscript has some tools for dealing with very close values - see, for example rs.IsVectorTiny(). A test of vector equality between vecA and vecB could be:
    rs.IsVectorTiny(rs.VectorCrossProduct(vecA, vecB)).

  5. It also depends on the scale of what you’re trying to do. rs.IsVectorTiny looks like it is hard coded to 1E-12, which for architectural applications is super small. Maybe you only need 1mm accuracy, or .1mm. In this case I would do all your transformations at full floating point accuracy, and then round the final value to your (lower) actual accuracy. Though technically imperfect(*) this approach is probably OK. Here’s a quick example:

     import rhinoscriptsyntax as rs
     import Rhino.Geometry
     
     #copy these 3 functions:
     def vector_round(vec, tolerance = 2):
         return Rhino.Geometry.Vector3d(
             round(vec.X,tolerance),
             round(vec.Y, tolerance),
             round(vec.Z, tolerance))
         
     def tuple_3_vectors_rounded(tup):
         return (vector_round(tup(0)), vector_round(tup(1)), vector_round(tup(2)))
         
     
     def append_rounded_tuple(workingSet, newTuple, tolerance = 2):
         workingSet.add( (vector_round(newTuple[0], tolerance),
                             vector_round(newTuple[1], tolerance),
                             vector_round(newTuple[2], tolerance) ) )
                             
     #these are placeholders for your vectors                        
     vectorOne = Rhino.Geometry.Vector3d(1,1,1)
     vectorTwo = Rhino.Geometry.Vector3d(2,2,2)
     vectorThree = Rhino.Geometry.Vector3d(1,0,0)
     
     #this is where you'll store your tuples of vectors
     output = set([])
     
     #do some stuff: this is a placeholder
     for i in range(6):
         vectorThree = rs.VectorRotate(vectorThree, 90, [0,0,1])  
    
         #once you do your operations, call this function to add the new output to the set
         append_rounded_tuple(output, (vectorOne, vectorTwo, vectorThree))
         print(i, ": ", len(output))
     
     #a few tests:
    
     vectorOne = rs.VectorCreate((1,1,1),(0,0,0))
     
     #vectorTwo is created by rotating vectorOne 360 degrees
     vectorTwo = rs.VectorRotate(vectorOne, 360, [0,0,1])
     
     #vectorThree is created by rotating vectorOne .1 degrees, 3600 times
     #introduces small rounding error
     vectorThree = vectorOne
     for i in range(3600):
         vectorThree = rs.VectorRotate(vectorThree, .1, [0,0,1])
     
     vectorFour = Rhino.Geometry.Vector3d(6,6,6)
     
     print(rs.VectorCrossProduct(vectorOne, vectorTwo))     #0,0,0
     print(rs.VectorCrossProduct(vectorOne, vectorThree))   #3 small numbers
     
     print(len(set([vectorOne, vectorTwo, vectorThree])))  # 2 (rounding error from 3)
     print(len(set([vector_round(vectorOne),vector_round(vectorTwo),vector_round(vectorThree)])))  # 1 (differences rounded off)
    

(*) typically you shouldn’t test for equality of rounded numbers, for example 1.499999 and 1.500001 are very close but may round away from another. In cases like this where you are rounding off many orders of magnitude (e.g. from 1e-12 to 1e-2) I feel like it’s probably fine.

1 Like