Rhino trouble with radius

sorry, incorrect conclusion.
it should be called:
C.) Swedish people think Swedish beer is awesome

I never drank Swedish beer. Does Ikea also sell beer? I’ll have a look for it. Swedish crab cream is awesome. That’s for sure. :wink:

Don’t even bother trying. If you want something you’ve never tried, go for some Lithuanian or Ukrainian, best when followed by something from local cuisine.

This is a bug that has been there 15 years. Nobody at McNeel seems to be interested in fixing the bugs in FilletSrf.
The reason it happens is that in order to compute the fillet it needs to compute the offset of the surfaces and the far end of the cone-like surface becomes self-intersecting when the fillet size is somewhere between 2 and 3. The self intersection should not make the fillet fail because its not near the fillet,
Shrinking gets rid of the self intersecting part, but that still means you will still encounter the bug if you want a larger size fillet. Rhino2 will make a fillet of 8mm

Update: it looks like it does eventually make the 3mm fillet
I don’t know how long it took while i was typing.

Rhino2 has no trouble instantly making a nice 3 mm filletSrf here. You can also make the fillet yourself by finding the center curve of the fillet and sweeping an arc along that curve.
In the enclosed file the blue surface is the surface made manually. The green surface is the one Rhino2 makes. The red surface is the slow V6 surface. They are all well within tolerance of being the correct shape.
3mm fillet.3dm (169.2 KB)

@Jim, the offset problem may be hard to handle in that case. I guess when doing it manually, you can offset loose and get the intersection curve pretty close but to find out if the offset results in something selfintersecting, it has to be offsetted once. Its just a side problem for what i am trying to do atm.

More problematic are the rules to define when a new fillet srf should be added and when not. Basically i am doing the test for normal deviation from the corners of a previous fillet and if that passed i asumed that this pair of surfaces can be filleted next. But i’ve found cases, where multiplpe surface pairs (which pass the normal test) are considered “filletable” but they are not, or, they are but would produce a fillet which already has been added. So there is need to develop additional checks when a fillet should happen and when not. A simple example is below, it produces 4 same looking fillet surfaces with a radius of 10. In this case, the 4 are geometrically identical, but that is nothing you can rely on. If the surface uv directions of the 2 surfaces to fillet are reversed or swapped, then they are not geometrically identical.

DuplicateFillets_10mm.3dm (51.0 KB)

I’ve discovered different cases now which are hard to tackle. Eg if FilletSrf returns multiple fillets, either connect or not and if it returns multiple fillets joined into a brep. All these would need special treatment in the script.

_
c.

Hi Clement - I see that, thanks.
https://mcneel.myjetbrains.com/youtrack/issue/RH-44582

-Pascal

This is a bug. It should be fixed. Before the bug was introduced in Rhino3, FilletSrf did not have any problem making a 3mm fillet here.

The rules are easy to define. You may not be able to implement them with a script because it involves having the ability to extend only one end of a fillet while not extending the other.

lets assume the case where you have an old surface at one corner (A) and an old and new surface at the other corner (B)

  1. A new fillet gets added when the the normals agree. at B

  2. when normals don’t agree then that last fillet should be discarded and made over again with that end extended (Other end not extended or not changed).

  3. Then run closest_point at corner A If it is still the same surface as before then it didn’t extended far enough and you can’t go further. If there is a new surface at corner A and the new surface is different than new surface at corner B then you proceed with making fillets. This next fillet will be extended at the near end and not extended at the far end.

You can’t use more than one fillet there without creating a non-manifold brep. A user making fillets one by one would only make one fillet because they would know that the fillet loop should not reverse direction 180 degrees. So if the next fillet goes back the way it came that fillet has to be wrong. In fact I would suspect something might be wrong if the next fillet changed direction more than a few degrees.

BTW isn’t a single surface just a brep with only one surface component?

No, the rules are pretty undefined. You’ve not really understood what is involved to first find out when to fillet and when not. I’ll give you an example based on your last reply:

That simple case is extreme rare. How would you keep track of old and new ? The reality is, you have eg. 3 surfaces at corner A and eg. 4 at corner B. All pass the criterion that their normals at the closest point agree. You first need to find out what to fillet, what has been filleted allready and when you fillet what you will keep and discard for what reasons. The latter can be considered a no go, if a fillet is first created to discard it later, there is no need to create it. Therefore, better rules have to be defined how to detect that no fillet should be done with a particular pair of faces which where not filleted yet.

