Scroll compressor
A scroll compressor has two spiral wraps, one fixed and one that orbits without turning. Where the wraps touch, they close crescent pockets of gas. As the orbiting wrap goes round, each pocket moves inward and shrinks, from the suction at the rim to the discharge in the middle.
The wraps are involutes of a circle, and involutes of one circle are parallel curves, so the pockets have closed forms. For the pitch \(P\), the distance from one turn of a wrap to the next, and the wrap thickness \(t\), the \(i\)-th pocket pair at the crank angle \(\theta\) holds \(\pi P (P - 2t)\,h\,[(2i - 1) - \theta/\pi]\) in the standard model, for the wrap height \(h\). The example finds the pockets with Boolean operations. Where the wraps end sets the size of the pockets, so the example checks what holds for any wrap. The pockets must come in pairs of equal area, shrink at \(P\,(P - 2t)/2\) per radian, and step by \(\pi P (P - 2t)\) from one pocket to the next.

[1]:
import math
import planex as px
The wraps
The base circle has the radius \(a = 1\), so the pitch is \(P = 2\pi a\), and the wraps are \(t = 1\) thick. A point of the involute at the angle \(\varphi\) lies \(a\varphi\) along the tangent where the circle’s radius at \(\varphi\) ends. The wrap lies between two involutes, the second turned by \(t/a\). The point across the wrap from \(\varphi\) lies on the same normal, at \(\varphi - t/a\). Each flank is drawn from its formula with px.Curve, with
\(\varphi\) in degrees and in radians where it multiplies, and two lines along the normals close the wrap’s ends. The orbiting wrap is the fixed one turned by 180°, and it orbits on a circle of radius \(r = P/2 - t\), the gap between the wraps less one thickness.
Where the wraps touch, their flanks are tangent, and a pocket closed by two tangent curves is one region only to rounding. Each wrap is therefore drawn 0.001 thicker, so that they overlap by a hair at the contacts and the pockets close cleanly. The formulas below use the thickness drawn. The wrap’s area is compared with \(t L - t^2 \Delta\varphi / 2\), the area between two parallel curves \(t\) apart, for the length \(L = a\,(\varphi_1^2 - \varphi_0^2)/2\) of the outer involute and the angle \(\Delta\varphi\) through which its tangent turns:
[3]:
a = 1.0
P = 2 * math.pi * a
t = 1.0 + 0.001
r = P / 2 - 1.0
def involute(phi, phase):
c, s = math.cos(phi + phase), math.sin(phi + phase)
return (a * (c + phi * s), a * (s - phi * c))
def wrap(phi0, phi1):
"""the wrap between the involute of phase 0 and the one turned by t / a, from phi0 to phi1"""
g, d0, d1 = t / a, math.degrees(phi0), math.degrees(phi1)
outer = px.Curve((f"{a}*(cos(t) + t*pi/180*sin(t))", f"{a}*(sin(t) - t*pi/180*cos(t))"), (d0, d1))
inner = px.Curve((f"{a}*(cos(t) + (t*pi/180 - {g})*sin(t))", f"{a}*(sin(t) - (t*pi/180 - {g})*cos(t))"),
(d1, d0)) # across the wrap, on the same normal, drawn back
ends = [involute(phi0, 0), involute(phi1, 0), involute(phi1 - g, g), involute(phi0 - g, g)]
return px.Shape([outer, px.Line(ends[1], ends[2]), inner, px.Line(ends[3], ends[0])]).Face()
fixed = wrap(math.pi / 2, 6.5 * math.pi)
orbiting = fixed.Rotated((0, 0), 180)
end = involute(6.5 * math.pi, 0)
housing = px.Circle((0, 0), math.hypot(*end) + 0.5).Face()
phi0, phi1 = math.pi / 2, 6.5 * math.pi
print("wrap area", fixed.mass, "| t L - t² Δφ / 2", t * a * (phi1**2 - phi0**2) / 2 - t**2 * (phi1 - phi0) / 2,
"| orbit radius", r)
wrap area 198.02531717379907 | t L - t² Δφ / 2 198.02531717383053 | orbit radius 2.141592653589793
The pockets
At the crank angle \(\theta\), the orbiting wrap sits at \(r\,(\cos\theta, -\sin\theta)\). The gas is the housing less both wraps, and its faces are the pockets, the suction space at the rim and the discharge space in the middle. The pockets come in pairs of equal area, one on either side, and the two spaces have no partner. At \(\theta = 180^\circ\), there are two pairs:
[4]:
def gas(theta):
moving = orbiting.Translated((r * math.cos(theta), -r * math.sin(theta)))
return housing - fixed - moving, moving
def pairs(region):
"""the pockets' areas, each pair once, smallest first"""
areas = [f.mass for f in region.faces]
return sorted({round(x, 6) for x in areas if sum(abs(x - y) < 1e-6 * x for y in areas) == 2})
region, moving = gas(math.pi)
print(len(region.faces), "faces | pocket pairs", pairs(region))
6 faces | pocket pairs [77.775845, 162.283316]

Compression
As the crank turns, each pocket moves inward and shrinks. The check runs from \(\theta = 30^\circ\) to \(180^\circ\) in steps of 30°, while two pairs are closed. It compares every pocket pair with its area at 30°, less \(P\,(P - 2t)/2\) per radian for each pocket, and the step from the inner pocket to the outer one with \(\pi P (P - 2t)\):
[6]:
start = pairs(gas(math.radians(30))[0])
for degrees in (30, 60, 90, 120, 150, 180):
now = pairs(gas(math.radians(degrees))[0])
turned = math.radians(degrees - 30)
print(degrees, "degrees | pockets", now, "| formula",
[round(x - P * (P - 2 * t) / 2 * turned, 6) for x in start], "| step", round(now[1] - now[0], 6))
print("pi P (P - 2t)", round(math.pi * P * (P - 2 * t), 6))
30 degrees | pockets [112.987293, 197.494761] | formula [112.987293, 197.494761] | step 84.507468
60 degrees | pockets [105.945003, 190.452472] | formula [105.945025, 190.452493] | step 84.507469
90 degrees | pockets [98.902714, 183.410183] | formula [98.902758, 183.410226] | step 84.507469
120 degrees | pockets [91.860425, 176.367894] | formula [91.86049, 176.367958] | step 84.507469
150 degrees | pockets [84.818135, 169.325605] | formula [84.818223, 169.325691] | step 84.50747
180 degrees | pockets [77.775845, 162.283316] | formula [77.775955, 162.283423] | step 84.507471
pi P (P - 2t) 84.507211
The pockets follow the formula to about \(10^{-6}\) of their area, and the step differs from \(\pi P (P - 2t)\) in the sixth digit. Both differences come from the overlap at the contacts. The formulas take the thickness drawn, 1.001, while the orbit radius \(r\) is set for a thickness of 1. The pockets shrink linearly with the crank angle, so a scroll compressor squeezes its gas evenly over the turns it takes to reach the middle. See Many faces at once for Boolean operations on regions like these.