Skip to content

Limit DXF BSpline Degree #1226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Feb 21, 2023
Merged

Conversation

sapphire-arches
Copy link
Contributor

Most DXF renderers and processing tools can't handle BSplines with degree >= 3 (order >= 4). For maximum compatibility, we should approximate such BSplines using degree-3 splines. This change uses the OpenCascade facilities to do so, though ezdxf.math also provides some spline approximation facilities that could be used. Using the OpenCascade approach allows us to match FreeCAD's parameters which are presumably tuned on a diversity of real-world designs.

Fixes #1225

Most DXF renderers and processing tools can't handle BSplines with
degree >= 3 (order >= 4). For maximum compatibility, we should
approximate such BSplines using degree-3 splines. This change uses the
OpenCascade facilities to do so, though ezdxf.math also provides some
spline approximation facilities that could be used. Using the
OpenCascade approach allows us to match FreeCAD's parameters which are
presumably tuned on a diversity of real-world designs.

Fixes CadQuery#1225
@sapphire-arches
Copy link
Contributor Author

This ends up having a pretty severe impact on the accuracy of the exported DXFs, degrading the output quality by a few orders of magnitude. We could probably recover that by reducing the allowed approximation error in GeomConvert_ApproxCurve but it's unclear if the accuracy is critically important.

@codecov
Copy link

codecov bot commented Jan 12, 2023

Codecov Report

Merging #1226 (59543da) into master (2356028) will increase coverage by 0.06%.
The diff coverage is 98.66%.

❗ Current head 59543da differs from pull request most recent head 0b99e28. Consider uploading reports for the commit 0b99e28 to get more accurate results

@@            Coverage Diff             @@
##           master    #1226      +/-   ##
==========================================
+ Coverage   94.07%   94.14%   +0.06%     
==========================================
  Files          26       26              
  Lines        5421     5451      +30     
  Branches      919      925       +6     
==========================================
+ Hits         5100     5132      +32     
+ Misses        190      189       -1     
+ Partials      131      130       -1     
Impacted Files Coverage Δ
cadquery/__init__.py 87.50% <0.00%> (ø)
cadquery/assembly.py 96.56% <ø> (ø)
cadquery/cq.py 92.19% <ø> (-0.01%) ⬇️
cadquery/cqgi.py 80.73% <ø> (ø)
cadquery/selectors.py 98.87% <ø> (ø)
cadquery/occ_impl/assembly.py 98.60% <100.00%> (+0.15%) ⬆️
cadquery/occ_impl/exporters/__init__.py 94.11% <100.00%> (+1.68%) ⬆️
cadquery/occ_impl/exporters/assembly.py 100.00% <100.00%> (ø)
cadquery/occ_impl/exporters/dxf.py 92.42% <100.00%> (+1.04%) ⬆️
cadquery/occ_impl/shapes.py 92.72% <100.00%> (+0.07%) ⬆️
... and 1 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@jmwright
Copy link
Member

DXF is also used to generate CNC gcode, and the current behavior is non-approximated, so I don't know if this approximation should be the default.

@sapphire-arches
Copy link
Contributor Author

The tolerance specified is for 1 micrometer in a mm scale drawing, or 1 thou on an inch-scale drawing. I suppose the later is probably worth worrying about. If you have suggestions on how we should plumb through specifying whether or not to approximate, I'd be happy to implement that change.

@jmwright
Copy link
Member

@bobtwinkles I'd like to hear from others about this too, but my first thought was to leave the default at un-approximated and add an option somewhere to approximate splines during the export. However, that forces the CQ dev to know about the option and use it correctly, and end users of the DXF will be stuck if the dev chooses incorrectly.

I guess I don't really understand the scope of the problem. How many applications are not able import our DXF splines now versus how many will have that problem after approximation? Do we know if FreeCAD approximates splines during their DXF export? If they do it, we'd probably be safe to as well.

@adam-urbanczyk
Copy link
Member

