CADability icon indicating copy to clipboard operation
CADability copied to clipboard

Spline is not imported correctly

Open dsn27 opened this issue 1 year ago • 4 comments

The attached spline is not imported correctly.

2 final_neu.zip

Result now: grafik

In the old Cadability this drawing is imported as a path and looks a lot better: grafik

The reason for this, is this check:

for (int i = 0; i < poles.Length; ++i)
{
    poles[i] = GeoPoint(controlPoints[i]);
    //OdGePoint3d ctrlpnt = new OdGePoint3d();
    //spline.getControlPointAt(i, ref ctrlpnt);
    //GeoPoint dbg = GeoPoint(controlPoints[i]);
    if (i > 0 && (poles[i] | poles[i - 1]) < Precision.eps)
    {   // doppelte pole sind i.A. knickstellen und am Anfang oder Ende singulär.
        // wenn wir hier null liefern, dann wird ein Polygon draus gemacht (cat1.dxf)
        return null;
    }
}

I tried the same thing for the new version:

if (i > 0 && (poles[i] | poles[i - 1]) < Precision.eps)
{
    //How to determine the precision that must be used?
    var p2d = spline.ToPolyline2D(spline.ControlPoints.Length + spline.Knots.Length + 1);
    return CreatePolyline2D(p2d);
}

New result: grafik

The questions are:

  • How to determine the precision (number of knots) that must be used?
  • Is Polyline2D always the right type or could it be Polyline3D?
  • Is there a different way to import this Spline correctly?

Any comments would be appreciated.

Michel

dsn27 avatar Aug 08 '24 12:08 dsn27

I would like to re-open this for discussion if nobody is opposed. A little quick background, I am using CADAbility for a project that is heavily dependent on AutoCAD import/export. netDxf is severely lacking for my needs (and the original maintainer has completely abandoned it) so I've been doing an extremely deep dive into improving AutoCAD DXF and DWG support over the last couple of months. I don't like the idea of implicitly approximating a spline to a polyline on import as my intent may be to manipulate that spline before converting it to a path myself using my own parameters.

I added this drawing to my tests for spline import and I don't think the import is the problem. In the screenshot below, I've imported the DXF without the polyline conversion check. You can see on hover, the CADAbility app is detecting the path of the spline in the correct place exactly where one would expect it. It is the rendering that is incorrect.

Image

Admittedly, I know little to nothing about Winforms/WPF/etc. I'm using CADAbility as a headless library to implement in another project. I don't know how it's calculating the geometric location for selection. Based on my testing it looks like the BSpline object is being rendered by approximating it to a polyline via ICurve.Approximate() from BSpline.GetCachedApproximation() in BSpline.PaintTo3D(). I disabled the implicit polyline conversion locally and ran the code below to import the spline and export it after approximating it by several different methods.

var psmFiles = new string[] { Path.GetFullPath(@"E:\Tests\2test.dxf") };

Settings.GlobalSettings.SetValue("DxfExport.ExportPathsAsBlocks", false);

foreach (var dxfFile in psmFiles)
{
    Console.WriteLine($"Processing {dxfFile}");
    ProcessDxf(dxfFile);
}

