Cleaning up imported drawings

Drawings exported by plotting, laser and graphics software often carry their curves as many short lines, their straight sides in pieces and their ends a hair apart. planex2d reads such a drawing, closes the gaps, fits arcs back to the tessellated curves and checks how far the result moved, so that the part is ready for Boolean operations, meshing or a CAM program. The same cleanup works on a face from a DXF drawing, a planar STEP or BREP file, a sketch from a .CATPart file, a circuit board, a Shapely polygon or a list of measured points. The first cell is the complete workflow, ready to copy and run, and the section Step by step runs it in parts, with the plots one looks at along the way.

The example is a pump-cover gasket of 100 × 70 mm with corners of radius 10, an opening of diameter 40 and four bolt holes of diameter 6.6, in the file gasket.svg. Every curve in it is a polyline, the holes 72-gons and the corners 12 segments each, its straight sides come in four pieces, its coordinates are rounded to 0.01 mm, and its outline stops 0.005 mm short of its start.

[2]:
import planex as px

raw = px.Face(px.FromSVG("gasket.svg", tol=1e-5))
gasket = raw.Simplified(fit_arcs=dict(tol=2e-5))
print(len(raw.edges), "edges read |", len(gasket.edges), "edges after the cleanup")
print("radii", sorted({round(e.edge.radius, 7) for e in gasket.edges if hasattr(e.edge, "radius")}))
print("largest deviation", gasket.Hausdorff(raw))
px.WriteDXF(gasket.wires, "gasket-clean.dxf", unit=px.mm)
gasket
412 edges read | 18 edges after the cleanup
radii [0.0033, 0.0100008, 0.02]
largest deviation 2.3465725344733573e-05
[2]:
../_images/workflows_cleaning-up-a-drawing_2_1.svg

Step by step

Finding what is wrong

Read without a tolerance, the drawing gives six wires, and the outline is the one that does not close. OpenEnds lists its loose ends by edge and end, here the start of its first edge and the end of its last (see Small gaps). tol joins ends that lie closer together than it, in meters, since the reader takes the unit from the file’s width and height, millimeters here, and returns meters (see SVG files). The face then holds 412 edges, all of them lines, with a vertex at every point of the curves’ polylines (Figure 1). The reader has already joined the four pieces of each straight side, since they lie exactly on one line.

[3]:
import planex as px

print("closed", [w.IsClosed() for w in px.FromSVG("gasket.svg")])
print("open ends", [w.OpenEnds() for w in px.FromSVG("gasket.svg") if not w.IsClosed()])
raw = px.Face(px.FromSVG("gasket.svg", tol=1e-5))
print(len(raw.edges), "edges, all lines")
# px.Plot(raw)
closed [False, True, True, True, True, True]
open ends [[(0, False), (63, True)]]
412 edges, all lines
The gasket as read: a vertex at every point
Figure 1: The gasket read from its SVG file

Cleaning up

Simplified returns a cleaned copy and leaves the face as read. fit_arcs fits arcs to runs of short lines that follow a circle, here within 0.02 mm of the drawing’s points, twice the rounding of its coordinates (see Fitting arcs). The gasket then has 18 edges, which are four straight sides, four corner arcs of radius 10 and five holes of two arcs each. The corners come out 0.0008 mm larger than drawn, because the drawing’s points are rounded to 0.01 mm (Figure 2).

[5]:
gasket = raw.Simplified(fit_arcs=dict(tol=2e-5))
print(len(gasket.edges), "edges after the cleanup")
print("radii", sorted({round(e.edge.radius, 7) for e in gasket.edges if hasattr(e.edge, "radius")}))
# px.Plot(gasket)
18 edges after the cleanup
radii [0.0033, 0.0100008, 0.02]
After the cleanup: lines and arcs
Figure 2: The gasket cleaned up

Other drawings need the other steps of Simplified as well. merge_collinear_closer joins straight pieces that lie nearly but not exactly on one line (see Collinear lines), and remove_edges_smaller removes edges far shorter than the part (see Small edges). See Defeaturing and simplification for every step, its defaults and Simplify, which cleans the face in place.

Checking the result

The Hausdorff distance between the cleaned face and the face as read is the largest distance from a point of one outline to the other (see Part analysis). It is 0.023 mm, the height of the polylines’ chords over the arcs they cut, so the cleanup moved nothing that the drawing actually holds:

[7]:
print("largest deviation", gasket.Hausdorff(raw))
largest deviation 2.3465725344733573e-05

Writing the clean drawing

px.WriteDXF of the face’s wires writes the outlines alone, without a fill, for a CAM program or a laser cutter. unit=px.mm writes millimeters, converted from the meters of the geometry, and the drawing read back is 100 × 70 mm again, 0.1 × 0.07 in meters. See Files for the other writers.

[8]:
px.WriteDXF(gasket.wires, "gasket-clean.dxf", unit=px.mm)
print(px.Compound(px.FromDXF("gasket-clean.dxf")).bounding_box)
((0.0, -0.07), (0.10000000000000003, 4.440892098500626e-18))

The clean drawing is gasket-clean.dxf.

Other formats

Every reader returns wires that the same cleanup takes:

Format

Reader

See

SVG

px.FromSVG(path, unit=..., tol=...)

SVG files

DXF

px.FromDXF(path, layers=..., unit=..., tol=...)

Files

STEP, BREP (planar)

px.FromStep(path), px.FromBrep(path)

STEP and BREP

KiCad, Altium, Eagle and other boards

px.FromKiCad(path)

KiCad, Altium, Eagle and more

Shapely

px.FromShapely(geometry)

Shapely

.CATPart sketches

px.FromCATPart(path)

CATIA V5

Measured or traced points

px.FromPolyline(points, closed=True)

Fitting arcs

Pitfalls

  • The fit’s tolerance. It must be at least as large as the rounding of the drawing’s coordinates, or no arc fits. A much larger one fits arcs to runs of short lines that the part does not have as arcs. Each fit is checked against the edges it replaces and rejected if it strays further.

  • Real small features. tol and remove_edges_smaller must stay below the smallest real feature of the part, such as a narrow slot or a short chamfer.

  • Units. An SVG file whose width names no unit is read as CSS pixels of 1/96 inch, with a warning, and unit= sets the unit instead. All tolerances are in meters, as the geometry is.

  • Free-form curves. A curve that is no circle, such as an airfoil or a cam, takes fit_splines instead of fit_arcs. See Fitting splines.

In the browser

The contour checker finds open contours, gaps, doubled lines and small edges in a DXF, SVG or STEP file in the browser and repairs them, with the same calls. The converters turn DXF, SVG and STEP files into each other.