Notifications
Clear all

[Closed] How to find if the object is cylinder?

Thank you to all. All methods works fine and I use all of them.

Aandres, your first method finds 62 cylinders(300 ms).
The Polytools method finds 59 cylinders(900 ms).
Aandres+Polytoos method finds: 58 cylinders(670 ms).

When I combine methods the found cylinders are 75.

If you lower the threshold1 and threshold2, possibly you’ll find all of them with the last method. My first method is not secure (it doesn’t work with truncated cones for example).

P.S.: do you have problems with batteries at home?

2 Replies
(@miauu)
Joined: 10 months ago

Posts: 0

I use threshold value of 0.1 and the results above are using 0.1.

P.S. When I am not at work my phone is somewhere in the house.

(@polytools3d)
Joined: 10 months ago

Posts: 0

Please note that the function has 2 threshold values, one for angles and the other for distances.

I set the angle threshold to0.0001 (89.9943 degrees), while you use 89.9 degrees for creating the smoothing groups.

The second threshold will be affected by the size of the objects. A value of 0.05 may be useful for small object while too big objects may need a different value in order to work properly.

A better heuristic approach would be to change this value based on the size of the objects.

As you can see, I did not include any time measurement in the code, which means it is not meant to be measured, it is just a generic idea. And certainly I can’t warrantee it will work perfectly, moreover when the details of the problem are not very clear to me.

The first step would be to make sure the algorithm works, just then it can be optimized.

But before you can design an algorithm you need to have a detailed description of the problem, otherwise it would fall in the “generic” category, which often is a lot harder than a “specific” algorithm, and many times it is not even possible to develop.

Could you please give us a detailed description of the cylinders in question? Something like:

  • They will be always default primitives converted to mesh, or not
  • They will always have 2 caps, or 1 or none
  • The caps will have no segments, or they will have N amount of segments
  • The sides will have no segments, or they will have N amount segments
  • They can be rotated in any angle, or they always have a fixed rotation
  • They can have any size or they will have a size in range X

Any other detail that you know about the cylinders is very important.

Is there any way we can reproduce the failing objects?

The time measurement is not so important. Aandres included it in the last code he posted, so I have decide to share the results. My own
code takes 2 sec to find cylinders and it detects 92 objects as
cylinders, but it is not perfect and your codes finds 55 more cylinders that mine can’t. The most important for me are the ideas you have shared here.

In my first post you can find a link to download max scene with two cylinders. But in the scene there are cylinders that looks different than those two. What I need was an idea/advice how to find which object is a cylinder.
In this image you can see how other “cylinders” looks, but they have to be detected as cylinders:

The object on the left is one single mesh object(one cap is missing, tha half of the side faces are missing). On the right there are bunch of objects, each one of them are single mesh object(one triangle face), but they(all of them) also have to be detected as a cylinder(I find them using the intersects function).
Your methods of finding a cylinder can’t detect the object on the left as a cylinder, but it is not a “proper” cylinder and your codes are not optimized to search for such an object.

But, as I said the most important for me is your ideas of how to solve the problem and I am grateful for the help all of you gave me.

Could you please give us a detailed description of the cylinders in question? Something like:

  • They will be always default primitives converted to mesh, or not – Not
  • They will always have 2 caps, or 1 or none – all situations are possible
  • The caps will have no segments, or they will have N amount of segments – all situations are possible
  • The sides will have no segments, or they will have N amount segments – all situations are possible
  • They can be rotated in any angle, or they always have a fixed rotation – they can be rotated in any angle
  • They can have any size or they will have a size in range X – they can have any size

Let’s change the rules, and make the task more interesting

Let’s rotate and move baseobject and reset xform!

Jorge tests work seems like the same, but all others fail because of object transform

2 Replies
(@miauu)
Joined: 10 months ago

Posts: 0

My first code, posted here also works:


