BUG: Inside offset of a loop of SketchControlPointSplines creates open loop

BUG: Inside offset of a loop of SketchControlPointSplines creates open loop

jiri.manak
Contributor Contributor
777 Views
8 Replies
Message 1 of 9

BUG: Inside offset of a loop of SketchControlPointSplines creates open loop

jiri.manak
Contributor
Contributor

Hello,

I have a loop made of 8 SketchControlPointSplines. If I make a offset inside the loop, it creates open loop - which doesn't create profile to extrude. However it works fine if the offset is made in outside direction.

To reproduce the bug, use attached Loop.dxf and attached python script.  Changing the sign of the offset distance

(line 35) will create outside offset, which works.

 

Do you have any idea for a workaround?

Is it possible to close the loop within a script?

 

Jiri

 

jirimanak_0-1662897058801.png

 

0 Likes
Accepted solutions (2)
778 Views
8 Replies
Replies (8)
Message 2 of 9

Jorge_Jaramillo
Collaborator
Collaborator
Accepted solution

Hi @jiri.manak ,

 

I was analyzing your DXF once loaded into Fusion and I found out that for some of the segments of the curve only one intersection curve is reported, and in only two cases the two adjacent curves are reported.

This is the snip I used for it:

 

    skt = root.sketches.itemByName(sketch_name)
    for curve in skt.sketchCurves:
        (ret, int_curves, int_points) = curve.intersections(None)
        app.log(f'{ret=} {len(int_curves)=} {len(int_points)=}')

 

 

Even though this, the curves do form a profile, which mean the curves are connected among them,  Very estrange this. 

 

To try to solve the issue, I loaded the DXF file into illustrator and generate a new DXF file.

With this new file both internal and external offsets the profiles worked with your script.

I'm attaching the file in case you might need it (loop2.dxf).

 

Hope this help.

 

Regards,
Jorge

0 Likes
Message 3 of 9

jiri.manak
Contributor
Contributor

Hi Jorge,

thanks for the investigation. I have accepted your solution, nevertheless it is still a bug. At least from my point of view.

If user imports such a loop/dxf which will do an extrusion, it will be problematic to explain to user that his sketch is not correct if used in my add-in. The behavior is inconsistent.

I have to try a way how to repair such loops - which are almost connected but not really connected.

(I will appreciate any hints)

regards

Jiri

 

 

0 Likes
Message 4 of 9

MichaelT_123
Advisor
Advisor

Hi Mr JiriManak,

 

Look at "Banach Space".  It would not be difficult to implement its principles in our add-in.

 

Regards

MichaelT

MichaelT
0 Likes
Message 5 of 9

jiri.manak
Contributor
Contributor

 

Mr. MichaelT,

Thank you for the tip for Banach space.

Thinking about to buy a book  "Understanding Banach Spaces", 478pages, only 50c a page. It will be nice reading for long winter evenings. 

StartPoint and EndPoints are read only. Which confused me. In a meantime I found out that through control points the curve is editable. Hopefully I will manage to find which points to connect. Without reading the book. 

 

Jiri

0 Likes
Message 6 of 9

Jorge_Jaramillo
Collaborator
Collaborator

Hi @jiri.manak ,

 

These are the two points that make the lines not to be closed connected:

(14.954504256447025, 3.854850104428831, 0.0)

(14.95450537511926, 3.854850957190464, 0.0)

 

I tried adding a line between them like so:

 

 

 

    sk = sketches.item(0)
    sk.sketchCurves.sketchLines.addByTwoPoints(
        adsk.core.Point3D.create(14.954504256447025, 3.854850104428831, 0.0),
        adsk.core.Point3D.create(14.95450537511926, 3.854850957190464, 0.0))

 

 

 

and it closed the profile:

wtallerdemadera_0-1663096135239.png

The distance between these two point is very tiny.