read above. You do not have to discard a fillet when you know that you don’t need it.

When you find surfaces by closest point, you don’t get single results but multiple. How would you determine “far enough” and “old” or “new” programmatically ? Remember you do not see anything, all you get is a list of breps, they “look” all the same and you have to analyze their properties to find out what you’re dealing with programmatically.

Again, there is no “next” or “previous” fillet, as there is no way to keep track of the direction you’re generating the fillet loops. How would you keep track of that ? If you have generated one, you get 2 new point pairs to propagate “next” and thats it. The order in which these point pairs will be processed can be arbitrary as the u direction of a resulting filled may be reversed depending on the input surfaces which have been filleted.

If it has to be halfway robust and reasonably fast, you’ll have to consider many more cases and avoid creating unnecessary fillets. Currently implemented a detection for fillets having one or two singularities so it does not try to propagate from coincident corner points. Also normals do agree if they are not identical but parallel (reversed), otherwise it would just not fillet if one of the input surfaces is flipped…i guess writing all these cases would fill a book which i do not have time for. Finally, there is no way to extend a fillet only on one side as you noticed. It is either both or none, i use the latter.

Yes.

_
c.

That case is where a tangent joining fillet is made.
That case would apply for all the fillets in the original model from the top of the thread. It also looks like it is the case in most of the successful fillets you have shown us. Those are the fillets the script just makes the fillets whiz bang…

In order to decide before you make the fillet you would need to have the center curve and the corner points. You can do that but I’m guessing it would be just as fast and easy for a script to make the fillet and then delete it if its incorrect.

I just assumed that since you had gotten as far as you have that you would understand those words as used in this context.
“Old surface” means the inputs for the fillet just completed ( the fillet that the corner points are being used for closest-point)
“New Surface” means surfaces that are candidates for the next fillet in the chain (any surface found by closest point that’s not an old surface)

When I said “far enough” I was talking about the cases where you make fillets that get extended instead of just the fillets that are unextended and tangent. Maybe your not ready for that yet and want to perfect the tangent fillet case.

If you want to do this in a complete way, I think you may need to keep a record of the inputs and outputs as an ordered list for the current fillet chain that is being created.

Yes that’s correct.

.

Ok getting the centerline is out of question atm. Actually i just need to implement addional checks after the normal check to avoid starting the fillet function with geometrically impossible surface pairs to fillet which have not been processed yet.

:wink: I’ve been keeping track of the processed surface pairs from the beginning if you mean that. Otherwise the process would never end as each fillet adds new corner pairs to the stack which then would never get empty. I’ve added something to keep track of the fillet order, when a new fillet is created it knows from where it comes. This reduced the failed attempts from 8 to 2 in below example case.

Imagine you start picking on Point 1 and 2 to create the initial fillet(s), this generate a fillet (red) having one singularity, so one side of the fillet can be ignored. On the other side, you get a new pair to search for new candidates (ab). The search results in 4 surface pairs, one can be omited as it has been done already to create the red fillet. The remaining 3 are all passing the normal check. One pair is shown in green, the other in blue. I need a geometric way to find out >before< that those cannot result in a fillet. Currently it does no harm if it just fails, but i guess it is better (and faster) to know that 2 of the 3 surface pairs can be safely omited.

NormalsAreNotEnough.3dm (355.2 KB)

I can only use what is there. The method has only option to create extended or not.

I got this partially implemented. It allowed me to generate the loop in one direction only or in both directions simultaniously. The problem with keeping track of processed surface pairs is that you cannot asume that if one surface of one pair has been used once, it will not be used again. See this example which was kind of complicated as it generates multiple unconnected fillets which are all correct when the top surface is involved:

InitDoubleResult.3dm (145.2 KB)

So i can only exclude the pair but not parts of it, otherwise it would not create a fillet when there is one to built for sure.

_
c.

I would think of it this way:
In each corner closest_point should always find one old surface. It may also find A) one new surface in each corner or B) it may find a new surface in only one corner or C) it may find no new surfaces.

C) means game over the chain is done in that direction
B) means you input the one new surface and the one old surface
A) means you input the 2 new surfaces to filletSrf

