diff --git a/packages/abstract-3d/src/renderers/index.ts b/packages/abstract-3d/src/renderers/index.ts
index 80f6c0d..d53525b 100644
--- a/packages/abstract-3d/src/renderers/index.ts
+++ b/packages/abstract-3d/src/renderers/index.ts
@@ -2,3 +2,4 @@ export * from "./dxf";
export * from "./svg";
export * from "./stl";
export * from "./react";
+export * from "./step";
diff --git a/packages/abstract-3d/src/renderers/step/index.ts b/packages/abstract-3d/src/renderers/step/index.ts
new file mode 100644
index 0000000..84f1615
--- /dev/null
+++ b/packages/abstract-3d/src/renderers/step/index.ts
@@ -0,0 +1 @@
+export * from "./step";
diff --git a/packages/abstract-3d/src/renderers/step/step-encoding.ts b/packages/abstract-3d/src/renderers/step/step-encoding.ts
new file mode 100644
index 0000000..50ff76c
--- /dev/null
+++ b/packages/abstract-3d/src/renderers/step/step-encoding.ts
@@ -0,0 +1,29 @@
+import { Vec3 } from "../../abstract-3d";
+
+export const CARTESIAN_POINT = (v: Vec3, cartRef: number): string =>
+ `#${cartRef} = CARTESIAN_POINT('', (${v.x}, ${v.y}, ${v.z}));`;
+
+export const POLY_LOOP = (
+ cartRef1: number,
+ cartRef2: number,
+ cartRef3: number,
+ cartRef4: number,
+ polyRef: number
+): string => `#${polyRef} = POLY_LOOP('', (#${cartRef1}, #${cartRef2}, #${cartRef3}, #${cartRef4}));`;
+
+export const ADVANCED_FACE = (polyRef: number, advRef: number): string =>
+ `#${advRef} = ADVANCED_FACE('', (#${polyRef}), .T.);`;
+
+export const CLOSED_SHELL = (
+ advRef1: number,
+ advRef2: number,
+ advRef3: number,
+ advRef4: number,
+ advRef5: number,
+ advRef6: number,
+ closedRef: number
+): string =>
+ `#${closedRef} = CLOSED_SHELL('', (#${advRef1}, #${advRef2}, #${advRef3}, #${advRef4}, #${advRef5}, #${advRef6}));`;
+
+export const MANIFOLD_SOLID_BREP = (closedRef: number, maniRef: number): string =>
+ `#${maniRef} = MANIFOLD_SOLID_BREP('', #${closedRef});`;
diff --git a/packages/abstract-3d/src/renderers/step/step-geometries/step-box.ts b/packages/abstract-3d/src/renderers/step/step-geometries/step-box.ts
new file mode 100644
index 0000000..e74654f
--- /dev/null
+++ b/packages/abstract-3d/src/renderers/step/step-geometries/step-box.ts
@@ -0,0 +1,50 @@
+import * as A3D from "../../../abstract-3d";
+import { ADVANCED_FACE, CARTESIAN_POINT, CLOSED_SHELL, MANIFOLD_SOLID_BREP, POLY_LOOP } from "../step-encoding";
+
+export function stepBox(
+ b: A3D.Box,
+ _m: A3D.Material,
+ parentPos: A3D.Vec3,
+ parentRot: A3D.Vec3,
+ refIdx: number
+): readonly [string, number] {
+ const half = A3D.vec3Scale(b.size, 0.5);
+ const pos = A3D.vec3TransRot(b.pos, parentPos, parentRot);
+ const rot = A3D.vec3RotCombine(parentRot, b.rot ?? A3D.vec3Zero);
+ const vec3tr = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot);
+
+ const v1 = vec3tr(-half.x, -half.y, -half.z);
+ const v2 = vec3tr(half.x, -half.y, -half.z);
+ const v3 = vec3tr(half.x, half.y, -half.z);
+ const v4 = vec3tr(-half.x, half.y, -half.z);
+ const v5 = vec3tr(-half.x, -half.y, half.z);
+ const v6 = vec3tr(half.x, -half.y, half.z);
+ const v7 = vec3tr(half.x, half.y, half.z);
+ const v8 = vec3tr(-half.x, half.y, half.z);
+
+ const step = `
+${CARTESIAN_POINT(v1, refIdx + 1)}
+${CARTESIAN_POINT(v2, refIdx + 2)}
+${CARTESIAN_POINT(v3, refIdx + 3)}
+${CARTESIAN_POINT(v4, refIdx + 4)}
+${CARTESIAN_POINT(v5, refIdx + 5)}
+${CARTESIAN_POINT(v6, refIdx + 6)}
+${CARTESIAN_POINT(v7, refIdx + 7)}
+${CARTESIAN_POINT(v8, refIdx + 8)}
+${POLY_LOOP(refIdx + 1, refIdx + 2, refIdx + 3, refIdx + 4, refIdx + 9)}
+${POLY_LOOP(refIdx + 5, refIdx + 6, refIdx + 7, refIdx + 8, refIdx + 10)}
+${POLY_LOOP(refIdx + 1, refIdx + 5, refIdx + 8, refIdx + 4, refIdx + 11)}
+${POLY_LOOP(refIdx + 2, refIdx + 6, refIdx + 7, refIdx + 3, refIdx + 12)}
+${POLY_LOOP(refIdx + 1, refIdx + 2, refIdx + 6, refIdx + 5, refIdx + 13)}
+${POLY_LOOP(refIdx + 4, refIdx + 3, refIdx + 7, refIdx + 8, refIdx + 14)}
+${ADVANCED_FACE(refIdx + 9, refIdx + 15)}
+${ADVANCED_FACE(refIdx + 10, refIdx + 16)}
+${ADVANCED_FACE(refIdx + 11, refIdx + 17)}
+${ADVANCED_FACE(refIdx + 12, refIdx + 18)}
+${ADVANCED_FACE(refIdx + 13, refIdx + 19)}
+${ADVANCED_FACE(refIdx + 14, refIdx + 20)}
+${CLOSED_SHELL(refIdx + 15, refIdx + 16, refIdx + 17, refIdx + 18, refIdx + 19, refIdx + 20, refIdx + 21)}
+${MANIFOLD_SOLID_BREP(refIdx + 21, refIdx + 22)}`;
+
+ return [step, 22];
+}
diff --git a/packages/abstract-3d/src/renderers/step/step.ts b/packages/abstract-3d/src/renderers/step/step.ts
new file mode 100644
index 0000000..3790b66
--- /dev/null
+++ b/packages/abstract-3d/src/renderers/step/step.ts
@@ -0,0 +1,55 @@
+import * as A3D from "../../abstract-3d";
+import { stepBox } from "./step-geometries/step-box";
+
+export const toStep = (scene: A3D.Scene): string => {
+ let step = "";
+ let nbrRefs = 0;
+
+ for (const g of scene.groups ?? []) {
+ const [newStep, newNbrRefs] = stepGroup(
+ g,
+ scene.center_deprecated ?? A3D.vec3Zero,
+ scene.rotation_deprecated ?? A3D.vec3Zero,
+ nbrRefs
+ );
+ step += newStep;
+ nbrRefs += newNbrRefs;
+ }
+
+ return `ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION(('Aircalc'), '1');
+FILE_NAME('aircalc.stp', '2024-09-09T12:00:00', (''), (''), 'Author', '', '');
+FILE_SCHEMA(('AP214'));
+ENDSEC;
+DATA;${step}
+ENDSEC;
+END-ISO-10303-21;`;
+};
+
+function stepGroup(g: A3D.Group, parentPos: A3D.Vec3, parentRot: A3D.Vec3, refIdx: number): [string, number] {
+ let step = "";
+ let nbrRefs = 0;
+ const pos = A3D.vec3TransRot(g.pos, parentPos, parentRot);
+ const rot = A3D.vec3RotCombine(parentRot, g.rot ?? A3D.vec3Zero);
+ for (const m of g.meshes ?? []) {
+ switch (m.geometry.type) {
+ case "Box": {
+ const [newStep, newNbrRefs] = stepBox(m.geometry, m.material, pos, rot, refIdx + nbrRefs);
+ step += newStep;
+ nbrRefs += newNbrRefs;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ for (const c of g.groups ?? []) {
+ const [newStep, newNbrRefs] = stepGroup(c, pos, rot, refIdx + nbrRefs);
+ step += newStep;
+ nbrRefs += newNbrRefs;
+ }
+
+ return [step, nbrRefs];
+}
diff --git a/packages/abstract-visuals-example/src/app/abstract-3d-example.tsx b/packages/abstract-visuals-example/src/app/abstract-3d-example.tsx
index 9529438..a6a221e 100644
--- a/packages/abstract-visuals-example/src/app/abstract-3d-example.tsx
+++ b/packages/abstract-visuals-example/src/app/abstract-3d-example.tsx
@@ -14,6 +14,9 @@ export function Abstract3DExample(): React.ReactNode {
+