Contents
raw Math

SVG and G-code describe the same circular arc in different ways. An SVG path stores two radii, two selection flags, and an endpoint; the center is implicit. A G2 or G3 move stores the endpoint and usually the center offset from the current position. Converting between them therefore means reconstructing the correct one of two possible circle centers and preserving the arc direction across the coordinate-system transformation.

The conversion is exact only when the transformed SVG segment is circular. A genuine ellipse cannot be represented by a single G2 or G3 move and must be approximated by circular arcs or line segments.

SVG and G-Code Arc Parameters

An absolute SVG elliptical-arc command has the form

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

Lowercase a uses an endpoint relative to the current SVG point. Convert it to an absolute endpoint before applying this method. All element and ancestor transforms must also be resolved first. A non-uniform transform can turn an SVG circle into an ellipse, in which case one G2/G3 move is no longer exact.

For a circular move in the G17 XY plane, center format is commonly written as

G2 X... Y... I... J...
G3 X... Y... I... J...

G2 moves clockwise and G3 counterclockwise as viewed from the positive Z-axis. Under the usual incremental arc-center mode G91.1, \(I\) and \(J\) are the X and Y offsets from the arc's start point to its center. They are not an endpoint, and they are not used together with radius-format R on the same move.

In unchanged coordinates, the center offset is \((I,J)=C-S=(-4,-3)\). Mapping SVG screen coordinates to a Y-up machine frame changes the signs of all Y coordinates and gives \(J=3\).

When a Single G2/G3 Move Is Exact

Resolve the complete SVG transform into the target machine coordinate system before testing the radii. The resulting segment can use one circular interpolation command only if its two transformed radii agree within a chosen numerical tolerance. For an untransformed SVG command this reduces to \(r_x=r_y=r\); the axis rotation then has no geometric effect because rotating a circle leaves it unchanged.

If \(r_x\ne r_y\), or a non-uniform transform makes the radii unequal, G2/G3 cannot reproduce the ellipse exactly. Subdivide it according to a documented chord-error tolerance instead. Silently replacing the ellipse by a circle changes the toolpath.

Deriving the Circle Center

Let \(S=(S_x,S_y)\) be the absolute start point, \(E=(E_x,E_y)\) the absolute endpoint, and

\[ \mathbf a=E-S,\qquad d=\lVert\mathbf a\rVert. \]

The midpoint of the chord is

\[ M=\frac{S+E}{2}. \]

Both candidate centers lie on the perpendicular bisector of the chord. With the perpendicular operator \(\mathbf a^\perp=(-a_y,a_x)\), the unit normal is \(\mathbf a^\perp/d\). The center, the midpoint, and either endpoint form a right triangle whose hypotenuse is \(r\) and whose half-chord is \(d/2\). The distance from the midpoint to either center is therefore

\[ h=\sqrt{r^2-\frac{d^2}{4}}. \]

The two center candidates are \(M\pm h\mathbf a^\perp/d\).

The SVG flags select the sign:

\[ \sigma= \begin{cases} +1,&f_A\ne f_S,\\ -1,&f_A=f_S. \end{cases} \]

Hence

\[ C=M+\sigma\frac{\mathbf a^\perp}{d} \sqrt{r^2-\frac{d^2}{4}}. \]

In incremental center mode, G-code needs \(C-S\), not the absolute center. Substituting \(M-S=\mathbf a/2\) gives the compact offset formula

\[ \boxed{ \begin{pmatrix}I\\J\end{pmatrix} =\frac{1}{2}\left( \mathbf a+\sigma\mathbf a^\perp \sqrt{\frac{4r^2}{\mathbf a\cdot\mathbf a}-1} \right)}. \]

SVG Radius Correction

A real center exists only when \(d\le2r\). SVG does not reject a smaller declared radius. For a circle it scales the radius to the smallest value that reaches both endpoints:

\[ r' = \max\left(|r|,\frac{d}{2}\right). \]

Use \(r'\) in the center formula. At \(r'=d/2\), the height \(h\) is zero and both center candidates coincide at the chord midpoint, producing a semicircle. Clamping the square-root argument to zero protects this boundary from a tiny negative value caused by floating-point rounding.

Choosing G2 or G3 Correctly

The SVG sweep flag describes increasing or decreasing parameter angle in the current SVG coordinate system. G2 and G3 describe clockwise or counterclockwise motion in the selected machine plane. These are equivalent only after the complete coordinate map is known.

For the common mapping from SVG screen coordinates to a Cartesian CNC plane,

\[ X=o_x+s x,\qquad Y=o_y-s y, \]

the negative sign flips orientation. Therefore \(f_S=1\) becomes clockwise G2, while \(f_S=0\) becomes counterclockwise G3. If Y is not flipped, the mapping is reversed: \(f_S=1\) becomes G3.

A general implementation should not hard-code this assumption. For the linear part \(A\) of the 2D SVG-to-machine transform, orientation is reversed exactly when \(\det(A)<0\). Uniform scaling, rotation, and translation preserve orientation; a reflection reverses it.

Worked Example

Consider

M 9 6 A 5 5 0 0 1 2 7

with \(S=(9,6)\), \(E=(2,7)\), \(r=5\), \(f_A=0\), and \(f_S=1\). Then

\[ \mathbf a=(-7,1),\qquad \mathbf a^\perp=(-1,-7),\qquad \sigma=1. \]

The center calculation gives

\[ C=(5,3),\qquad C-S=(-4,-3). \]

Without changing coordinate handedness, the move is counterclockwise:

G17 G90 G91.1
G3 X2 Y7 I-4 J-3

With the usual map \((X,Y)=(x,-y)\), the transformed points and offset become

\[ S'=(9,-6),\qquad E'=(2,-7),\qquad (I,J)=(-4,3), \]

and the orientation reversal changes the command to

G17 G90 G91.1
G2 X2 Y-7 I-4 J3

Compact JavaScript Implementation

The implementation below exposes the geometric core without validation or SVG parsing. It assumes a resolved, non-degenerate circular arc, applies SVG radius correction, optionally reflects Y, and emits incremental I/J offsets.

function svgArcToGcode(start, end, radius, largeArc, sweep, flipY = true) {
  const a = { x: end.x - start.x, y: end.y - start.y };
  const d = Math.hypot(a.x, a.y);
  const r = Math.max(Math.abs(radius), d / 2);
  const midpoint = {
    x: (start.x + end.x) / 2,
    y: (start.y + end.y) / 2
  };
  const h = Math.sqrt(r * r - d * d / 4);
  const sign = largeArc === sweep ? -1 : 1;
  const center = {
    x: midpoint.x - sign * a.y * h / d,
    y: midpoint.y + sign * a.x * h / d
  };
  const map = point => ({
    x: point.x,
    y: flipY ? -point.y : point.y
  });
  const S = map(start);
  const E = map(end);
  const C = map(center);
  const I = C.x - S.x;
  const J = C.y - S.y;
  const command = (flipY ? sweep : !sweep) ? 'G2' : 'G3';
  const format = value => String(Number(value.toFixed(4)));

  return `${command} X${format(E.x)} Y${format(E.y)}`
    + ` I${format(I)} J${format(J)}`;
}

svgArcToGcode({ x: 9, y: 6 }, { x: 2, y: 7 }, 5, 0, 1);
// G2 X2 Y-7 I-4 J3

Larger path converters can express the same construction with Vector2.js and use Circle.js for related circle geometry. The elementary form above keeps every operation visible and maps directly to the derivation.

Degenerate Cases and Controller Checks