gumyr / build123d

A python CAD programming library
Apache License 2.0
546 stars 90 forks source link

Fillet of vertices in a BuildLine randomly fails #413

Closed jdegenstein closed 9 months ago

jdegenstein commented 11 months ago
from build123d import *
from OCP.StdFail import StdFail_NotDone

def discretize_curve(obj: Edge, segments: int):
    pts = [i/(segments) for i in range(0,segments+1)]
    vecs = [obj@i for i in pts]
    with BuildLine() as l:
        for idx,vec in enumerate(vecs):
            if idx+1 < len(vecs):
                Line(vec,vecs[idx+1])
    return l.line

c1 = CenterArc((0,0),1,0,135)
segs = 4
asdf = discretize_curve(c1.edge(),segs)

trials = 1000
fail_rate = 0
for i in range(trials):
    try:
        with BuildLine() as l2:
            add(asdf)
            fillet(l2.vertices(),.2)
    except StdFail_NotDone as exc:
        print(f'{exc=}')
        fail_rate += 1/trials

print(f"{fail_rate=}")

Returns ~0.53 (i.e. 53% failure rate). Changing segs e.g. 3 increases the failure rate to ~77%, whereas segs=2,5,6,7,etc... seem to have a 0% failure rate. This may be related to other issues already filed. image

snoyer commented 11 months ago

The first question is why are there failure rates that are anything but 100% and 0% and it seems the answer is that line.vertices() is not deterministic (returns vertices in random order).

Now why does the order matter is another question... It doesn't feel right that fillet(sorted(l2.vertices(), key=tuple),.2) does get 0% failure while fillet(sorted(l2.vertices(), key=tuple, reverse=True),.2) does get some 100%, but here we are :D

You can get more clever with the sorting and do fillet(sorted(l2.vertices(), key=curve.param_at_point, reverse=True), 0.2) to get 0% on various curves, but that doesn't explain why fillet(sorted(l2.vertices(), key=curve.param_at_point), 0.2) still has some 100%

jdegenstein commented 11 months ago

Interestingly, the StdFail_NotDone is raised on return self.__class__(fillet_builder.Shape()) and not on fillet_builder.Build() is I would have expected.

StdFail_NotDone                           Traceback (most recent call last)
c:\Users\someuser\script.py in line 33
     42 with BuildLine() as l2:
     44     add(asdf)
---> 45     fillet(l2.vertices(),.2)
     46 print(f"{fail_rate=}")
     50 #print(f"\npart mass = {p.part.volume*densa}")

File c:\Users\someuser\.conda\envs\ocp_vscode\Lib\site-packages\build123d\operations_generic.py:461, in fillet(objects, radius)
    453 if not target.is_closed:
    454     object_list = filter(
    455         lambda v: not (
    456             (Vector(*v.to_tuple()) - target.position_at(0)).length == 0
   (...)
    459         object_list,
    460     )
--> 461 new_wire = target.fillet_2d(radius, object_list)
    462 if context is not None:
    463     context._add_to_context(new_wire, mode=Mode.REPLACE)

File c:\Users\someuser\.conda\envs\ocp_vscode\Lib\site-packages\build123d\topology.py:7011, in Wire.fillet_2d(self, radius, vertices)
   6999 def fillet_2d(self, radius: float, vertices: Iterable[Vertex]) -> Wire:
   7000     """fillet_2d
   7001 
   7002     Apply 2D fillet to a wire
   (...)
   7009         Wire: filleted wire
   7010     """
-> 7011     return Face.make_from_wires(self).fillet_2d(radius, vertices).outer_wire()

File c:\Users\someuser\.conda\envs\ocp_vscode\Lib\site-packages\build123d\topology.py:5541, in Face.fillet_2d(self, radius, vertices)
   5537     fillet_builder.AddFillet(vertex.wrapped, radius)
   5539 fillet_builder.Build()
-> 5541 return self.__class__(fillet_builder.Shape())

StdFail_NotDone: BRep_API: command not done
jdegenstein commented 9 months ago

Retested and this is fixed. It turns out it was because of a float comparison with zero issue. Closed by recent PR https://github.com/gumyr/build123d/pull/515