kapy academy

Objects Made of Code

7 min readUpdated Aug 2026

Some objects are a short list of steps. Others are a rule: a vent grille whose slot count follows the face it lands on, a texture computed across an area, a lattice that thins where a wall is thick. You cannot draw those once and replay them, because the geometry is different every time. Create one with New Smart Object and the Code grip, and write the rule instead.

Like every other kind of authoring, this is a Maker feature. Placing somebody else's code object is free.

The split editor

The Design pane divides into a code editor on the left and a live 3D preview on the right, with a divider you can drag; where you put it is remembered. The feature timeline is gone, because there are no features to list. The code is the model.

A vertical slider sits beside the code and works like the timeline scrubber you already know. Its play-head snaps between top-level statements, and every line below the head is drawn dimmed, so you can walk down your own script and watch the shape appear a step at a time. A for or if block counts as one stop: the whole block or none of it.

The preview recompiles about a third of a second after you stop typing. That cadence is the actual working rhythm of this editor: type, glance right, keep typing.

The file you start with

A new code object is not empty. It arrives runnable, so the preview has something to show the moment the tab opens:

export const meta = {
    id: "my-object",
    name: "My object",
    version: "1.0.0",
    category: "Custom",
} satisfies PartMeta;

export default smartObject(
    ({ region }: { region: Region<{ prompt: "Pick a face to build on" }> }) => {
        const [cx, cy] = region.anchor;
        const radius = 5;
        // …build a ring of points…
        return region.cut([[ring]], { depth: 5 });
    },
);

Two halves, and both matter. meta is the object's identity, including the version string that saving will publish. The default export is the rule: it declares what the consumer must pick, in the destructured signature, and returns geometry built from it.

The starter cuts a 5 mm radius hole, 5 mm deep, into whatever face is picked. Change the numbers, watch the preview, and you have already learned the loop.

The source is TypeScript, and the file extension is .kpycode.

What the consumer picks

The signature is the interface. Declaring region asks for a flat face or a sketch region drawn on one, and the prompt string is the text they are shown while picking. Declaring a body instead asks for a whole solid, and the result replaces that body rather than adding to it.

Switching between the two while you edit is allowed, and the object follows: change the pick from a region to a body and the object is re-filed as a body object on the next save. What you should not do is switch casually on an object other people have already placed, because their placement is pointing at the wrong kind of thing afterwards.

Config parameters go in the same signature, and they reach the placement popover the way any exported parameter does. Choosing the Parameters You Expose applies here unchanged: expose the decisions, compute the consequences.

The sandbox is pure on purpose

Your script runs in a small sandbox in the browser, with a 30 second ceiling and a few hundred megabytes to work in. Three things are switched off inside it: Math.random, Date and performance.

That is the guarantee that makes the object worth publishing, not a limitation to work around. Same inputs, same geometry, on your machine and on a stranger's, this month and next year. A random jitter would mean nobody could ever reproduce a part built with your object.

Know how you will find this out, because both the editor's type checking and the error message send you the wrong way. Calling Math.random() compiles green and then throws at build time with Math.random is disabled: the KapyCode sandbox is pure (same inputs → same solid). Use random(seed) (smart object, Phase 2). Follow that advice and random(seed) throws in its turn, with random(seed) is a smart-object feature (Phase 2); a MODEL is deterministic without it. There is no seeded generator in the sandbox to reach, in a code object or anywhere else.

So derive your jitter from the inputs instead. A hash of the coordinates, or of the loop index, gives you scatter that is stable and that a consumer can reproduce.

Calibration constants are available to scripts, which is the strongest reason to write an object as code. A texture that reads MIN_GAP, or a snap-fit that sizes its gap from FIT_SLIDE, comes out right on the consumer's printer rather than on yours. Material, machine and your own numbers explains what those values represent.

Saving, and what saving does

Two mechanisms, and they behave differently.

Autosave skips a buffer that does not compile. It runs a couple of seconds after you stop typing, and a broken draft is never written over a good one: the save is rescheduled until the code builds again. You can leave a half-finished expression on screen and go to lunch.

Cmd/Ctrl+S, or the Save button, saves and publishes. It writes a local version stamped with the version string in meta. On a broken buffer it refuses, with the message Fix the errors before publishing.

Saving overwrites the version in place

A code object's local versions are stamped from meta.version, and saving with the same string overwrites that version rather than creating a new one. Placements pinned to 1.0.0 in other documents pick up the new geometry.

That is convenient while you are developing and dangerous once anyone else is using the object. The discipline is simple: bump meta.version before you save a change that alters geometry, exactly as you would bump any other released artefact. Patch for a fix nobody has to think about, minor for new parameters that leave old settings working, major for anything that breaks an existing configuration.

The immutability you may have read about applies to store releases. It does not apply here.

Sharing a code object

A code object cannot be published to the public store today. The store accepts documents, and a .kpycode script is not one.

What you can do is send the file. The Export button in a code object reads Export .kpycode, and dropping the file into somebody else's file manager imports it. Re-importing a file with the same name overwrites that object's draft, which makes the file a workable update channel inside a team and a poor one with strangers.

There is a condition on the far end. Importing a smart object needs the Maker plan. A colleague on the free plan who drops your .kpycode gets the upgrade panel, not an object, so the file only reaches people who already have the plan.

When it fails

Preview unavailable, with The code doesn't compile (line {N}). Fix it to resume the live preview. The last good preview is frozen behind the overlay, so what you are looking at is not what the current buffer would build. Go to the line named.

Saving refuses with Fix the errors before publishing. Same cause. Autosave has been quietly skipping too, so the draft on the server is older than you think.

The preview is empty but the code compiles. The script ran and returned nothing that intersects the input. Drag the timeline slider down statement by statement and find the first one that produces nothing.

The script times out. Thirty seconds is a lot of geometry. A loop that scales with the area of the face will find the ceiling on a large part even though it was instant on the reference cube, so test on a big face before you ship.

A placement built from an older save looks different now. You saved over the same meta.version. Bump it.

See also

Discord