In other words if there is a new surface use it if there is only an old surface use that.

In your picture that is A) case, but the only reason it is a A is because fillets 1 and 2 are the same size and all the base surfaces are at right angles. Make it so you don’t have right angle fillets and you would likely have a case B)

I wouldn’t worry about when closest_point finds more than one new surface at a corner. Just use the first one if that fails to return a result then try the second one. When you get a result then move on. I can’t think of a situation where that won’t work

I understand. What the manual filletsrf user has to do is use splitsrf to get rid of the extension on the side that is unwanted.

Making a center curve and corner points is really not as hard as you might think It can be done as a macro

Yes but the same pair of surfaces should not be used in the same loop twice.

Is the problem that you get 2 disconnected pieces?
You will have to use the one that connects to the current loop. The loop may eventually get to the second one and it will be created again and then you will use it and discard the other.

C) i have handled from the beginning. Since your ABC list was in reversed order i found that i have to sort the candidate pairs and process the ones first who’s members have been used less often. If i then succed with a fillet i skip the remaining candidates near the corners. So far this did not result in any failed or missing fillets but the more candidates have been processed, the longer the sorting will take.

It always finds multiple, including the newly added fillets as they pass the normal check too :wink: But that was easier to solve.

This i have had from the beginning. If a pair created a fillet, it is tagged to be done forever. This is related to your quote below:

It currently creates this if you click on point 1 and 2: InitDoubleResult.3dm (276.6 KB)

Remember this is still done with single surfaces at the moment so i have object IDs to check, count and compare. If the whole thing would be joined before picking the 2 points, then it has to be decided what to include in the candidate search and what not. This is the tricky part. It may be hard to figure out what to process when the picked starting sub-surfaces are part of two different polysurfaces.

…and the duplicate fillet result problem is still not solved.

_
c.

I think you are making this more complicated than it needs to be. The thing your missing is the UV structure of the fillet surface.

If the UV domain of a fillet is U0 U1 V0 V1 then when you you make a fillet surface and find the 3d point at corner U1V0 you already know that you are going to find two surfaces located there.
You will find the fillet and you will find the surface that was input1 for the fillet (I call that the old surface). In other words, the V0 edge will always lie on the first pick surface.
When you look at corner U1V1 you will also find the fillet and the second input (old surface) that created the fillet. You are not interested in identifying any of those surfaces. All you want to find out is are their any new surfaces.

You only need to be running a normal test when you find a new surface at a corner. If the corner point has no new surface then there is no purpose in running the test (unless maybe you want to check to see if Rhino made the fillet correctly).

I don’t think it will be tricky at all. I believe the structure of the fillet will remain the same regardless of whether the base surface is part of a polysurface or not.

Ideally your call to your closest_point function would allow you to pass parameters that tell it to not return the surfaces that you already know are at the corner point. That way it will return only new surfaces and then you do the normal check only when there is a new surface found. Also ideally the closest_point function should not return surfaces that are hidden or on layers that are turned off.

Take your example:

You already know that you will find surface1 and the red filletsrf at corner A and that you will find the surface2 and the filletsrf at corner B. You ignore those and all you are left with is the 2 new surfaces for the next fillet.

But as I said before when its not all 90 degree geometry you will usually get only one corner (a or b) returning a new surface and the next fillet will be a narrow one that goes from green to green or blue to blue

If you take all the rules you’ve written up to now, multiply them with each other, there are a lot of possibilities when something can happen and when not. You probably think you can access all the conditions you’re aware of visually at any time while running various nested loops, but this is not the case. You cannot rely on what you have made a condition if you’re breaking it at other places. I’ll give you examples based on your last replies:

You cannot 100% rely on that. I’ve found cases where the fillet UV domains have been reversed, depending on the UV directions of each candidate pair member to be filleted. Try it with 2 surfaces and lock at the UV directions using _Dir. So you’re having to deal with a bunch of combinations of what you asume. The only thing which has been constant (so far) is that the circular direction is always in V direction. If you find a case where this is not true, please post it.

Again, when you are at one of the corners, you have no idea which surface pairs have been processed previously for that corner, if it created a fillet, if it never was filleted or if it failed to create a fillet. If you get a point from U1VO, you get a just one 3d coordinate and thats it. In order to access all this information you need to collect it in some way which does not happen by itself.