(
    function FindCenterOfCircle p1 p2 p3 =
    (
        fn barycentricToWorld p1 p2 p3 u v w = (u*p1 + v*p2 + w*p3) / (u + v + w)

        local a = p3 - p2
        local b = p1 - p3
        local c = p2 - p1

        local u = (dot a a) * (dot c b)
        local v = (dot b b) * (dot c a)
        local w = (dot c c) * (dot b a)

        barycentricToWorld p1 p2 p3 u v w
    )
    
    function sortByXMember arr1 arr2 x:1 =
    (
        case of
        (
            (arr1[x] < arr2[x]):-1
            (arr1[x] > arr2[x]):1
            default:0
        )
    )


    o = selection[1]

    edgesIdxLengthArr = #()
    for eIdx = 1 to o.edges.count do
    (
        edgeVertsBA = meshOp.getVertsUsingEdge o eIdx
        vertPosArr = for v in edgeVertsBA collect getVert o v
        append edgesIdxLengthArr #(eIDx, (distance vertPosArr[1] vertPosArr[2]))
    )
    qsort edgesIdxLengthArr SortByXMember x:2

    shrotestLength = edgesIdxLengthArr[1][2]
    shortestEdgesBA = #{}
    for arr in edgesIdxLengthArr where ( abs (shrotestLength - arr[2]) ) < 0.1 do join shortestEdgesBA #{arr[1]}


    if shortestEdgesBA.numberset > 12 then
    (
        edge01 = (shortestEdgesBA as array)[1]
        verts01 = meshop.getVertsUsingEdge o #{edge01}
        adjacentEdgesBA = meshop.getEdgesUsingVert o verts01
        openAdjacentEdgesBA = (shortestEdgesBA * adjacentEdgesBA) - #{edge01}
        verts02 = meshop.getVertsUsingEdge o openAdjacentEdgesBA
        adjacentEdgesBA = meshop.getEdgesUsingVert o verts02
        openAdjacentEdgesBA = (shortestEdgesBA * adjacentEdgesBA) - openAdjacentEdgesBA - #{edge01}
        verts03 = meshop.getVertsUsingEdge o openAdjacentEdgesBA

        p1 = getVert o (verts01 as array)[1]
        p2 = getVert o (verts03 as array)[1]
        p3 = getVert o (verts03 as array)[verts03.numberset]
        center = FindCenterOfCircle p1 p2 p3

        testVertPos = getVert o (verts02 as array)[1]

        distTest01 = (distance p1 center)
        distTest02 = (distance testVertPos center)

        if abs ( distTest01 - distTest02 ) < 0.1 then
        (
            print "Cylinder"
        )
        else
        (
            print "Not cylinder"
        )
    )

)

I use the onject on the left from my previous post. Then I select all vertices, rotate them, reset xform, convert to editable mesh and the code detects the object as a cylinder. The same is the result with the objects from the scene that you can download from my first post in the thread.
I know that my code can detect as cylinders objects that are not cylinders and this was the reason to start the thread.

Edit: I have tested the cylinder object(from the proivided scene) and only the last code posted by Aandres not works. All other detects the object as a cylinder.

(@aaandres)
Joined: 10 months ago

Posts: 0

If you are talking about the scene with two cylinders that you have shared, my last code finds them as cylinders (in 11ms to be more precise ). And get the same results than Jorge’s ones with his cylinder tests.
EDIT: what code are you using? In fact, times you have shown don’t seem to me accordinly to my code (just for the 10 cylinder test, my code was 300ms faster in the option 1 case).

I use the scene with 3629 objects. more than 3000 ot them are single mesh objects that have only one triangle face. The results I have posted are using this scene. I use all the codes posted here one by one.

Ok, that makes things a lot more complex. For geometries like the one in the image you can’t use any of the solutions we’ve proposed here as using Smoothing Groups (or meshop.explodefaces()) won’t work with them.

Run the previous code on the geometries, if it fails run the following one. It return 1 if the object is a cylinder, otherwise it returns negative values.

NOTE: Absolutely unoptimized and only tested with the geometry you provided. If you have an object that fails please upload it.


