--- jupyter: description: 'A scroll compressor with planex2d: two involute wraps, one fixed and one orbiting, the crescent pockets between them found by Boolean operations, in pairs of equal area that shrink at P(P - 2t)/2 per radian and step by pi P (P - 2t) from pocket to pocket.' --- # 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. ![The two wraps of a scroll compressor and the gas pockets between them](../_static/gallery/scroll-compressor.png){.result} ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import GUIDE, LIGHT, PLANEX, SOFT, Row ``` ## 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: ```python 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) ``` ## 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: ```python 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)) ``` ```python nbsphinx="hide-input" faces = sorted(region.faces, key=lambda f: f.mass) paired = [f for f in faces if sum(abs(f.mass - g.mass) < 1e-6 * f.mass for g in faces) == 2] Row((px.Compound([fixed, moving]), "At 180°: the pockets in pairs", {"fills": [(fixed, SOFT), (moving, GUIDE)] + [(px.Face(f), PLANEX if f.mass > paired[0].mass + 1 else LIGHT) for f in paired]}), caption="The fixed wrap, the orbiting one in orange, and two pairs of pockets") ``` ## 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)$: ```python 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)) ``` 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](../core/booleans/many-faces.md) for Boolean operations on regions like these. ```python nbsphinx="hidden" import sys import matplotlib.pyplot as plt sys.path.insert(0, "..") from gallery import save_figure fig, ax = plt.subplots(figsize=(7, 7)) colors = ["#a9d6e5", "#6fb0cf", "#0f7ea8"] region, moving = gas(math.radians(90)) faces = sorted(region.faces, key=lambda f: f.mass) for f in faces: partner = sum(abs(f.mass - g.mass) < 1e-6 * f.mass for g in faces) == 2 rank = sorted({round(g.mass, 6) for g in faces if sum(abs(g.mass - h.mass) < 1e-6 * g.mass for h in faces) == 2}) color = colors[rank.index(round(f.mass, 6)) % 3] if partner else "#f4ecd8" px.Face(f).Plot(ax=ax, face_col=color, line_col=color, legend=False) fixed.Plot(ax=ax, face_col="#5d6d7e", line_col="#22303c", legend=False) moving.Plot(ax=ax, face_col="#d98c2b", line_col="#22303c", legend=False) housing.outer.Plot(ax=ax, line_col="#22303c", legend=False) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "scroll-compressor", dpi=140) plt.close(fig) ```