I’ve just found one case where this is not true. Up to now, i was asuming that if one pair of surfaces created a fillet, it will never create one again. So i could just sort my candidates by old/new and process new ones, which omitted allready filleted ones completely by default. But that was wrong. It’'s a pretty simple case: SkipAllreadyFilletedPairFail.3dm (225.2 KB)

The second fillet failed between the large and small pipe as it has been filleted before. This case also made this candidate pair the last item in the list of candidates…resulting in 2 failed fillet attempts similar to my example picture above.

To make it clear, i’ve been keeping track of surface pairs which have been filleted successfully. If this pair would ever come up again i have fully excluded it. The bad news is that if you ignore that rule, which would allow an already filleted surface pair to be filleted again, it will remove the most important stop condition. It really sucks when the loop never stops. I’ve tried to find out when a fillet loop is closed, so when the first pair of surfaces matches the last, but this then fails if you get multiple fillet surface from one fillet, each resulting in 2 new directions you can continue the search from. Many of the conditions already defined collide with each other, i know you will disagree with that.

I understand that. In order to do this, i’ll need to track this and perform a search whenever i am visiting any point. This adds a lot of calculations.

It does include only selectable, single surface breps. It’s actually not a single closest point search but one for all surface objects within tolerance. This will have to be changed to limit the search to faces of the initially picked brep(s).

A fillet itself does not include any information from the originating surface pairs yet. It only knows the previous point pairs from the other (opposite) site at the moment when it is created. In case of a singularity there is nothing. I’ll think about that when i have more time.
_
c.

The closest_point function when given 3d point at U0V0 will always return the same base surface as it does for U1V0. It has to - both corner points were derived from that base surface. If you compute the corner points for the fillet before making it you will see how this is obvious.

To keep things from getting confused you need to make sure that the surface1 and surface2 inputs to the filletsrf function are always consistent (always on the same side of the chain) which side is side1 and which is side2 can be determined by the first fillet.

If your script is only making tangent chains, then I think all you need to store is the surface1 and surface2 of the first fillet. You are going to need that if the fillet chain ends. The script will then need to go back and work the chain in the opposite direction starting back at the first inputs.

To check for closed loops I think it would be best to store the corner 3d points at the start of the chain. Then when you get each pair of new corner points at the new end of the chain you compare those points to the start points. The loop is closed when the start points equal (within tolerance) the end points.

The fillets isocurve at U0 lies on one of the fillet’s input surfaces and the isocurve at U1 lies on the other input surface. If you know the input surface identities and you know which one lies at one corner of the fillet you then also know for certain which input surfaces lie at all the other 3 corners.

At a singularity you will find the 2 input surfaces and maybe a new surface. The singularity may often be malformed (all the control points won’t be in one location) If your algorithm detects a new surface and you use it along with the old surface at the other corner the filletsrf function will just return empty and that will be the end of the loop.

I

I looked at the UV structure (Dir command) of the fillets that you have already posted that were made with your script and it looks like you are already consistently arranging the inputs so that surface1 is on one side of the chain and surface2 on the other side.
That’s good.

A user doesn’t need to worry about which surface is picked first or second but to efficiently programmatically determine which two surfaces are the input for the next fillet you need that consistency.

@jim, i first thought the same. But there are multiple loops depending on the amount of initially created unconnected fillet surfaces. If you look at the InitDoubleResult.3dm file posted above, it creates 2 unconnected faces. Both add two pairs of points to the stack to look for new filletable surfaces. I have created some test geometry to handle all these cases, eg. one creates 8 fillets, all unconnected. So to look for only one loop end is not enough. Looping in one direction only helps to gain more speed if all lookups are done from the last items added to the lists. If the looping will be done on both ends simultaniously, the lookups seem to be slower. This becomes only noticable if the fillet chain is endlessy long.

Yes, i did that already. There are multiple close cases, also found one where a0 ends on b1 and b0 on a1, some kind of moebius problem…below is the common case, which works when a0b0 and a1b1 are both moving:

I just detect a on b, if it’s smaller than tolerance, i asume this is one. Any other ideas ?
_
c.