For me this is a BUG in Fusion360 which generates the two different points in the offset curve from the same point in the source curve.  I 'd checked the points in the source curve, and the points connect all the curves in the loop, otherwise it wont be selected as a profile.

 

Regards,
Jorge

Message 7 of 9

jiri.manak
Contributor
Contributor

Hi Jorge,

yea, to connect points by line is a good idea. However I need to find a more universal solution. It would be better to replace points by one point in the middle, or just to replace end point by the start point of the next curve.

Disadvantage of joining points simply by line sometimes creates broken surfaces - especially if the loop is outlined. See the pictures. 

I hope it will be possible by editing "sketchControlPointSpline_var.controlPoints"

 

Regards

Jiri

 

Screenshot 2022-09-13 215241.png

Screenshot 2022-09-13 215104.png

 

0 Likes
Message 8 of 9

Jorge_Jaramillo
Collaborator
Collaborator
Accepted solution

Hi @jiri.manak ,

 

I was able to modify a spline so that the set of splines form a profile.

Once you offset the curve, run this function with parameters sketcn_name and fix_gap set to True:

 

 

def close_loops(sketch_name: str, fix_gap: boolean):
    # created 2022-09-16 - Fusion360 ver 2.0.14106
    def change_point(loop: dict, point1_str: str, point2_str: str):
        if loop['start'] == point1_str:
            loop['start'] = point2_str
        else:
            loop['end'] = point2_str
        if loop['start'] == loop['end']:
            loop['closed'] = True

    def add_control_point(scps: adsk.fusion.SketchControlPointSpline, x: float, y:float, z:float):
        solution = 'B'

        (returnValue, controlPoints, degree, knots, isRational, weights, isPeriodic) = scps.geometry.getData()
        app.log(f'Control points before (#{len(controlPoints)}): {degree=}')
        for cp in controlPoints:
            app.log(f'    {cp.asArray()}')

        if solution == 'A':
            # to add a control point to the spline
            oc = scps.extend(adsk.core.Point3D.create(x, y, z), False)
            app.log(f'Solution A) ----> curve fixed! extended to: ({x}, {y}, {z})   {oc.count=}')

            (returnValue, controlPoints, degree, knots, isRational, weights, isPeriodic) = oc.item(0).geometry.getData()
        elif solution == 'B':
            # to replace the spline with a new created one using the same control points except for the last one
            parent_scps = scps.parentSketch.sketchCurves.sketchControlPointSplines
            new_controlPoints = [adsk.core.Point3D.create(p.x,p.y,p.z) for p in controlPoints]
            new_controlPoints[-1] = adsk.core.Point3D.create(x, y, z) # replace the last point by this new one
            returnValue = scps.deleteMe()  # needed to be deleted first
            n_scps = parent_scps.add(new_controlPoints, degree) # adding this spline it adds also construction lines for polygon
            app.log(f'Solution B)----> curve fixed! extended to: ({x}, {y}, {z})  --> curve deleted: {returnValue}')

            (returnValue, controlPoints, degree, knots, isRational, weights, isPeriodic) = n_scps.geometry.getData()
        app.log(f'Control points after (#{len(controlPoints)}): {degree=}')
        for cp in controlPoints:
            app.log(f'    {cp.asArray()}')

    skt = root.sketches.itemByName(sketch_name)
    if skt is None:
        app.log(f'sketch "{sketch_name}" not found.')
        return False
    app.log(f'{skt.sketchCurves.count=} {skt.sketchCurves.sketchControlPointSplines.count=}')
    loops = {}
    curve: adsk.fusion.SketchCurve
    curve_number=1
    for curve in skt.sketchCurves:
        if not isinstance(curve, adsk.fusion.SketchCircle) and not isinstance(curve, adsk.fusion.SketchEllipse) and not curve.isConstruction:
            start_point_str = curve.startSketchPoint.geometry.asArray()
            end_point_str = curve.endSketchPoint.geometry.asArray()
            app.log(f'{curve_number} {curve.objectType}  {start_point_str} {end_point_str}')
            start_curve = {key:val for key,val in loops.items() 
                                        if val['closed'] == False and (val['start'] == start_point_str or val['end'] == start_point_str)}
            end_curve = {key:val for key,val in loops.items() 
                                    if val['closed'] == False and (val['start'] == end_point_str or val['end'] == end_point_str)}
            n_curves = len(start_curve) + len(end_curve)
            if n_curves == 0:
                new_loop = {'closed': False, 'start': start_point_str, 'end': end_point_str, 'count':1}
                loops[len(loops)] = new_loop
            elif n_curves == 1:
                if len(start_curve):
                    (_, loop) = start_curve.popitem()
                    change_point(loop, start_point_str, end_point_str)
                else:
                    (_, loop) = end_curve.popitem()
                    change_point(loop, end_point_str, start_point_str)
                loop['count'] += 1
            else:
                (k1, loop1) = start_curve.popitem()
                (k2, loop2) = end_curve.popitem()
                if k1 == k2:
                    change_point(loop, start_point_str, end_point_str)
                    loop['count'] += 1
                else:
                    change_point(loop1,
                                start_point_str,
                                loop2['start'] if loop2['end'] == end_point_str else loop2['end'])
                    loop1['count'] += loop2['count'] + 1
                    del loops[k2]
                pass
        curve_number += 1
    loop_number = 1
    for loop in loops.values():
        if loop['closed']:
            app.log(f"loop#{loop_number} closed with {loop['count']} segments")
        else:
            (segm1X, segm1Y, segm1Z) = (float(x) for x in loop['start'])
            (segm2X, segm2Y, segm2Z) = (float(x) for x in loop['end'])
            app.log(f"loop#{loop_number} not closed. DeltaX={segm1X-segm2X} DeltaY={segm1Y-segm2Y} DeltaZ={segm1Z-segm2Z} with {loop['count']} segments")
            app.log(f"    start={loop['start']}")
            app.log(f"    end  ={loop['end']}")
            if fix_gap and abs(segm1X-segm2X) < 0.0001:
                for curve in skt.sketchCurves:
                    if isinstance(curve, adsk.fusion.SketchControlPointSpline):
                        # correct loop ONLY if curve is Sketch Control Point Spline
                        scps = adsk.fusion.SketchControlPointSpline.cast(curve)
                        end_point_str = scps.endSketchPoint.geometry.asArray()
                        if end_point_str == loop['start']:
                            add_control_point(scps, segm2X, segm2Y, segm2Z)
                            break
                        elif end_point_str == loop['end']:
                            add_control_point(scps, segm1X, segm1Y, segm1Z)
                            break
        loop_number += 1
    app.log(f'{skt.sketchCurves.count=} {skt.sketchCurves.sketchControlPointSplines.count=}')

 

 

 

It will iterate over the curves, to identify which set is open (not a profile) or not.

If you run it a second time, you will get the two curves as closed.

 

Lesson learned here:

The control points can be obtained from SketchControlPointSpline.geometry.getData(); the second returned value is the control points of the curve.

 

You will see I add solutions A and B on add_control_point function.

With solution A a new control point is added. Even the curves will form a profile, running the function for the second time won't report inner curve as closed because the endpoint is being calculated to some other point (here further investigation to understand better splines).

With solution B the last curve of the set is replaced to close the gap; in that new curve the end point is replaced by the start point of the first curve of the set of splines, using the same control points of the former spline curve.

 

Hope this function could help to correct the curves while a solution could be obtained from Fusion360 team.

 

Regards,
Jorge

0 Likes
Message 9 of 9

jiri.manak
Contributor
Contributor

it looks the only solution is to create a new spline curve

this is the best solution despite the new curve slightly differs from the original one

 

jirimanak_0-1663691011083.png

 

 

0 Likes