void ProcessDxf(string dxfFile)
{
    var import = new CADability.DXF.Import(dxfFile).Project;
    //var import = new CADability.DXF.Import2(dxfFile).Project;
    
    
    var newProj = Project.CreateSimpleProject();
    var newModel = newProj.GetModel(0);
    var bsplineImport = import.GetModel(0).AllObjects[0] as BSpline;
    var bsplineProjected = (bsplineImport as ICurve).GetProjectedCurve(Plane.XYPlane);
    var bspline2DP = bsplineProjected as BSpline2D;
    var bsplineReducePoles = bsplineImport.Clone() as BSpline;
    bsplineReducePoles.ReducePoles(0.18);
    var bsplineGeoList = new CADability.GeoObject.GeoObjectList() { bsplineImport };
    
    // Make BSpline2D?
    var bspline2DPGeo = bspline2DP.MakeGeoObject(Plane.XYPlane);
    bspline2DPGeo.Layer = newProj.LayerList.CreateOrFind("BSpline2D");
    newModel.Add(bspline2DPGeo);
    
    //Approximate BSpline to Polyline using PointAtParam();
    var paramPoints = new List<GeoPoint>();
    for (double t = 0; t <= 1; t += 0.01)
    {
        paramPoints.Add(bsplineImport.PointAtParam(t));
    }
    paramPoints.Add(bsplineImport.PointAtParam(1));
    var paramPoly = Polyline.FromPoints(paramPoints.ToArray());
    paramPoly.Layer = newProj.LayerList.CreateOrFind("Approx_PointAtParam");
    newModel.Add(paramPoly);
    
    //Approximate BSpline to Path using Reduce2D()
    var splineReducePath = new Reduce2D();
    splineReducePath.OutputMode = Reduce2D.Mode.Paths;
    splineReducePath.Precision = 0.001;
    splineReducePath.Add(bsplineGeoList, Plane.XYPlane);
    var splinePath = splineReducePath.Reduced;

    foreach (var entity in splinePath)
    {
        var go = entity.MakeGeoObject(Plane.XYPlane);
        go.Layer = newProj.LayerList.CreateOrFind("Approx_Reduce2D_Path");
        newModel.Add(go);
    }

    //Approximate BSpline to Polyline using Reduce2D();
    var splineReducePolylines = new Reduce2D();
    splineReducePolylines.OutputMode = Reduce2D.Mode.Polylines;
    splineReducePolylines.Precision = 0.001;
    splineReducePolylines.Add(bsplineGeoList, Plane.XYPlane);
    var splinePolyline = splineReducePolylines.Reduced;
    
    foreach (var entity in splinePolyline)
    {
        var go = entity.MakeGeoObject(Plane.XYPlane);
        go.Layer = newProj.LayerList.CreateOrFind("Approx_Reduce2D_PL");
        newModel.Add(go);
    }

    // Approximate BSpline to Polyline using ICurve.Approximate();
    // Same method as BSpline.PaintTo3D
    var approxCurve = (bsplineImport as ICurve).Approximate(true, 0.18);
    if (approxCurve is CADability.GeoObject.Path)
    {
        CADability.GeoObject.Path path = approxCurve as CADability.GeoObject.Path;
        var approximation = new GeoPoint[path.CurveCount + 1];
        for (int i = 0; i < path.CurveCount; ++i)
        {
            approximation[i] = path.Curve(i).StartPoint;
        }
        approximation[path.CurveCount] = path.Curve(path.CurveCount - 1).EndPoint;
        var polyline = Polyline.FromPoints(approximation);
        polyline.Layer = newProj.LayerList.CreateOrFind("Approx_ICurve");
        newModel.Add(polyline);
    }
    
    // Re-export original BSpline as-is
    bsplineImport.Layer = newProj.LayerList.CreateOrFind("Exported_Original");
    newModel.Add(bsplineImport);
    
    // Export reduced poles geometry
    bsplineReducePoles.Layer = newProj.LayerList.CreateOrFind("Exported_ReducedPoles");
    newModel.Add(bsplineReducePoles);

    var export = new CADability.DXF.Export(DxfVersion.AutoCad2000);
    //var export = new CADability.DXF.Export2(ACadVersion.AC1032);
    
    export.WriteToFile(newProj, $"{outDir}{Path.GetFileNameWithoutExtension(dxfFile)}-out.dxf");
}

If you generate the drawing yourself or open the attached output in AutoCAD, you'll see several different layers which represent the different methods for exporting. The interesting bit is the splines that are created by Reduce2D match the bad approximation perfectly, and you can see they have control points that don't match the original.

I've narrowed down the source of most of these issues to ICurve.GetProjectedCurve() in BSpline.cs. The identicalPoles check is removing "duplicate" poles which are essential to representing the sharp corners in the original spline. This is causing a loss of fidelity before the spline is being approximated to a polyline or path. If I remove that check, then BSpline.Approximate and Reduce2D all produce good polylines or splines, depending on the parameters. It also resolves the rendering issue in CADAbility.App:

Image

The workaround in the PR that resolved this issue used the NURBS Evaluator method from netDxf to generate a polyline before import to sidestep the issue with the translation from BSpline to BSpline2D.

Based on my translation of this comment, it looks like this was implemented to resolve some other bug(s) that were popping up from duplicate poles being generated from projecting 3D splines?:

// For now, let's assume that the 2D BSpline is created with the same parameters
// as the 3D BSpline; only the points are projected onto the plane.
// Is that correct?
// Yes, that seems to be correct; it is stated that way in the NURBS book for affine transformations,
// at least as long as no identical poles are generated. If identical poles are present,
// the 2D BSpline behaves strangely, and DirectionAt can become 0.