I have been thinking about that example. What you have done is correct, but it may not be helpful for the user. Its hard to tell where that model is going. The additional fillets at the other 3 corners may not be wanted or expected by the user.
What happens if after making those fillets you decide to fill the
middle area with a tangent surface and then make the rest of the fillets?

Topographically that shouldn’t be possible. The fillet has to be twisted

My guess is that was one of those fillets that was supposed to end in a singularity but didn’t quite make it. You have to zoom way in to see what is happening. Degenerate base surfaces will also produce some pretty twisted fillet surfaces.

I think its really good that you are finding out what the old surfaces are before analyzing the closest point for new surfaces. Without that the algorithm would be more fragile and prone to failure.

You should be able to complete partial chains that already exist (assuming the fillet radius is exactly the same). The existing fillet may have been made by Rhino or made by some other CAD software - it doesn’t really matter. That’s the nice thing about fillets. You can edit the base geometry and as long as the changes you make are tangent with the existing base surfaces the fillets will join right up with the existing fillet loops.

When you encounter more than one newsurface at a corner and the other corner also finds that same surface and both pass
the normal test. that means you just encountered an existing fillet of the same size so you should not proceed to pile more fillets on top of the existing one.

A singularity should have tolerance of zero. That is the points should all be exactly at one point. But if they are not its not your fault. Detecting them within tolerance should work.

That example was actually a show stopper. If you fillet at the two points in the file using _FilletSrf, you’ll get 2 new unconneced fillet surfaces. The one near the pick points is expected, the other i discovered by accident later. If i would define a rule that whenever multiple fillets are created on the initial fillet and just keep the one closest to the initial pickpoints, the case will later happen again in the loop. Unfortunately then it adds a fillet exactly where there is one from the other side.

Remember, that top surface is the cause. But that top surface is used to create adjacent new fillets from the first initial fillet near the pick points. So you cannot ignore one of the initial fillets and you cannot ignore double results in the loop either as they will be created when you start processing from one of two. It also needs special handling of double results if they are connected and if they are connected and joined. The latter i can force by just using way too high radii.

Something really bad happens: It tries to fillet an existing fillet of the same radius (it passes normal check and all other conditions) resulting in some geometry which is nicely distorted. I’ve been able to create something like that when i forgot to delete one of the previously made fillet loops and running the loop over all surfaces in the document again.

Aren’t these beautiful ? FilletThroughExistingFilletOfSameSize.3dm (491.6 KB)

Like my mind, the more i think about solving all these cases.

I’ll have to add some kind of extra validation whenever a fillet is created. It must stop then. That is why i really tried to opt for avoiding that you just try out if it fillets and continue blindly. Too many bad results could happen which take forever to build the rendermesh until you press ESC and find a disaster in Nurbs.

Isn’t it funny that Rhino does not prevent you to do this using _FilletSrf command ? I think when i run this in a loop, i have to check the ending edge of a fillet to work from to the next (in V direction). If this edge is also in one of the canditates, the loop has to skip that candidate pair or just end filleting in this direction. Maybe this is one of the reasons why this has never been added as an option to _FilletSrf. Many things could get out of control if you run into such cases and it just continues as the expected stop conditions are never met. I therefore decided that you should see the fillets immediately and be able to cancel.

This condition is handled to match the behaviour of _FilletSrf. It does not allow the same surface, so i skip if a candidate pair is made from identical Object IDs. Using these is helpful, but when i try to extend this comman to work with polysurfaces there is not much i can do to check if any of the sub-faces in the first input brep matches and overlaps (even partially) a sub-face in the other input brep and is sometime later visited by the fillet loop…

You still think this is all trivial, don’t you ? :smile:
_
c.

I don’t think you need to worry about things you currently have no control over. The best solution would be an option in the Rhino SDK function that you are using to not make disconnected fillets. If it only made fillets that are connected to the input points that would solve the problem.
As a user of filletSrf it is easier for me to delete a fillet than to make one so I don’t mind if it makes extra fillets that may or may not be useful later. But for you the best solution is to not make the disconnected fillets.

I just explained in the last post that your script should never do that. When you find new surfaces at both corners that pass the normal test you need to compare surface IDs to find any surfaces that corner A and B have in common. If you find that it means there is a surface tangent to both sides you just discovered why your script cannot use such a surface. The chain should stop there and not try to make a fillet.

