Symmetric difference
a ^ b keeps what lies in exactly one of two faces, the union less the intersection, and px.SymmetricDifference does the same for a list of parts, keeping what an odd number of them covers. The result is the region where the parts disagree, which makes it the natural comparison of two versions of a part, or of a part and its nominal geometry. Each face of the result keeps the name and the color of the part it comes from, so the material that one part adds and the material it removes can
be told apart.
[1]:
import math
import planex as px
Two revisions of a part
The example is a mounting plate in two revisions. Revision A is 80 × 50 with two holes of diameter 8, 50 apart. Revision B is 10 longer, and its second hole has moved with the right edge, to 60 from the first (Figure 1a). The symmetric difference holds three faces. Revision B adds two of them, the strip of 10 × 50 and the disc where the old hole was, and it removes the third, the disc of its new hole, from the material of revision A (Figure 1b).
[3]:
old = px.QuickSketch().Rectangle(80, 50).Circle((15, 25), 4).Circle((65, 25), 4).Face()
new = px.QuickSketch().Rectangle(90, 50).Circle((15, 25), 4).Circle((75, 25), 4).Face()
old.name, new.name = "revision A", "revision B"
change = old ^ new
print([f.name for f in change.faces])
['revision A', 'revision B', 'revision B']
(a) Revision B, with revision A dashed
(b) Added in cyan, removed in red
Selected by name, the faces give the material added and the material removed. The first is the strip and one hole, the second one hole:
[5]:
added = sum(f.mass for f in change.faces(name="revision B"))
removed = sum(f.mass for f in change.faces(name="revision A"))
print("added", added, "exact", 10 * 50 + math.pi * 4**2)
print("removed", removed, "exact", math.pi * 4**2)
added 550.2654824574367 exact 550.2654824574367
removed 50.26548245743669 exact 50.26548245743669
The symmetric difference is the same region as (a - b) + (b - a), made in one operation, and its area is the area of both parts less twice the area they share:
[6]:
print("area", change.mass, "exact", old.mass + new.mass - 2 * (old * new).mass)
area 600.5309649148735 exact 600.530964914873
An eccentric bore
The area of the symmetric difference measures how far a part is from its nominal geometry. The example is a bore of diameter 20 drilled 0.5 off its position. As discs, the nominal and the drilled bore differ in two crescents, at most 0.5 wide. The crescent on the right is material that the drill cut away outside the nominal bore, and the one on the left is material that it left standing inside the nominal bore (Figure 2). Two circles of radius \(r\) whose centers are \(e\) apart share a lens of area \(2 r^2 \arccos\left(\tfrac{e}{2r}\right) - \tfrac{e}{2} \sqrt{4r^2 - e^2}\), so the crescents have the area of both discs less twice the lens. For an offset small against the radius, that is close to \(4 r e\).
[7]:
r = 10
e = 0.5
nominal = px.QuickSketch().Circle((0, 0), r).Face()
drilled = px.QuickSketch().Circle((e, 0), r).Face()
crescents = nominal ^ drilled
lens = 2 * r**2 * math.acos(e / (2 * r)) - e / 2 * math.sqrt(4 * r**2 - e**2)
print(len(crescents.faces), "faces, area", crescents.mass, "exact", 2 * (math.pi * r**2 - lens), "4 r e", 4 * r * e)
2 faces, area 19.997916471310532 exact 19.997916471310532 4 r e 20.0
(a) The nominal and the drilled bore
(b) Cut away in red, left standing in cyan
Many parts
px.SymmetricDifference keeps the regions that an odd number of the parts covers, and like px.Unify it runs on all cores. This is the even-odd rule by which SVG and PostScript fill overlapping outlines. The example lays four horizontal bars of 80 × 10 over four vertical ones, every other row and column of a board of 8 × 8 squares of 10. A square lies in one bar where only its row or only its column has one, and in two bars, or none, where both or neither have one. The odd squares are
those of a checkerboard, 32 of them, which touch only at their corners (Figure 3):
[9]:
rows = [px.QuickSketch((0, 10 * k)).Rectangle(80, 10).Face() for k in (1, 3, 5, 7)]
columns = [px.QuickSketch((10 * k, 0)).Rectangle(10, 80).Face() for k in (1, 3, 5, 7)]
board = px.SymmetricDifference(rows + columns)
print(len(board.faces), "faces, area", board.mass, "exact", 32 * 10**2)
32 faces, area 3200.0 exact 3200
(a) Four rows and four columns
(b) Their symmetric difference
See Two faces for the other operations on two faces, and Many faces at once for lists of faces on all cores.