How to fillet a ObjectData edge in Python?



  • Hello,
    I'd like to fillet an edge loop for a Polygon ObjectData plugin I am making (like the Fillet option in the native Polygon Object primitives). I am seeking help in getting the start & end angle of the bevel to work correctly.

    So far in my research, it appears to be a matter of mathematically generating an arc of points between two points [V2, V3] based on their distance from a third angular point [V1] and a radius.

    I'd like to get this result (sorry, I have B & C swapped in my scene example):
    Start_and_End_Angle.jpg

    I am not sure how I should limit the radius size or to get the correct arc direction for the arc & arc points. Here are my current code & scene file:

    import c4d,math,random
    
    global C,startAngle,endAngle,radius,subdivisions
    v1Obj = doc.SearchObject("V1")
    v2Obj = doc.SearchObject("V2")
    v3Obj = doc.SearchObject("V3")
    aObj = doc.SearchObject("A")
    bObj = doc.SearchObject("B")
    cObj = doc.SearchObject("C")
    archB = doc.SearchObject("Arc B")
    obj0 = doc.SearchObject("0")
    obj1 = doc.SearchObject("1")
    obj2 = doc.SearchObject("2")
    obj3 = doc.SearchObject("3")
    txt = doc.SearchObject("Text")
    
    def GetPointsOfArc():
        global C,startAngle,endAngle
        FullCircleAngle = 2 * math.pi
        normalizedEndAngle = None
    
        if startAngle < endAngle:
            normalizedEndAngle = endAngle
        else:
            normalizedEndAngle = endAngle + FullCircleAngle
    
        angleRange = normalizedEndAngle - startAngle
        angleRange = FullCircleAngle if angleRange > FullCircleAngle else angleRange
        step = angleRange / subdivisions
        currentAngle = startAngle
        while (currentAngle <= normalizedEndAngle):
            arcPt = c4d.Vector()
            arcPt.x = C.x + radius * math.cos(currentAngle)
            arcPt.y = C.y + radius * math.sin(currentAngle)
            yield arcPt
            currentAngle += step;
    
        archB[c4d.PRIM_ARC_START] = startAngle
        archB[c4d.PRIM_ARC_END] = endAngle
        degStartAngle = c4d.utils.Deg(startAngle)
        degEndAngle = c4d.utils.RadToDeg(endAngle)
        print("*"*50)
        print("startAngle: %s°\nendAngle: %s°\n"\
            "angleRange: %s°\nnormalizedEndAngle: %s°"%(round(degStartAngle),
                round(degEndAngle),round(angleRange,2),round(normalizedEndAngle,2)))
    
    def draw(bd):
        points = GetPointsOfArc()
        COLOR_SPHERE = c4d.Vector(0, 1.0, 0)
    
        for pt in points:
            bd.DrawSphere(pt, c4d.Vector(4.0), COLOR_SPHERE, c4d.NOCLIP_Z)
    
    def main():
        global C,startAngle,endAngle,radius,subdivisions
        radius = op[c4d.ID_USERDATA,2]
        subdivisions = op[c4d.ID_USERDATA,3]
        V1 = v1Obj.GetAbsPos()
        V2 = v2Obj.GetAbsPos()
        V3 = v3Obj.GetAbsPos()
    
        a = V2-V1
        b = V2-V3
        a.Normalize()
        b.Normalize()
        halfang = math.acos((a.Dot(b)))/2
        ab = (a+b)/2
        ab.Normalize()
    
        C = V2 - radius/math.sin(halfang)*ab
        A = V2 - radius/math.tan(halfang)*a
        B = V2 - radius/math.tan(halfang)*b
    
        aObj.SetAbsPos(A)
        bObj.SetAbsPos(B)
        cObj.SetAbsPos(C)
        startAngle = math.atan2(A.y - C.y, A.x - C.x);
        endAngle = math.atan2(B.y - C.y, B.x - C.x);
    

    SCENE FILE: Bevel.c4d

    I pieced a lot of the math code together from online resources, so I don't entirely understand it. Currently, the arc points are sweeping in the wrong direction. When the startAngle is negative, both the arc & arc points are incorrect. When both the startAngle & endAngle are negative, however, both are correct.

    Can anyone provide guidance for getting the arc & arc points to go in the correct direction in these cases please? Thank you!



  • Hi,

    sorry, I did not see that you already did that. Very cool little rig in your file ;) But I am sorry, I cannot debug your code above for you. Because I am not so well versed in trigonometry to immediately see what is going wrong there, I would have to work through it myself. And as already stated, I would personally use transforms, i.e. c4d.Matrix to encode the arcs. You could also use two quaternions and then just spherically interpolate between them. Your code is rather hard to understand in each detail (for me) in this raw form where you write out all the transforms as trigonometric expressions. Maybe some dev here is more well versed in the subject.

    And I would also point to curves/splines again, because it will simplify the problem (you do not have to think about circles anymore, just little boxes, i.e. where to place the control point(s) in that box). In case you are lost with splines, I did attach a little example for a quadratic Bezier curve which should work pretty well in your case.

    quadratic_bezier.c4d

    Cheers,
    zipit



  • Hi,

    the nature of your problem remains a bit unclear to me. You talk about polygonal data and beveling edges, which I would interpret as the question for bevelling in a network of curves, .e.g. a polygonal topology, but you show the image of a curve and also your code operates in a plane. I assume you want interpret your mesh as a series of cross-sections/curves?

    That aside, beveling (either curves or curves in surfaces, i.e. edges) is an application of interpolation and can therefor implemented in various forms, here are some approaches:

    1. Piecing in arc segments. This what you are basically trying to do from the looks of your code. You normally would properly define a series of transforms (i.e. "matrices" in Cinema) to transform a base vector and by that construct the vertices making up the arc segment. This solution is not very suited for a data of intersecting curve networks, i.e. surfaces, it works better in something like a 2D Vector app.

    2. Than there are multiple ways to construct a smooth curve over a series points. The easiest one are Chaikin's Curves which also have spawned a whole group of algorithms all following more or less the same one fourth three fourth idea and some of them can also be applied to meshes.

    3. The next obvious candidates would be quadratic or cubic polynomials/curves. They can the can also be viewed as a combination of linear interpolations. This is called De Casteljau's algorithm. To get from multiple curves segments to splines, you just piece them together (there are multiple ways to do this). But in you case of beveling you probably will only need a single curve. Depending on how deep you venture into this topic this reaches from splines and curves (.e.g. a B-Spline or a Bezier spline) to surfaces (e.g. a NURBS surface).

    4. The last one, and that is the one Cinema probably uses for its edge tool is just Subdivision Surfaces. There are also multiple algorithms of varying complexity, but classical Catmull-Clark SDS have very nice properties (producing only quads for example) and are very easy to implement. The problem here is how to restrict the subdivision part of the algorithm so that you only generate new edges along the edge loop and not perpendicular to it and then come up with the correct modified weights for the smoothing.

    Its all a bit vague, but your question is very broad, so I thought this might help.

    edit: Forgot this, but should have put this first - Implementing a bevel tool is not trivial. Covering the default case is not that hard, but covering all the corner cases is, especially when you want to maintain secondary mesh attributes like texture coordinates. I would not go for reinventing the wheel here. Is there a reason why you do not want to use Cinema's edge bevel? Does it not work with SendModellingCommand?

    Cheers,
    zipit



  • Hi @zipit, thanks for the message! I thought you might know about this 😄 Your assumption is right: my mesh will consist of a series of cross polygon edges similar to a standard polygon sphere/Lathe. The algorithms you provided were very helpful, but I am seeking support with a beveling function for just one point on the cross section rather than the entire thing. Something more like this. My apologies, I probably should've used a term other than 'subdivision' but I went with that as that's the name Cinema 4D uses with their bevel tool.

    I don't know if you had a chance to look at the scene file I attached in my original post, but the implementation is very close. I'm now just looking for help in getting the start & end angles of the arc points to work correctly. They are all over the place depending on the placement of V1, V2, & V3: often on the wrong side of the arc. I hope this clarifies my question.

    The subdivision links were not for waste: I saved them all as I know I'll need them eventually (or perhaps with this object if I can't get it to work with the current methodology). Thanks again!



  • @zipit said in How to bevel an edge in Python?:

    Forgot this, but should have put this first - Implementing a bevel tool is not trivial. Covering the default case is not that hard, but covering all the corner cases is, especially when you want to maintain secondary mesh attributes like texture coordinates. I would not go for reinventing the wheel here. Is there a reason why you do not want to use Cinema's edge bevel? Does it not work with SendModellingCommand?

    I am definitely seeing what you mean regarding 'corner' cases!

    Can I use the SendModellingCommand dynamically if the bevel is being set with a float (functionality similar to the Fillet Radius/Subdivision in an Object Generator)? That is what I'm looking to recreate.
    3af620c5-6081-492b-a0a4-16ad13fa001c-image.png

    My fear of that was that it would be too resource-heavy to use for fast Viewport feedback. I couldn't find any examples on this forum. If that is possible, where would I use it? Currently I have a 'build' function that I'm calling from ObjectData.GetVirtualObjects.

    On a related note, @m_adam implements a Fillet Radius in the py-rounded_tube_r13.pyp demo (variable rrad) but doesn't use the Bevel tool so that was another reason I didn't think it was a best practice. I am constructing my Polygon Object differently than the Rounded Tube and I didn't understand the math being used. If it is possible to call SendModellingCommand, it would definitely save me the time of implementing all use cases myself.



  • Hi again ;)

    I only looked very briefly at your code to get a rough idea of what you are doing. I also missed the part of you wanting to do this as a "fillet" which might simplify things a lot. The major question here is if there are intersection points where more then two fillet edges meet. If there are, things can get tricky. It would probably best if you would either show the actual geometry you want to fillet or a mock up of it.

    Finding the start and end angle would be done geometrically the following way:

    1. Find the circle that is tangential to the start point A and end point B or in other words find the curvature of that segment.
    2. Construct the normal for both points and see where they do intersect.
    3. The length of the intersection vector is the radius of your circle to construct points on.
    4. The angle between AP and BP (P being the mid point of the circle) implies the arc length of the fitting arc segment, i.e. your "start" and "end" angle.

    But: Unless the "rail" curve of the fillet is very simple, i.e. planar, I would use spline interpolation and not arc segments. While splines can never be mathematically perfect circles, that does not matter here, since discrete geometry, i.e. polygons, can't either.

    If you want to have fillets with intersecting corners, .e.g. a cube with filleted edges, you can piece together the geometry with some clever construction of arc segments or curves, but depending on how complex the object is, SDS might be an easier way.

    Cheers,
    zipit



  • Hello @zipit ! Thanks again for the message.

    Filleted edges going different directions do not meet, thank God!

    If you take a look at the Python tag in the scene file I attached above, I had already done all of the steps in your list. There appears to be some exceptions though depending on the positions of the three points making the corner:

    • In my scene example, the arc spline works with the start & end angle correctly, but not the arc points.
    • When the startAngle is negative and the endAngle is positive, the arc spline flips (and the arc points are still incorrect).
    • How can I limit the size of the radius based on the available space?

    ** When both the startAngle & endAngle are negative, the arc spline and arc points are correct.

    As mentioned in the first two posts, those are the issues for which I'm seeking help.



  • Hi,

    sorry, I did not see that you already did that. Very cool little rig in your file ;) But I am sorry, I cannot debug your code above for you. Because I am not so well versed in trigonometry to immediately see what is going wrong there, I would have to work through it myself. And as already stated, I would personally use transforms, i.e. c4d.Matrix to encode the arcs. You could also use two quaternions and then just spherically interpolate between them. Your code is rather hard to understand in each detail (for me) in this raw form where you write out all the transforms as trigonometric expressions. Maybe some dev here is more well versed in the subject.

    And I would also point to curves/splines again, because it will simplify the problem (you do not have to think about circles anymore, just little boxes, i.e. where to place the control point(s) in that box). In case you are lost with splines, I did attach a little example for a quadratic Bezier curve which should work pretty well in your case.

    quadratic_bezier.c4d

    Cheers,
    zipit



  • Hi @blastframe I would say here I would simply do a SendModelingCommand, in worse case you shouldn't see any performance lost, since the operation will occur in C++ while your logics will be in Python, and in complex cases, this can be quiet computer heavy.
    Just in case in py-rounded_tube_r13 it was done this way because it's just a port of the C++ example roundedtube.cpp .

    But if you still want to implement the bevel yourself, then I guess @zipit already did all the job here :)
    Cheers,
    Maxime.



  • @zipit Absolutely incredible work! So elegant! Thank you very much 😃

    @m_adam Thank you for the reply and confirmation!