yes beauteous. I’m surprised that the fillet function did not return empty. If you try to make a fillet between the side and existing fillet nothing happens. Usually when you try to make a fillet using the same size fillet nothing happens.

I think you are doing more checks than you really need to do. You do need to make sure that you don’t use a surface that is tangent to both sides but you don’t really need to check for singularities. Here is an example. Singularity.3dm (120.8 KB)

Notice that you should check for surfaces that are tangent to both sides but that is really all you need to check for. The chain will end there because there are no new surfaces that qualify for a fillet. The reason you need to check for surfaces that are tangent to both sides is that the function you are calling is sometimes not smart enough to know that the inputs cannot support a fillet.

In the singularity example above you don’t really need to check for anything. If you try to use the new surface that you find at the pointy end the fillet function will just return empty and the chain will end at the point. But checking for surfaces tangent to both sides is also good for avoiding filleting places that have already been filleted.

You are still over-thinking this. It is much simpler than you think it is. Your script works well when it finds new tangent surfaces that are not tangent to both sides of the current fillet. The problems you are running into are when you don’t have any new tangent surfaces that qualify. You just have to accept that the chain ends there unless you are willing to go beyond tangent fillets.

It is a whole lot more trivial than you are making it out to be.

There are only 3 cases you have to consider

!) when you find no new surfaces that qualify - the chain ends
2) when you find one new surface that qualifies you use it for one side and use the old surface on the other side
3) when you find a new surface that qualifies on both sides you use them.

If it should happen (rare) that you find more than one new surface that qualifies on either side just use the first one. If that returns empty try the next. When you get a result use it.

In order for a new surface to qualify it must pass the normal test and it must pass the test that checks to make sure the 2 input surfaces are not tangent (have the same normals at the test point).

The above algorthm is a very trivial algorthm to implement .
It may not be trivial for you to understand how and why it works, but you have so far been doing an excellent job of implementing it without understanding the hows and whys.

That option i could write myself but, even when a single fillet is created from FilletSrf, the input points will never be connected with the fillet. For multiple results, see this example, it creates 7 new fillets resulting in 14 (a/b) pairs. InitMultiResult.3dm (170.4 KB)

It would make things easier to deal with single results, in the initial fillet and during the loop.

As a programmer you mind because it does not create one duplicate but thousands. See this example it started the loop at pick points (1,2) , created 4 red fillet faces OK, then it found that perfect blue pair of faces which passed all tests. It then created the orange fillet after the 4th red one which goes in opposite direction as expected. From there it changed the direction again and continued in some kind of ping pong mode. You’ll get the resulting fillets if you just show the hidden objects. This is a case where it never stopped looping because the consecutive fillets are not duplicates and the last search pair never reaches the initial search pair. RollOverNakedEdge.3dm (1.5 MB)

This requires extra stop conditions.

You have not understood yet that the involved surfaces later have no Ids to compare. They are part of one or two breps. Each brep has one ID. I’ll need to find out how i then can do the comparison.

I know. There are some better condition checks required like (full and partially) overlap detection which kicks in not only when the start meets the end of the loop. So its not enough to compare the first with the last fillet or the last with the second to last.

That is because as a user you do not click exactly at the point locations which i pass to the fillet function. You simply can’t. Therefore you think that if you just pass the corner point locations you’ll get the same results like when scripting it. This is not the case.

I think that to make this halfway robust, hundrets of conditions need to be checked and many of the possibilities have to be catched by the least amount of rules. Otherwise you’ll end with results which i’ve already posted. Singularities are handled.

As i’ve tried to explain to you, all cases you see and decide with your eyes are not visible when you just loop over the candidates for the next fillet. I’ll have to prove them all.

I’m working on this when i have some spare time. I’ve seen results after defining many rules you’ve imaginated. But i see that they are only partially working using very simple test cases. First thing to improve is to really find tangent surfaces, which you do not get just by comparing normals at the (a/b) points. Then the surface UVN directions need to be taken into account. Look at this example and check them to understand why:

FilletEdgeFailed_02.3dm (236.6 KB)

I’ve created many examples like the above where _FilletEdge fails and _FilletSrf worked, with much larger radii. Often _FilletEdge cannot be used since there are no (joined) edges to use the command at all. A tool for filleting in a loop as proposed is potentially useful, even when the whole trimming work has to be done by hand.
_
c.