Thanks for the PR @bobtwinkles . I agree with @jmwright , this has to be an option (e.g. approximate : Optional[Literal['spline', 'arc']] = None. I want to be able to use the current exact approach.

You had to modify the tests significantly, so there is something wrong in the approximation. 1e-6 error cannot result in up to 50% change of the area. -1 for merging until this is fixed/understood.

BTW: you can look into ShapeCustom_BSplineRestriction.

@sapphire-arches
Copy link
Contributor Author

However, that forces the CQ dev to know about the option and use it correctly, and end users of the DXF will be stuck if the dev chooses incorrectly.

Right, this is my concern as well. If I do implement an option I'll try to drop a note in the docs to point out this potential footgun which will hopefully help avoid the problem.

How many applications are not able import our DXF splines now versus how many will have that problem after approximation? Do we know if FreeCAD approximates splines during their DXF export?

Nothing I have to hand (LibreCAD and the Ponoko/SendCutSend online processors) can import the unapproximated DXFs correctly (except round-tripping to other OpenCascade-based software like cadquery and FreeCAD). My implementation is basically a direct port of the FreeCAD 0.20 DXF export logic.

You had to modify the tests significantly, so there is something wrong in the approximation. 1e-6 error cannot result in up to 50% change of the area. -1 for merging until this is fixed/understood.

I don't think the test changes indicate there's a 50% change in area? The changes are loosening the acceptable order of magnitude difference by 2-3 notches, not changing the value being compared with. All cases still require 1E-3 agreement or better, which matches the tolerance of the approximation (1E-3). Or really it's better, since most of the tests check area while the approximation is a tolerance on the wires.

BTW: you can look into ShapeCustom_BSplineRestriction.

Looks like that uses ApproxCurve internally, so I don't know if we would see any benefit from using the more generic interface.

@adam-urbanczyk
Copy link
Member

My bad, I confused the tolerance for the test value. I think you are making the problem bigger than it is, random online dxf viewer does display your shape correctly: https://sharecad.org/.

Regarding the function - I'd prefer that the conversion is handled at the Shape (a.k.a. topological level) so that you don't have to modify the individual geometry handling routines. Next user will want arc spline approximation...

@sapphire-arches
Copy link
Contributor Author

Regarding the function - I'd prefer that the conversion is handled at the Shape (a.k.a. topological level) so that you don't have to modify the individual geometry handling routines.

Ah, I see. Do you mean we should add a method to Workplane or Sketch to wrap the current shape in a ShapeCustom_BSplineRestriction? That wouldn't be too bad to implement but would be a poor user experience IMO. ShapeCustom_BSplineRestriction will convert all curves of the underlying shape to BSplines including conics. The important case there being circles, which (again referring to the online fabrication quoting services I'm familiar with) will break "tappable hole" detection. That can itself be worked around by doing the approximation before forming any circular features, but that forces users to think about low-level representation details instead of the natural order of expression of their model. In the case of the part I was actually working on, I'd need to restructure a few dozen operations to make that work. For this reason I'd prefer doing this at the export layer, not while building the shape.

@adam-urbanczyk
Copy link
Member

The idea would be to implement the approximation functionality in say cq.Shape, and do not touch the _dfx_spline function. The approximation function would be optionally called based on inputs parameters of the dxf export function.

If ShapeCustom_BSplineRestriction does not work well enough, your current approach is also fine.

This adds plumbing through the option infrastructure to make the DXF
approximation optional, and expose the important control parameters to
the user with reasonable defaults. Includes some additional
documentation to ease discovery and explain why that should be
important.
@sapphire-arches
Copy link
Contributor Author

I've updated this PR with plumbng to make the approximation optional, and some documentation about why it might be required. I ended up reusing the opt infrastructure that the SVG and VRML exporters use, though I didn't reuse the linear/angular specification because they don't mean exactly the same thing as they do for those exporters. Maybe it should though.

@adam-urbanczyk adam-urbanczyk self-requested a review February 5, 2023 18:11
@adam-urbanczyk adam-urbanczyk mentioned this pull request Feb 10, 2023
2 tasks
Copy link
Member

@lorenzncode lorenzncode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started testing toSplines and found a case where the degree was not restricted to the specified value (3).

pts = [(0, 0), (10, 10), (20, -10)]
wp = cq.Workplane().splineApprox(pts, 1e-3, 3, 6)

This resulted in degree = 5. After toSplines (with default args or with reduced tol) the degree was not reduced.

Original edge on left, with translated and restricted version on right (with display of poles).

image

Changing pts to the following resulted in degree = 6.
pts = [(0, 0), (10, 10), (20, -10), (30, 10)]

With toSplines, it resulted in degree = 5.
image

@adam-urbanczyk
Copy link
Member

@lorenzncode thanks! Using C0 or G2 seems to solve the issue.

Copy link
Member

@jmwright jmwright left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bobtwinkles !

Copy link
Member

@lorenzncode lorenzncode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the approx="arc" and did not find any problem. Added minor doc suggestions. Thanks @adam-urbanczyk and @bobtwinkles!

adam-urbanczyk and others added 3 commits February 21, 2023 07:01
@adam-urbanczyk
Copy link
Member

Merging, thank you @bobtwinkles !

@adam-urbanczyk adam-urbanczyk merged commit 4c2a038 into CadQuery:master Feb 21, 2023
@lorenzncode lorenzncode mentioned this pull request Mar 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DXF export of filleted splines is missing fillet arcs
4 participants