(
(
    /* denisT ---------------------------------------- */
    fn GetFirstBit bits =
    (
        local b
        for n in bits while not (b = n; bits[n]) do ()
        b
    )
    /* ----------------------------------------------- */

    fn GetVerticesCenter mesh verts =
    (
        cx = cy = cz = 0.0d0

        for j in verts do
        (
            vert = getvert mesh j
            cx += vert.x
            cy += vert.y
            cz += vert.z
        )

        cx /= verts.numberset
        cy /= verts.numberset
        cz /= verts.numberset

        return [cx, cy, cz]
    )

    fn IsObjectCylinder node threshold:0.02 =
    (
        tmesh = snapshotasmesh node
        faces = tmesh.faces as bitarray
        verts = tmesh.verts as bitarray

        maxDistance = 0.0d0

        for j in verts do
        (
            v1 = getvert tmesh j
            for k in verts do
            (
                v2 = getvert tmesh k
                maxDistance = amax maxDistance (distance v1 v2)
            )
        )

        dmin = maxDistance - threshold
        dmax = maxDistance + threshold
        capVerts = #{}

        for j in verts do
        (
            v1 = getvert tmesh j
            for k in verts do
            (
                v2 = getvert tmesh k
                d = distance v1 v2
                if d < dmax and d > dmin do capVerts[j] = capVerts[k] = true
            )
        )

        minDistance = 1E9
        for j in capVerts do
        (
            v1 = getvert tmesh j
            for k in capVerts where k != j do
            (
                v2 = getvert tmesh k
                minDistance = amin minDistance (distance v1 v2)
            )
        )
        
        if capVerts.numberset < 10 do return -1
        
        dmin = minDistance - threshold
        dmax = minDistance + threshold
        
        cap1 = #(GetFirstBit capVerts)
        cap2 = #{}
        done = #{}

        for j in cap1 do
        (
            v1 = getvert tmesh j
            for k in capVerts where not done[k] do
            (
                v2 = getvert tmesh k
                d = distance v1 v2
                if d < dmax and d > dmin do
                (
                    append cap1 k
                    done[k] = true
                )
            )
        )
        
        /* Test Cap 1 */
        cap1    = cap1 as bitarray
        center  = GetVerticesCenter tmesh cap1
        minDist =  1e9
        maxDist = -1e9

        for j in cap1 do
        (
            d = distance center (getvert tmesh j)
            minDist = amin minDist d
            maxDist = amax maxDist d
        )

        if (maxDist-minDist > threshold) do return -2

        /* Test Cap 2 */
        cap2    = capVerts - cap1
        center  = GetVerticesCenter tmesh cap2
        minDist =  1e9
        maxDist = -1e9
        
        for j in cap2 do
        (
            d = distance center (getvert tmesh j)
            minDist = amin minDist d
            maxDist = amax maxDist d
        )

        if (maxDist-minDist > threshold) do return -3

        caps    = cap1 + cap2
        center  = GetVerticesCenter tmesh caps
        minDist =  1e9
        maxDist = -1e9

        for j in caps do
        (
            d = distance center (getvert tmesh j)
            minDist = amin minDist d
            maxDist = amax maxDist d
        )

        if (maxDist-minDist > threshold) do return -4

        return 1

    )

    IsObjectCylinder $ threshold:0.02

)

1 Reply
(@miauu)
Joined: 10 months ago

Posts: 0

This detects much more objects as cylinders, even objects that are not cylinders. You can download this file:

https://drive.google.com/open?id=15aM8J34btBN5i5SSmY_wBizj-dB_zOtt

The objects with green wirecolor are detected as cylinders.
The objects with red color are not detected as cylidners.
The strange is this:
Object $Solid_003(red color) is not detected as cylinder, while other objects with the same topology are detected as cylinders.
Objects $Solid_001 and $Solid_002(green color) are detected as cylinders, but they are not.
The small triangles(red wirecolor) names Paret_xx along with other triangles forms a “cylinder” without caps. But, the rest of the objects, that forms a cylinder are detected as cylinders, this one(the red one) are not.
The rest of the objects – single objects with one triangle face, are detected as cylinders or not detected as cylinders.

$Solid_003 is not a cylinder from my point of view. At the caps it has edges with very different lengths. For example edges 286 277 are much shorter than the average.

I made a small update that shoulddetect $Solid_001 and $Solid_002 as not cylinders.

Anyway, it is a very rudimentaryapproach and it will fail in many cases. It needs to be improved. Perhaps from all the code posted here you can build a better solution. If i can think on something else, I will post it.

1 Reply
(@miauu)
Joined: 10 months ago

Posts: 0

Yes, most of the cylinders are not exactly cylinders.
This is how may cylinders your code can find:

  • before the update – 898 (with the single triangle objects, but most of the times they forms a “cylinder”)
  • after the update – 109 (no single triangles)

I’ve spent the night modifying all posted code to get the best. Tomorrow I will add your last code. Thank you for the help.

here is my attempt… there are several cases where it fails on right cylinders, but the approach is different than others and might be interesting:


fn firstbit arr = 
(
   local b
   for n in arr while (b = n; off) do ()
   b
)
fn pointRayDist r p = 
(
   length (cross r.dir (p - r.pos))
)
fn round1 d tolerance:0.001 =
(
   d = (d as float)/tolerance
   v = if (d - (v1 = floor d)) > ((v2 = ceil d) - d) then v2 else v1 
   v*tolerance
)
fn round3 p tolerance:0.001 = [round1 p.x tolerance:tolerance, round1 p.y tolerance:tolerance, round1 p.z tolerance:tolerance]
fn sameBitArrays b1 b2 = (b1.numberset == b2.numberset) and ((b1*b2).numberset == b1.numberset)
fn sortByNumberSet v1 v2 = v2.numberset - v1.numberset
fn getEdgeVerts node edge =
(
   face = (edge - 1)/3 + 1
   vv = getFace node face
   index = mod (edge - 1) 3 
   case index of
   (
      0: point2 vv.x vv.y
      1: point2 vv.y vv.z
      2: point2 vv.z vv.x
   )
)
fn getEdgeVector node edge = 
(
   vv = getEdgeVerts node edge
   p0 = getvert node vv[1]
   p1 = getvert node vv[2]
   ray p0 (normalize (p1 - p0))
)

for obj in objects where iskindof obj Editable_Mesh do undo off
(
   keys = #()
   vals = #()
   --obj = $
   for i=1 to obj.numfaces*3 do 
   (
      v = getedgevector obj i
      d = round3 v.dir tolerance:0.01
      if (k = finditem keys d) == 0 then 
      (
         append keys d
         append vals #{i}
         k = keys.count
      )
      else
      (
         append vals[k] i
      )
   )
   qsort vals sortByNumberSet 
   obj.selectededges = hedges = vals[1]
   faces = meshop.getpolysusingedge obj hedges   
   verts = meshop.getvertsusingedge obj hedges   
   obj.selectedfaces = faces
   obj.selectedverts = verts
   center_test = (vals[1].numberset == vals[2].numberset and vals[1].numberset > vals[3].numberset)
   --format "CENTER ** %
" center_test
   c_axis = ray obj.center (getedgevector obj (firstbit vals[1])).dir
   -- point pos:c_axis.pos dir:c_axis.dir 
   -- pointraydistance c_axis (getvert obj (firstbit (obj.selectedverts as bitarray)))
   vkeys = #()
   vvals = #()
   --obj = $
   for i=1 to obj.numverts do 
   (
      p = getvert obj i
      d = pointraydist c_axis p
      v = round1 d tolerance:0.01
      if (k = finditem vkeys v) == 0 then 
      (
         append vkeys v
         append vvals #{i}
         k = vkeys.count
      )
      else
      (
         append vvals[k] i
      )
   )
   qsort vvals sortByNumberSet
   rverts = vvals[1]
   obj.selectedverts = rverts
   --format "RADIUS *:%
" vkeys
   radius_test = (sameBitArrays verts rverts)
   --format "RADIUS ** %
" radius_test
   bad_faces = #{}
   for i=1 to obj.numfaces do 
   (
      n = getfacenormal obj i
      d = abs (dot c_axis.dir n)
      v = round1 d tolerance:0.001
      if (v != 0 and v != 1) do append bad_faces i
   )
   
   normal_test = (bad_faces.isempty)
   --format "NORMAL ** %
" normal_test
   
   format "% >>>>> PASS ** %
" obj.name (center_test and radius_test and normal_test)   
)

Thank you, Denis. The firstBit function is missing from the code. I assume that this is the GetFirstBit function from the Polytoos code?

Edit: With the GetFirstBit function the code works. It finds 43 cylinders and two objects with this shape [

I will continuw tomorrow after work.

Page 3 / 4