Proof: SVG Arc to GCode G2 and G3

The SVG arc command consists of

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

For the case that \(r_x\neq r_y\), we can only sample from the path and interpolate it linearly. But for the case that \(r_x=r_y\), it’s possible to improve the gcode by using G2 and G3. In general, the syntax of G2 and G3 are

G2 I<offset> J<offset> R<radius> [X<pos>] [Y<pos>]
G3 I<offset> J<offset> R<radius> [X<pos>] [Y<pos>]

The combination (I, J) or R are exclusive. As an example, an arc can be drawn like this with (I, J) offset or via radius R:

Arc from (9, 6) to (2, 7) J=-3 R=5 I=-4 (9, 6) (2, 7) (5,3)


In this derivation, the use of the R parameter is ignored, and the gcode is fed with the (I, J) tuple.

C M S E v r

Let’s say \(\mathbf{S}\) is the absolute starting point of the SVG arc path and \(\mathbf{E}\) the absolute ending point of the SVG arc path. The points are connected with the vector \(\mathbf{a}=\mathbf{E}-\mathbf{S}\).

The point \(\mathbf{M} = \frac{1}{2}(\mathbf{S}+\mathbf{E})\) is the mid-point between start and end. On this point, we can construct an orthogonal vector \(\mathbf{v}\) to our desired center \(\mathbf{C}\). The length \(|\mathbf{v}|\) can be determined using Pythagorean theorem:

\[|\mathbf{v}| = \sqrt{r^2 - \frac{1}{4}|\mathbf{a}|^2}\]

Now the vector \(\mathbf{v}\) can be found with the perp operator:

\[\mathbf{v} = \mathbf{\hat{a}}^\perp |\mathbf{v}|\]

Finally, the center (I, J) can be found with

\[\begin{array}{rl} (I, J) =& \mathbf{M} \pm \mathbf{v} - \mathbf{S}\\ =& \mathbf{M} \pm \mathbf{\hat{a}}^\perp |\mathbf{v}| - \mathbf{S}\\ =& \frac{1}{2}(\mathbf{S}+\mathbf{E}) - \mathbf{S} \pm \frac{\mathbf{{a}}^\perp}{|\mathbf{a}|} \sqrt{r^2 - \frac{1}{4}|\mathbf{a}|^2}\\ =& \frac{1}{2}(\mathbf{E} - \mathbf{S}) \pm \mathbf{{a}}^\perp \sqrt{\frac{r^2}{|\mathbf{a}|^2} - \frac{1}{4}}\\ =& \frac{1}{2}\left(\mathbf{a} \pm \mathbf{{a}}^\perp \sqrt{\frac{4r^2}{\mathbf{a}\cdot\mathbf{a}} - 1}\right)\\ \end{array}\]

For which the positive part of the equation is used when the sweep-flag and large-arc-flag differ \(f_S\neq f_A\) and the negative part is used for \(f_S=f_A\).

The remaining question is to decide if we need to draw the circle clockwise or counter-clockwise, which is determined by the direction the circle is drawn - or simply if the sweep-flag \(f_S=1\).


func arcToGCode(S, E, r, fS, fA) {

    a = {
      x: E.x - S.x,
      y: E.y - S.y
    aP = fS != fA ? {
      x: -a.y,
      y: a.x
    } : {
      x: a.y,
      y: -a.x
    w = sqrt(4 * r * r / (a.x * a.x + a.y * a.y) - 1)
    I = (a.x + aP.x * w) / 2
    J = (a.y + aP.y * w) / 2

    if (fS != 1)
      return "G2 X" + E.x + " Y" + E.y + " I" + I + " J" + J
      return "G3 X" + E.x + " Y" + E.y + " I" + I + " J" + J