Skimming the code block that is triggered on identicalPoles it looks like it's trying and failing to approximate the NURBS using through points. I'm far from an expert on NURBS, but I don't think that's possible without generating even more through points than the original pole count. My collection of drawings to test on is all 2D so I can't see if any 3D splines would have issues with removing this check.

BSpline.ReducePoles() is using some other method to attempt to reduce the poles in a spline that is causing the result to be far outside of the provided tolerance. I haven't dug into that one and I assume based on the name that it may just be incompatible with splines that have necessary duplicate poles.

It's also worth noting here that this spline will not export correctly from netDxf as the periodic parameter is being set based on the closed parameter. This is a closed non-periodic spline so it ends up with an invalid number of knots when attempting to export it and throws an exception.

Can anyone identify any splines that present issues with duplicate poles being projected which would require the check currently in ICurve.GetProjectedCurve()? Is this really necessary or can those issues be resolved in a way that allows splines like this to be converted to BSpline2D correctly?

2-out.zip

2-out-FixedKinda.zip

ChrisClems avatar Jan 22 '25 18:01 ChrisClems

I wanted to include this but I ran out of time to split these changes out of my AutoCAD testing branch before leaving work. You can get up and running testing this quickly using this branch: https://github.com/ChrisClems/CADability/tree/pointy-spline-fix

ChrisClems avatar Jan 23 '25 02:01 ChrisClems

Hi Chris.

Thank you for your detailed input and for reopening the discussion.

netDxf is severely lacking for my needs (and the original maintainer has completely abandoned it) so I've been doing an extremely deep dive into improving AutoCAD DXF and DWG support over the last couple of months.

I share your dissatisfaction with the way netDxf currently works, especially with its lack of support for older DXF versions and DWG files. During my search for alternatives, I came across ACadSharp, which still seems to be actively supported—it might be worth exploring.

The workaround in the PR that resolved this issue used the NURBS Evaluator method from netDxf to generate a polyline before import to sidestep the issue with the translation from BSpline to BSpline2D.

If you found a proper solution instead of my relying on my workaround that would be really great.

It also seems like you’re much deeper into the details of AutoCAD import/export than I am, and we greatly appreciate the effort you’re putting into this.

Can anyone identify any splines that present issues with duplicate poles being projected which would require the check currently in ICurve.GetProjectedCurve()? Is this really necessary or can those issues be resolved in a way that allows splines like this to be converted to BSpline2D correctly?

That's an excellent question. I can't currently identify any specific cases where splines with duplicate poles cause significant issues during projection, but it might be worth testing against a broader variety of sample files to confirm.

@SOFAgh Any comments on this?

At the moment, we’re working on plans to develop CADAbility 2.0. We’re hosting monthly Teams meetings to discuss the future direction of the project and how to proceed. If you’re interested in joining us, we’d love to have you involved! Please let us know your time zone so we can schedule a suitable time for everyone.

dsn27 avatar Jan 25 '25 12:01 dsn27

I share your dissatisfaction with the way netDxf currently works, especially with its lack of support for older DXF versions and DWG files. During my search for alternatives, I came across ACadSharp, which still seems to be actively supported—it might be worth exploring.

I've been working on implementing ACadSharp with netDxf import/export feature parity over the last few months. It's in a very usable state for basic entities. I've implemented it as an Import2 and Export2 class. You can test it out here. I still have a lot of testing to do, as well as just a general once-over cleanup pass. I've done very little testing on hatches, solids, etc. I implemented it basically in-place by going through one entity at a time to convert netDxf classes and members to be ACadSharp compatible.

The only big limitation that I've run into in ACadSharp is the XData support is in heavy active development. I have a fork with some dirty stop-gap patches for testing purposes, but I expect official support will be finished soon.

That's an excellent question. I can't currently identify any specific cases where splines with duplicate poles cause significant issues during projection, but it might be worth testing against a broader variety of sample files to confirm.

I've got a library of several tens of thousands of drawings to test on, but unfortunately they're not great for testing. Almost all of them are exported from Solid Edge and they're all 2D. High in quantity but low in diversity.

At the moment, we’re working on plans to develop CADAbility 2.0. We’re hosting monthly Teams meetings to discuss the future direction of the project and how to proceed. If you’re interested in joining us, we’d love to have you involved! Please let us know your time zone so we can schedule a suitable time for everyone.

I'm not sure how much help I would be. I'm not a particularly talented programmer nor an engineer. My field is CAD/CAM automation. However I'd be happy to join if the scheduling works out. I'm in Pacific time (UTC -7 or 8)

ChrisClems avatar Jan 25 '25 17:01 ChrisClems