GearScript Documentationfor Studio 1.0 Open the studio
Documentation

Describe how parts relate. GearScript works out the motion.

GearScript is a small, CSS-like language for mechanism animations. You write three short files, and the studio solves the joints, gears and cameras, renders every frame and exports a GIF.

At a glance

A script for machines: describe the parts and how they connect, drive one of them, and the rest follow.

The whole language follows three rules:

  1. A part declares what it connects to, inside its own block: attach, hinge, slider, mesh, couple, belt, pin.
  2. Units decide what a value means. 90deg is an angle, 90deg/s a spin rate, 90deg/s2 an angular acceleration. The same goes for mm, mm/s and mm/s2.
  3. Values come first, then keywords: rotate: 60deg/s about G1.axis; · orbit: Sun 30deg/s; · follow: Car offset (…);
// .scene: what exists let wheel = 55mm; Car::Group { body: Box { size: (440mm, 50mm, 180mm); } axle: Rod { length: 230mm; orient: 90deg about x; at: body.face.bottom; } } FL::Wheel { radius: wheel; hinge: axis on Car.axle.end.b; } Observer { position: (380mm, 330mm, 1150mm); }
// .style: how it looks Car { color: #2f6f8f; outline: 1px #13303d; } Wheel { color: gradient(#141619, #b9c0c8, axis: x); label: "front left"; }
// .motion: how it moves Car { 0s..2s: move: (0, 0, 0) -> (600mm/s, 0, 0) ease-in; } // speed up: a change of speed FL { forever: roll: Road; } // spin follows the distance travelled Observer { forever: follow: Car offset (380mm, 260mm, 1150mm); }

Getting started

A project is three files. Each one has a single job, like HTML, CSS and animations on the web.

FileAnswersContains
.sceneWhat exists?Parts, groups, anchors, connections, the camera and lights
.styleHow does it look?Selectors with colour, material, outline, opacity and labels
.motionHow does it move?Time blocks that drive parts, joints and the camera

Three files or one file

The same project can also be one file, with a line @scene, @style or @motion starting each section. The studio starts in One file, because the combined file is easiest to read top to bottom and to hand to an AI assistant for review or edits. Switch with the One file / Three files buttons in the header (or in Settings); the choice is remembered, and a project opened from a .zip follows it. One file is one .gscript file; Three files keeps three tabs and saves them as a .zip. Both views hold exactly the same project.

@gearscript 1 @scene Crank::Link { length: 40mm; hinge: end.a on (0, 0, 0); } @style Crank { color: #b8442e; } @motion Crank { forever: rotate: 180deg/s; }
  • The first line, @gearscript 1, says which version of the language the project is written in. It is optional, and the studio writes it for you. Language 1 is stable: a project that works today keeps working in every 1.x release, and error codes keep their meaning.
  • Problems show the line in the single file and which section it is in; clicking one jumps there. Ctrl 1 / 2 / 3 jump to a section.
  • Pasting a single-file project into the three-file view fills all three tabs, so an assistant's one-block reply drops straight in.
  • In the single-file view, Save project writes project.gscript; Open reads .gscript files in either view.
  • Each marker sits alone on its line and each section appears once (E0105–E0107 explain any mix-ups; parts written before any marker, or straight under @common or a chapter line, are read as the scene with W0148, and Fix adds the missing @scene line). You cannot switch back to three files until the markers are fixed, so nothing is lost.

The studio

  • Left: the three file tabs and the editor. The preview rebuilds a moment after you stop typing.
  • Problems: under the editor. Click a problem to jump to its line. A problem the studio knows how to correct has a Fix button with a spanner. Some fixes are sure: what the studio reads anyway, such as a # comment, colour for color, shadow: no or a property that is ignored. Others are guesses, marked with a dashed outline and Did you mean …? in the message: the one known name close to a misspelt one (Gaer → Gear, a part, style, sound or voice name), or a value back in its range (opacity: 40 → 40%). With the site's AI on, the other problems have Fix with AI.
  • The Fix button at the top of Problems does the most useful thing first: Fix all applies every sure fix; when none are left it becomes Apply suggestions; with only errors left and AI on the site, Fix with AI. Its menu has all of these, and Fix as much as possible, which repeats sure fixes then suggestions until nothing more changes (the compiler finds one error at a time, so this clears several in one go). It never asks the AI by itself. Each fix shows what it changed, with Undo and Show changes. Ctrl . presses the Fix button. Copy as JSON gives the list to an AI agent.
  • A long list: the chips at the top (errors, warnings, fixable) show only those; click again for all. The same problem in several places is one row with ×n: click it to see the places, and its Fix fixes them all. Every row has its action in the same place: Fix (a ? badge marks a guess, whose word is in bold in the message) or Ask AI.
  • Copy as JSON copies the problems for an AI that already has your code. Once you have changed the code since your last paste (by typing or with Fix), the button becomes Copy with code: a message ready to send, with what to fix, the problems and your code, in the format the AI should answer in. Both are in its menu.
  • Right: the live preview. Observer shows your camera; Free lets you drag to look around. Hinge axes and gear lines can be switched on in Settings.
  • Examples opens every example by category, with a picture of each. Clicking one opens it here; if your project has unsaved changes the studio first asks whether to save them, discard them or open the example in a new tab instead. Ctrl-click (or middle-click) always opens a new tab. New project works the same way.
  • Copy state (the button at the right end of the timeline, or right-click the preview) copies, as text, where every part is at the current moment: its position, rotation, bounding box and joint, plus the camera and any problems. Paste it into an AI chat with what looks wrong, for example "the left foot goes through the floor at 2.4 s". The Prompt AI dialog can include it too: tick Include the state.
  • Header: on the left, the project and the editor: File, Examples, Insert and the One file / Three files switch; on the right, the build status, then Prompt AI, Save and Export, in the order you use them. Everything else (open, 3D models, this guide, settings, the tours, and cookie preferences where the site collects statistics) is under More (the ⋯ button), which also shows the studio's version. The ? button beside it opens the tours.
  • Click any part in the preview to jump to its code.
  • Timeline: play, pause (or press Space) and drag to scrub.
  • Resize anything: drag the divider between code and preview, or above the Problems list. Double-click a divider to reset it. The expand buttons give the code or the preview the whole window; Esc brings the layout back.
  • Tours: the ? button at the top (or More › Take the tour, or Settings › Layout › Choose a tour) offers two. The interactive tour (recommended, about 4 minutes) has you build a crane on a scratch project: write a prompt for the AI, paste the code a simulated assistant sends back, fix an error with Copy as JSON, find a wrong motion with the timeline and Copy state, then export. Its AI replies are written in advance; when you work, use your own assistant. Your own project, settings and undo history are put back when it ends, and Esc leaves it at any step. The quick tour (seven stops) spotlights where things are: Previous, Next and Skip tour, or ← → and Esc. The choice appears once by itself on a first visit.
  • Type hints: as you type, a list suggests what fits where the caret is: shape types after Name::, the properties that shape takes (required ones marked), anchors after Part. (lines and points labelled), on / to / about keywords, gears to mesh with, units after a number, materials, colours, easings, time blocks and camera properties. The bottom line shows how the property is written. ↑ ↓ to choose, Enter or Tab to insert, Esc to close, Ctrl Space to open them any time. Settings › Type hints turns them to manual or off.
  • Insert a shape: the cube button above the code, or Browse shapes… at the top of the type hints after Name::, opens every built-in shape by category with a picture. Search, pick one and press Insert (or double-click it): it arrives with sensible starting values to edit.
  • Colours: a small swatch sits beside every colour in the code; click it to choose a new one with your system's colour picker. While typing a colour, the hints show Pick a colour…, then the colours this project already uses (with their RGB values) and the named finishes.
  • The code editor has find and replace (Ctrl F, with match case, regular expressions and replace all), multiple cursors (Ctrl-click to add one, Ctrl D to select the next match too, Alt-drag for a column), folding (the arrows beside the line numbers fold a block or a whole @section), bracket matching, and undo and redo that remember each tab separately. It is CodeMirror, bundled with the studio; if it cannot load, a plain editor takes its place.
  • Checkpoint: the flag button beside Beautify clears the undo history of the whole project, every file and chapter, so pressing Ctrl Z again and again stops there. It asks first; Shift-click skips the question. Set one when a version works before you try something bold.
  • Beautify: the lines-and-sparkle button above the code (or Shift Alt F) lays the code out like CSS: one property per line, even indents, a blank line between rules, comments kept where they were. A motion block with one property stays on one line; with several, its time label gets a line of its own. Only spacing changes, so the animation is exactly the same, and Ctrl Z undoes it. In the Three files view it tidies the open file; in the One file view, all of it.
  • Right-click the code (or press the Menu key or Shift F10 in it) for undo and redo, cut, copy, paste, select all, beautify, comment lines (Ctrl /), find and replace, run now, insert a shape or a sound, the state of the part under the caret, copy the whole project, the Prompt AI dialog and expand.
  • Settings (More › Settings, or Ctrl ,): theme, accent colour, syntax colours, interface size, editor font, size, spacing, ligatures and indent, line numbers, bracket closing, type hints, when the preview updates, playback speed and looping, joint axes and gear lines, the floor grid, labels, trails, render sharpness, background, where the code panel sits, and export defaults. Settings are kept in this browser.
  • Themes: Graphite (the default, dark, with an orange accent; Settings › Accent colour changes it), Studio light and Ember (warm red) are GearScript's own. Monokai (the classic palette by Wimer Hazenberg, as shipped in VS Code), Dracula (Zeno Rocha) and Solarized Light (Ethan Schoonover) follow well-known editor colour schemes, published under the MIT licence; their names belong to their authors and are used here only to say which scheme a theme follows.
  • Built with three.js (3D), CodeMirror (the editor) and mp4-muxer and webm-muxer by Vanilagy (video files), all under the MIT licence.
  • Open source: GearScript itself is under the Apache License 2.0, so you can read, change and share the code, including for commercial work. The animations and projects you make are yours. The name and logo are not covered by the licence; see TRADEMARKS.md in the source.
ShortcutDoes
Ctrl SpaceShow type hints for where the caret is
Ctrl EnterRun the preview now (the only way when Settings › Update the preview is set to manual)
SpacePlay or pause, when the editor is not focused
Ctrl 1 / 2 / 3Switch to .scene, .style or .motion
Ctrl SSave the project
Ctrl Shift EExpand the preview, or restore it
Shift Alt FBeautify the code
Ctrl /Comment or uncomment the selected lines
EscClose a panel, or leave the expanded view

Only the parts that drive a mechanism need motion rules. Anything attached, hinged, meshed, coupled or belted to a driver follows by itself.

Your first animation: a swinging sign

Four steps build a shop sign that swings in the wind. Each step adds a few lines.

  1. Make a post and an arm. The arm's left face is aligned to a point near the top of the post's right face, so you never compute its centre.

    Post::Box { size: (40mm, 600mm, 40mm); at: (0, 300mm, 0); } Arm::Box { size: (300mm, 30mm, 30mm); align: face.left; at: Post.face.right + (0, 270mm, 0); }
  2. Hang the sign on a hinge. hinge: edge.top on Arm.edge.bottom; snaps the sign's top edge to the arm's bottom edge and lets it turn about that line.

    Sign::Box { size: (220mm, 140mm, 10mm); hinge: edge.top on Arm.edge.bottom; } Observer { position: (700mm, 520mm, 900mm); look-at: (120mm, 460mm, 0); } Light { kind: directional; direction: (-0.6, -1, -0.8); power: 0.85; } Scene { duration: 2s; }
  3. Give it a look. Styles use selectors: a type, a name, a .class or an #id.

    Post, Arm { color: #3b4148; material: metal; } Sign { color: #e0a63a; outline: 1px #5a3f0c; label: "Open"; }
  4. Make it swing. On a hinged part, rotate turns it about its hinge. Here the angle is a function of time t.

    Sign { forever: rotate: 20deg * sin(t * 3.1416); }

Ten minutes: from one crank to a finished video

Five steps take a four-bar linkage from one moving part to a lit, ticking animation you can export or send as a link. Each step only adds lines.

  1. Drive one part. A hinge at the origin and one motion rule. Nothing else moves by itself in GearScript: parts move because they are driven or connected.

    let crank = 40mm; Crank::Link { length: crank; width: 14mm; hinge: end.a on (0, 0, 0); }
    Crank { forever: rotate: 180deg/s; }
  2. Close a loop. The coupler hangs on the crank, the rocker on a fixed point, and a pin joins their free ends. You never say how the coupler or rocker move: the solver keeps the pin together at every instant.

    Coupler::Link { length: 120mm; width: 14mm; hinge: end.a on Crank.end.b + (0, 0, 6mm); anchor tip: (10mm, 46mm, 0); pin: end.b to Rocker.end.b; } Rocker::Link { length: 90mm; width: 14mm; orient: 90deg about z; hinge: end.a on (110mm, 0, 0); }
  3. Trace a curve. The coupler names a point of its own, tip. Attach a small sphere there and give it a trail: it draws the coupler curve, the shape engineers use to design walking machines.

    Pen::Sphere { radius: 5mm; attach: Coupler.tip; }
    Pen { color: #ffd27a; trail: #ffb84d; }
  4. Light and sound. luminosity makes the pen glow and light what is near it; a generated click marks each turn of the crank (one turn takes 2 s at 180°/s).

    Click::Sound { kind: click; } Scene { duration: 4s; background: #0f1216; }
    Pen { color: #ffd27a; luminosity: 80%; trail: #ffb84d; }
    Crank { forever: rotate: 180deg/s; sound: Click every 2s; }
  5. Tidy, check and share. Press Shift Alt F to beautify the code. The Problems list should say "No problems". Then Export › MP4 for a video with sound, or Save › GearScript file to send the project itself: whoever opens the file gets the whole project, every chapter included.

Values and units

Units decide what a value means

QuantityPosition or angleSpeedAcceleration
Lengthmm cm mmm/s m/smm/s2 m/s2
Angledeg rad turndeg/s turn/sdeg/s2

Dividing by a time also makes a speed: 360deg / 12s is a spin rate. Other units: s and ms for time, % for fractions. A bare number is millimetres for lengths and seconds for times; write units everywhere to be clear.

Kinds of value

KindExampleNotes
Number40mm, 90deg, 0.8No space between number and unit
Vector(0, 1500mm, 0)x right, y up, z towards the viewer
AnchorDoor.panel.edge.leftA point or line on a part; see Anchors
Point mathschassis.face.top + (0, 10mm, 0)An anchor plus or minus a vector
Range0deg..110degHinge and slider limits, time blocks
Change0deg -> 90deg ease-in-outGoes from one value to the other across its time block
Colour#e0a63a, steel, peruHex, CSS names, or GearScript's named finishes
Text"500 mm/s"Labels

Expressions

Arithmetic (+ - * /) and these functions work anywhere a number does: sin cos tan sqrt abs min max pow floor exp atan2 length. PI is π.

// A point on a circle of radius 140mm, 250 degrees round Earth::Sphere { radius: 10mm; at: (140mm * cos(250deg), 0, 140mm * sin(250deg)); }

let: named values

let names a value once and works in all three files. Names start with a lower-case letter, because capitals are reserved for parts. A let can hold a number, a vector or a whole expression, even one that uses a curve's u.

let cruise = (600mm/s, 0, 0); let year = 12s; Car { 0s..2s: move: (0, 0, 0) -> cruise; } Earth { forever: orbit: Sun 360deg / year; }

Reserved names: t i u v x y z axis to on at about step offset.

Comments

// to the end of the line and /* blocks */, as in CSS. # is never a comment: a # followed directly by letters or digits is a colour (#38bdf8) or an id (#G2), and a # followed by a space is error E0104.

Parts and groups

Every part is declared as Name::Type, with optional classes, and a block of properties. Names start with a capital letter.

G1::Gear.gear.steel { teeth: 36; module: 2mm; width: 10mm; at: (0, 0, 0); }

Placement properties

PropertyExampleMeaning
atat: Frame.face.top + (0, 10mm, 0);Where the part's centre goes
alignalign: face.bottom;Put this anchor of the part on the at point instead of its centre
orientorient: 90deg about x;Initial rotation about x, y, z or a vector
visiblevisible: false;Hidden but still usable, for example as an invisible path
repeatrepeat: 11 step (500mm, 0, 0);Numbered copies; see below
connectionshinge: edge.top on Arm.edge.bottom;See Connecting parts

Groups

A Group holds named members that share one frame. Members can be placed against each other, and the group moves as one.

Door::Group.wooden { panel: Box { size: (800mm, 2000mm, 40mm); } knob: Sphere { radius: 30mm; at: panel.face.front + (320mm, 0, 25mm); } anchor grip: knob.center; // a custom anchor on the group at: (430mm, 1000mm, 0); }

Refer to a member as Door.knob, and to its anchors as Door.panel.edge.left.

repeat and i

repeat: N makes N copies named Name1 to NameN. Add step (dx, dy, dz) to shift each copy, or use i (0, 1, 2 …) in any expression for patterns such as rings.

// A row of lane dashes: Road.dash1 ... Road.dash11, 500mm apart Road::Group { surface: Box { size: (6000mm, 4mm, 700mm); } dash: Box.dash { size: (220mm, 2mm, 18mm); at: surface.face.top + (-2500mm, 1mm, 0); repeat: 11 step (500mm, 0, 0); } } // A ring of 7 trees Trunk::Cylinder.trunk { radius: 9mm; height: 70mm; repeat: 7; at: (680mm * cos(i * 0.9), 35mm, 620mm * sin(i * 0.9)); }

Give repeated parts a class (.dash, .trunk) and style or move them all with one selector.

scatter: random copies

scatter places copies at random spots that stay the same every time you build: leaves on a floor, droplets in a cloud, stars in a field. Choose the area, then any options.

Leaf::Polygon.leaf { sides: 5; radius: 22mm; thickness: 2mm; scatter: 40 in plane xz (640mm, 440mm) seed 4 spin 360deg vary 30% apart; } Drop::Sphere.drop { radius: 8mm; at: (0, 200mm, 0); scatter: 25 in sphere 120mm; } Peg::Cylinder.peg { radius: 6mm; height: 40mm; scatter: 12 on circle 300mm; }

Areas: in plane xz (w, d) (also xy, yz, any plane as an equation in quotes, in plane "x + y + z = 300mm" (w, d), or in plane normal (1, 1, 0) (w, d) through the part's at), along x 800mm, in box (x, y, z), in disc 300mm, on circle 300mm, in sphere 200mm. Options: seed 7 for another layout, spin 360deg for a random turn, vary 20% for different sizes, apart to keep copies from touching. Flat shapes lie down in the plane; give a plane a third size, in plane xz (600mm, 400mm, 200mm), and the copies also spread 200 mm off it, filling a slab (bubbles, dust, stars) instead of lying flat. The copies are Leaf1 … Leaf40; style and move them all through their class.

fixed: a place on the screen

When the Observer moves, orbits or follows a part, a title in the scene drifts away. fixed pins a part to the screen instead: it stays at that place, at the size it has from the Observer's starting point, whatever the camera does. It keeps its own motion, so a badge can still spin and a caption can still fade.

Title::Text { text: "How a gearbox works"; size: 16mm; fixed: top-left; } Logo::Polygon { sides: 6; radius: 14mm; thickness: 3mm; fixed: bottom-right 4%; } Step::Text { text: "Step 2"; fixed: (50%, 90%); }

Places: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right, then an optional margin written as a bare percentage, fixed: bottom-right 4%; (5% of the frame's height by default; there is no margin word or property, and W0232 offers to fix either); or (x%, y%) for the part's centre from the top-left corner. A fixed part goes on a whole part, not a group member, and takes no hinge, attach or other connection.

Stacking: parts at the same named place that show at the same time stack instead of covering each other: a title and a subtitle both at fixed: top; sit one under the other; at the bottom they stack upwards. A part that has faded out (opacity 0) leaves its place free, so captions that take turns still sit in one spot.

caption-band: a clear strip for words

Words on top of a moving mechanism are hard to read and hide what they describe. caption-band keeps a strip at the bottom of the picture clear: the scene is drawn smaller, above it. Captions from narration sit at the bottom of the band, and Texts with fixed: band; at its top.

Scene { frame: youtube; caption-band: 18%; } Note::Text { text: "Teeth push on teeth"; size: 6mm; fixed: band; }

The band runs from 0 to 40% of the height; on is 18%, off removes it. It shapes the Observer's view, in the preview and in exports; the Free camera ignores it.

define: your own part types

define names a part type built on a shape or a group, with parameters and optional defaults. Use it like a built-in shape, with values in brackets, by position or by name. Properties in the part's own block are added to the type's, and replace any with the same name.

define Tyre(mount, r = 55mm) :: Wheel { radius: r; width: 34mm; hinge: axis on mount; } FL::Tyre(Car.axle1.end.b); FR::Tyre(Car.axle1.end.a); RL::Tyre(mount: Car.axle2.end.b, r: 60mm);

The type's name is also a selector and a motion target, so one rule styles or moves every part built from it:

Tyre { color: gradient(#16181b, #5d6670, axis: x); }
Tyre { forever: roll: Road; }

A parameter can hold any value, including an anchor such as Car.axle1.end.b. Parameters hide let values with the same name. A type built on a Group cannot go inside another group.

Shape reference

Every shape also has center and com, its true centre of mass for a uniform solid (center-of-mass is the long name). For a Box they are the same point; for a Cone the centre of mass is a quarter of the way up, for a Dome 3/8 of the radius above its base, and for a Sector or Arc it moves toward the curved edge. com.x, com.y and com.z are lines through it, so hinge: com.z on Base.axis pivots a part at its balance point. A group's com weighs its members by volume. A required property is in bold.

TypePropertiesAnchors
Boxsize (x, y, z)face.*, edge.*, corner.*, axis (y)
Sphereradiuspole.top, pole.bottom, axis (y)
Cylinderradius, heightaxis (y), face.top, face.bottom
Coneradius, heightaxis, face.top, face.bottom, apex
Torusradius, tubeaxis (z), face.front, face.back
Rodlength, radiusaxis (y), end.a, end.b
Linklength, width, thicknessend.a, end.b, mid, axis: all lines along z, like pin axles; line (along the bar)
Gearteeth, module (2mm), widthaxis (z), face.front, face.back, pitch-circle
Pulley, Wheelradius, widthaxis (z), face.front, face.back
Discradius, thicknessaxis (z), normal, face.front, face.back
Rectsize (w, h), thicknesslike Box, plus normal
Springradius, length, turns, wireaxis (y), end.a, end.b
Linefrom, to, strokestart, end, mid, axis
Arrowfrom, to, stroke, headtail, head, start, end, mid
Curvedomain, point, strokestart, mid, end
Tuberadius, inner, heightaxis (y), face.top, face.bottom
Capsuleradius, lengthaxis (y), end.a, end.b
Prism, Pyramidsides, radius, heightaxis (y), face.top, face.bottom (Pyramid: apex)
Domeradiusaxis (y), face.bottom, pole.top
Wedgesize (x, y, z)like Box, plus slope
Platesize (w, h), thickness, corner, holelike Box, plus normal and hole
Beamlength, height, width, thicknessan I-beam along x: like Box, plus end.a, end.b, line
Ringradius, inner, thicknessaxis (z), face.front, face.back
Nutradius, thickness, holeaxis (z), face.front, face.back
Boltradius, length, headaxis (y), end.a, end.b, head (at end.b)
Camradius, lift, widthaxis (z), face.front, face.back, nose
Starpoints, radius, inner, thicknessaxis (z), face.front, face.back
Meshsrc, size (x, y, z), fitlike Box (a 3D model you added, fitted in the size)
2D shapes lie flat in the x-y plane, facing +z; thickness defaults to 1mm. Angles go anticlockwise from +x.
Trianglesize (w, h), apex, thicknesslike Box, plus normal, apex, corner.left, corner.right
Ellipsesize (w, h), thicknesslike Box, plus normal
Polygonsides, radius, thicknessaxis (z), normal, face.front, face.back
Sectorradius, angle (90deg), start, inner, thicknessa pie slice, or a ring segment with inner: axis, normal, tip, mid, edge.start, edge.end
Arcradius, angle (180deg), start, strokea curved line: start, mid, end, axis
Pathpoints (x, y) (x, y) …, thicknessany flat outline: axis, normal, start, end
Texttext "…", size, facingwords as a part (see Text): like Box, plus normal
Groupmemberscenter, com, com.x, com.y, com.z, custom anchors

Your own 3D models

More › 3D models… adds STL, GLB or glTF files (a glTF must be a single file; export as .glb if it points to others). Each file is placed with a Mesh part, which fits the model inside its size box: fit: contain (the default) keeps its proportions, fit: stretch fills the box. Because the box is known, every Box anchor works, so models attach, hinge and slide like any other part. Adding one model inserts a Mesh line for you, sized from the file (read as millimetres). Models are saved inside the project's .zip and kept in this browser.

Piston::Mesh { src: "piston.stl"; size: (60mm, 90mm, 60mm); at: (0, 45mm, 0); }

Curves: shapes as functions

A Curve is any function of a parameter. Use t inside it and the curve changes over time.

Wave::Curve { domain: u in 0..1; point: (u * 400mm - 200mm, 40mm * sin(6.283 * (u * 2 - t / 2s)), 0); stroke: 4mm; }

Anchors

Anchors are named points and lines on a part. They live in the part's own frame and move with it, which is why you can place and connect parts without computing coordinates.

face.front face.top face.right edge.left-front edge.right (face axis) corner.right-top-front +y up · +x right · +z towards you
On a Box, faces are named by direction. edge.left-front is where two faces meet. edge.right alone is the right face's centre line along its longest side, which is what you want for hinging thin plates.
FormKindExample
face.<f>point with a normalFrame.face.top
edge.<f>line (face axis)Door.panel.edge.left
edge.<f1>-<f2>line where two faces meetBase.edge.right-top
corner.<x>-<y>-<z>pointBox.corner.left-top-front
axislineG1.axis
end.a, end.bpoint on a lineCrank.end.b
anchor name: …;custom pointanchor tip: (0, 120mm, 0);

Faces are front (+z), back (−z), left (−x), right (+x), top (+y), bottom (−y). Anchors are never guessed: a wrong name is an error that lists the valid ones.

Connecting parts

A part declares what it connects to, inside its own block. The connection places the part for you and keeps it connected while things move.

PropertyFreedomWritten as
attachnone: moves as oneattach: end.a to G1.face.front;
hingeturns about a linehinge: edge.top on Arm.edge.bottom;
sliderslides along a lineslider: axis on Barrel.axis;
meshturns with a gear, ratio from teethmesh: G1; or mesh: G1 at 60deg;
coupleturns with a part at any ratiocouple: MinuteHub 1/12;
beltturns with a pulley, same directionbelt: Motor;
pinkeeps two points together, closing a looppin: end.b to Rocker.end.b;

attach

Moves the part so its anchor sits on the other part's anchor, then fuses them. With one anchor, the part's centre is used.

Crank::Rod { length: 50mm; radius: 2.5mm; attach: end.a to G1.face.front; } Bob::Sphere { radius: 30mm; attach: Arm.face.bottom; }

hinge and slider

Both take own line on other line, or just the other line (then the part's own axis is used). The other side can also be a point: an anchor plus an offset, or a fixed position such as (110mm, 0, 0). Then the part turns about its own line through that point. The part's motion rule drives the joint. Add range to limit it. A group can be hinged too: name a member's line as its own line.

Sign::Box { size: (220mm, 140mm, 10mm); hinge: edge.top on Arm.edge.bottom; range: -30deg..30deg; } FL::Wheel { radius: 53mm; width: 34mm; hinge: axis on Car.axleF.end.b; } Piston::Cylinder { radius: 36mm; height: 50mm; slider: axis on Barrel.axis; range: -110mm..110mm; } Door::Group { panel: Box { size: (800mm, 2000mm, 40mm); } hinge: panel.edge.left on Frame.edge.right; }

Hinges carry their part along when the part they sit on moves. That is how wheels ride on a car and a forearm rides on an upper arm.

mesh, couple and belt

mesh places the gear at the correct centre distance, turns it so the teeth interlock and sets the ratio. Add at 60deg to place it round the other gear. couple uses any ratio between round parts. belt draws a belt and turns both pulleys the same way.

G1::Gear { teeth: 36; module: 2mm; at: (0, 0, 0); } G2::Gear { teeth: 12; module: 2mm; mesh: G1; } // turns -3 times as fast G3::Gear { teeth: 18; module: 2mm; mesh: G2 at 60deg; } // placed above G2

Linkages and closed loops

Hinges in a chain are easy: each part rides on the one before. A closed loop, where the chain comes back to the ground, also needs the parts to stay together. pin says which two points must meet. Drive one joint, such as the crank. Every hinge or slider in the loop without a motion rule is solved at each frame so the pins hold.

// A crank-rocker four-bar linkage Crank::Link { length: 40mm; hinge: end.a on (0, 0, 0); } Coupler::Link { length: 120mm; hinge: end.a on Crank.end.b + (0, 0, 6mm); pin: end.b to Rocker.end.b; } Rocker::Link { length: 90mm; orient: 90deg about z; hinge: end.a on (110mm, 0, 0); }
Crank { forever: rotate: 180deg/s; }

Slider-crank

The same idea with a slider. The piston has no motion rule, so it slides to wherever the connecting rod puts it.

Crank::Link { length: 50mm; hinge: end.a on (0, 0, 0); } Bore::Cylinder { radius: 22mm; height: 220mm; orient: 90deg about z; at: (200mm, 0, 0); } Piston::Cylinder { radius: 20mm; height: 40mm; slider: axis on Bore.axis; } Rod::Link { length: 180mm; hinge: end.a on Crank.end.b + (0, 0, 6mm); pin: end.b to Piston.center; }
  • Use Link for bars. Its ends are lines along z, so hinges and pins on them turn in the xy plane.
  • A pin is an axle. Parts can sit at different depths along it, which is why + (0, 0, 6mm) layers the coupler in front of the crank.
  • The starting pose picks the branch. A four-bar can usually close two ways, elbow up or down. Orient the rocker roughly where you want it.
  • The compiler checks the whole motion. E0352 means the loop cannot close at all; check the lengths. E0353 gives the time when the linkage locks, for example when a crank is too long to turn fully.

Style

Selectors match like CSS. Later rules win; there is no specificity scoring.

SelectorMatches
Gearevery part of that type, or the part named Gear
Doorthe part named Door, including all its members
Door.knobone member of a group
.woodenevery part with that class (members inherit their group's classes)
#G2the part named G2
Crank, Knobboth
*every part (later rules still win, so put it first)
PropertyValues
color#hex, #rrggbbaa (the last two digits are alpha), rgb(255, 140, 0), rgba(255, 140, 0, 0.5), hsl(30deg, 100%, 50%), transparent, CSS names, steel brass oak copper aluminium iron rubber walnut glass graphite, or gradient(#a, #b, axis: x). A colour's alpha makes the faces see-through; transparent hides them and leaves the outline, a quick wireframe.
materialmatte plastic metal brushed chrome gold glass ghost (an x-ray look: faint faces with clear edges; see Cutaway) flat glow (shorthand for luminosity: 60%; the metals reflect a soft studio light)
outline1px #2b3138; an alpha colour such as #00000040 gives a faint edge
opacity0 (invisible) to 1 (solid), or a percentage like 40%. The faces, outline and label fade together; on a group it fades every member. It multiplies with the colour's alpha. Animate it in the motion file to fade parts in and out.
luminosityThe part shines and lights the parts near it: [level] [colour] [range], as in luminosity: 80%; or luminosity: 120% #ffd27a 600mm;. 100% is a lamp, up to 400% for a strong light. The colour defaults to the part's own; the range is how far its light reaches. A solid luminous part gets a soft halo; a see-through one (a beam, a glass shade) adds its light like a real beam. A lamp casts no shadow of itself. The first eight luminous parts light the scene (W0610 beyond that); the rest still shine. Animate the level in the motion file.
roughness, metalness0 to 1, or a percentage; they override the material. roughness: 0 is a mirror, 1 is chalk. metalness: 1 is bare metal and reflects the studio light.
wireframeon draws only the triangle edges, off (the default) the faces
shadownone stops the part casting a shadow; cast is the default
text and the font propertieswords printed on the part's faces: see Text
labela tag shown above the part, which follows it and is always on top. It is bare text with a soft halo, so it reads over light and dark backgrounds. For anything that stays on screen, Text reads better and never covers the motion
label-color, label-sizethe colour of the label's text, and its size (140%; 100% is normal)
label-backgrounda rounded panel behind the label, such as #10203080 (the last two digits are its alpha). Left out, there is no panel; none removes one set by another rule
cut, cut-color, explodesection and exploded views: see Cutaway and exploded views
traildraws the path an anchor follows: [anchor] [time] [colour], each optional. No time means the whole run so far.

Named styles: write once, reuse everywhere

Metal::Style { material: metal; color: steel; roughness: 0.3; } Polished::Style { roughness: 0.1; outline: 1px #2b3138; } Brass::Style { use: Metal; color: brass; } Gear { use: Brass, Polished; } Crank { use: Metal; color: #8f99a3; }

Metal::Style { … } names a set of properties, written like any other declaration; use: a, b; takes them. The list is read left to right, so a later style wins when two set the same property, and properties written beside use win over all of them. Styles can use other styles. A part can also take styles in the scene: Crank::Link { length: 80mm; use: Metal; }. Style rules written later still win over earlier ones, as in CSS.

Gear { color: steel; material: metal; outline: 1px #1e252c; } #G2 { color: brass; } .tyre { color: gradient(#141619, #b9c0c8, axis: x); } // a gradient makes spin visible Sun { color: #ffcf5a; luminosity: 160%; label: "Sun"; } // shines and lights the planets Pen { trail: #c2410c; } // the coupler curve of a linkage Blade { trail: face.top 1s #2f6f8f; } // the last second of a wiper's sweep

Cutaway and exploded views

Explainers often need to show what is inside. Three tools do it without changing the parts themselves.

Cut a part open

Barrel { cut: z 0mm; cut-color: #b8442e; } // cuts away the half towards the viewer Cover { cut: -x 40mm; } // cuts away everything left of x = 40mm

cut is a direction and an offset: a flat plane in scene coordinates, and the side the direction points to is removed. z points at the viewer, so cut: z 0mm; opens the front half. Use x, -x, y, -y, z, -z or a vector such as (1, 0, 1). The cut faces are filled in a darker shade of the part's colour, or cut-color. The plane stays where it is while the part moves, like a real section. cut: none; removes a cut set by an earlier rule.

Barrel, Gland { 0s..1.5s: cut: 90mm -> 0mm ease-out; } // the plane slides in

In motion, cut moves the plane: an offset, or a change from one offset to another. It takes its direction from the style (or z if the style has none). A cut never moves a part, so it can sit beside any motion source.

See through a casing

Housing { material: ghost; } // faint faces, clear edges Housing { material: ghost; opacity: 0.3; } // a little more solid

Explode an assembly

Scene { explode: 1 along y; } // a still exploded view
Scene { 1s..3s: explode: 0 -> 1.2 ease-in-out; } // the parts move apart, then stay apart
Base { explode: 0; } // stays where it is Bolts { explode: 2; } // moves twice as far as the rest

Each part moves away from the middle of the model by its distance from the middle times the amount: at 1, every part is twice as far out. along x, y or z keeps the movement to one axis, which suits stacks such as a gearbox cover and back plate. Parts held together by a group move as one; Text and Plot parts stay put unless they have their own explode weight. The exploded positions are for showing: hinges, meshes and the camera still follow the assembled positions.

Try the Cutaway: a hydraulic cylinder example, and the third chapter of How a gearbox works.

Motion

A motion rule names what to move and lists time blocks. The target can be a part, a .class, several of these separated by commas, or Observer.

Upper, Fore { 0s..2s: rotate: 0deg -> 40deg ease-in-out; } .tyre { forever: roll: Road; }

Time blocks

BlockRuns
forever:the whole animation
0s..2s:during that range
then:after the previous block, for as long as it lasted
then 3s:after the previous block, for 3 s

How values behave over time

ValueExampleBehaviour
Position or anglerotate: 90deg;Set from the start of its block. The latest block that has started wins.
Change of positionrotate: 0deg -> 90deg ease-in-out;Moves across its block, then holds the end value
Speedrotate: 60deg/s;Moves during its block; what it moved stays moved. Speeds from several blocks add up.
Change of speedmove: (0, 0, 0) -> (600mm/s, 0, 0);Speeds up or slows down across its block: an acceleration with easing
Accelerationmove: (0, -9.81m/s2, 0);Constant acceleration during its block
Function of trotate: 35deg * sin(t * 10);Any expression using time in seconds

Easings follow CSS: linear (default), ease, ease-in, ease-out, ease-in-out, cubic-bezier(x1, y1, x2, y2).

Properties for parts

PropertyExampleNotes
rotaterotate: 60deg/s about G1.axis;On a hinged part, turns it about its hinge. Without about, the part's own axis (or y). Also about x, y, z or a line on another part
orbitorbit: Earth 360deg / 2s;Circles round another part about the vertical, following it if it moves
movemove: (500mm/s, 0, 0);A vector: an offset, a speed or an acceleration. Add more move lines to combine them
alongalong: Run 12s;Follow a Curve, one lap per given time
x, y, zx: 0;Pin one coordinate
opacity0s..1s: opacity: 0 -> 1 ease-in;Fade in or out; works on a part or a whole group, with any easing or an expression of t
color0s..2s: color: #2f6fb0 -> #e0a63a;Change colour over a block (alpha too); the latest started block wins
luminosity0s..1s: luminosity: 0 -> 100%;Turn a light on, off or up; the colour and range come from the style

Properties for hinged and sliding parts

Every kind of motion uses one of two verbs: rotate turns and move slides. On a hinged part, rotate turns it about its hinge; on a slider, move takes a number along its line. opacity, color and luminosity change only how a part looks, so they work on hinged and sliding parts too, in the same rule or a rule of their own.

ConnectionPropertyExample
hingerotaterotate: 0deg -> 95deg ease-in-out;
hingerollroll: Road;
slidermovemove: -90mm -> 90mm;

Acceleration

There is no separate acceleration keyword: write the unit and the value means it. Three common patterns:

// 1. Speed up, cruise, brake: changes between speeds let cruise = (600mm/s, 0, 0); Car { 0s..1.5s: move: (0, 0, 0) -> cruise ease-in; then: move: cruise; 3s..4.2s: move: cruise -> (0, 0, 0) ease-out; } // 2. A thrown ball: a launch speed plus gravity, both as move lines Ball { 0s..1.5s: move: (600mm/s, 1200mm/s, 0); move: (0, -1.62m/s2, 0); } // 3. A motor spinning up: an angular acceleration, then a steady speed Motor { 0s..1s: rotate: 360deg/s2; then 3s: rotate: 360deg/s; }

roll

roll turns a hinged wheel by the distance its centre travels divided by its radius, so the spin always matches the speed, including while speeding up and braking. It assumes straight-line travel on level ground.

orbit

orbit keeps a part's offset from a centre part and swings it round. Because the centre is followed, orbits nest: a moon can circle a planet that circles a star.

Earth { forever: orbit: Sun 360deg / 12s; rotate: 360deg / 2s; } Moon { forever: orbit: Earth 360deg / 2s; }

Text

Words are part of the scene, drawn in 3D, so they sit where you put them and never cover the motion. There are two kinds: a Text part, made only of words, and face text, printed on any part's faces.

Title::Text { text: "Four-stroke cycle"; size: 30mm; at: (0, 260mm, 0); } Block::Box { size: (200mm, 120mm, 80mm); at: (0, 60mm, 0); }
Title { color: #ffd27a; font-weight: 600; text-background: #10203080; } Block { color: steel; text: "ENGINE BLOCK"; text-side: front top; font-family: serif; font-style: 3d; text-depth: 2mm; }

A Text is a part like any other: place it, attach it, move it, fade it with opacity or change its colour with color: #a -> #b. size is the height of a line of letters; \n in the text starts a new line. A Text turns to face the viewer (facing: camera, the default), so it reads straight however the camera moves or zooms; facing: front keeps it fixed in the scene, like a sign on a wall. Words printed on a part's faces stay on the faces; for words that should always read straight, use a Text part beside it. To keep a title in one place on the screen while the camera moves, add fixed.

PropertyValues
text"PISTON". In style it prints on the part's faces; on a Text it replaces the scene's text
text-sidefront (the default), back, left, right, top, bottom, several at once, or all. Cylinders, rods and other round parts wrap the text round their side, like print on a can
font-familysans, serif, mono, a bundled font (below), or any font installed on the computer in quotes: "Avenir Next"
font-sizea length such as 24mm, or a share of the face such as 40%. Leave it out and the text fits the face
font-weightnormal, bold, or 100 to 900
font-styleitalic; 3d for solid letters with depth, lighting and shadows (sans, serif and mono outlines); or italic 3d
text-colorthe colour of the letters (a Text also takes color). Without it, dark or light, whichever reads on the part
text-depthhow far 3D letters stand out, such as 4mm
text-align, text-padding, line-heightleft / center / right; the space to the edge; the space between lines (1.2)
text-backgrounda panel behind the letters, such as #10203080, so captions read over anything
text-transformuppercase, capitalize (first letters), lowercase or none. It changes the letters drawn, not your code
letter-spacingextra space between letters: a share of the letter height (8% for airy capitals, -2% for tight large titles) or a length (1.5mm)

Bundled fonts

These fonts ship with GearScript, so text looks the same in the preview, on every computer and in every export, online or offline. Each loads the first time a project uses it. All are under the SIL Open Font License.

FontGood for
Montserratbold, geometric titles
Oswaldtall, condensed headlines
"Bebas Neue"all-capitals poster titles (one weight)
"Playfair Display"elegant serif titles
"Space Grotesk"a technical, modern look
Interclean body text and labels
Caveathandwritten notes and callouts
"JetBrains Mono"code, readouts and numbers
"Noto Sans Devanagari"Hindi, Marathi, Sanskrit and Nepali, with Latin letters too
Title { font-family: Montserrat; font-weight: 700; text-transform: uppercase; letter-spacing: 8%; } Note { font-family: Inter; }

Two fonts are plenty: one for titles, one for everything else. 3D letters (font-style: 3d) come in sans, serif and mono only; a bundled font asked for in 3D is drawn in the nearest of those, with warning W0623.

The type hints suggest every property and its values as you type. Insert a Text from the shape picker (Text and sky).

Camera, lights and scene

Observer { position: (2600mm, 1700mm, 3400mm); look-at: (450mm, 1000mm, 0); zoom: 1; fov: 40deg; } Light { kind: directional; direction: (-1, -2, -1.5); power: 0.85; } Light { kind: point; at: (0, 0, 0); power: 0.5; } Scene { duration: 4.5s; grid: floor; background: #070a10; }

background takes any colour. background: transparent keeps the background clear: the preview shows a checkerboard, and a saved PNG frame, PNG frames, GIF and the 3D player keep the transparency, so the animation can sit on a slide, a web page or in a video editor. MP4 and WebM have no transparency, so those fill it with the theme colour. You can also pick Transparent under Background in the right-click menu or Settings without changing the code.

Frame: portrait, landscape, Shorts and Reels

Scene { duration: 8s; frame: reel; }

frame sets the shape and size of the picture. The preview shows exactly that frame, and exports use its size. Presets: youtube (1920×1080), youtube-short, reel and tiktok (1080×1920), instagram and linkedin (1080×1350), square, slides for PowerPoint, Keynote and Google Slides, 4k and cinema. Or write a ratio, frame: 4:5, or pixels, frame: 1080x1920. For Shorts, Reels and TikTok a dashed box shows the safe area that the app's buttons and captions leave clear; keep titles inside it. A tall frame keeps the camera's width, so turning a scene portrait does not crop it.

Sky, ground and sun

Scene { duration: 8s; sky: dusk; ground: #2c3328; sun: (6deg, -30deg) #ffb36b; }
Scene { 0s..8s: sun: -5deg -> 10deg ease-in-out; } // a sunrise

sun draws a sun on the sky: its height above the horizon, then its direction (0deg looks into the scene along −z, 90deg is to the right, along +x), then an optional colour. It sits far beyond the scene, so it never rests on the ground; below the horizon it is hidden, and it lights the scene from where it stands, fading as it sets. Animate its height in a Scene motion rule for a sunrise or a sunset.

The ground sits at the foot of the model (glowing parts do not count, so a lamp below it stays hidden); ground: #2c3328 at 0mm; sets the height yourself. shadow: none; in a part's style stops it casting a shadow.

sky wraps the whole scene in a hollow, glowing sphere that travels with the camera, so you never reach its edge. Choose a preset, day dawn dusk sunset night space overcast studio, or give one to three colours from the top through the horizon to below: sky: #1d3b70 #f2b38a #2a2030;. The sky also tints the soft ambient light, so a dusk sky warms the parts. night and space have stars, and stars: 1500; sets how many. ground lays a wide floor at the foot of the model that fades into the sky and catches the shadows. Right-click the preview and pick Sky to try the presets; it writes your choice into the Scene block. The sky shows while the preview background is From the scene, the default.

Without an Observer the studio frames the whole scene. Without a duration it uses the end of the last time block; if every block runs forever it uses 6 s, and with no motion at all, 4 s.

Moving the camera

PropertyExampleNotes
followfollow: Car offset (380mm, 260mm, 1150mm);Keeps a fixed offset and looks at the part
orbitorbit: Mouse 60deg/s;Circles a part, even a moving one, and looks at it
look-atlook-at: Door.knob;Turns to face a part or point
zoomzoom: 1 -> 2;Zooms about the look-at point
move, x, y, z, positionmove: (0, -20mm/s, 0);Combine with follow or orbit for spirals
// Circle with the eagle while dropping, like a hunter closing in Observer { forever: orbit: Mouse 60deg/s; move: (0, -20mm/s, 0); }

Sound

Sounds play at moments of the motion, so they stay in time when the motion changes. Declare a Sound in the scene, then play it from a motion rule with sound:. Every sound is generated by the studio from a recipe, not recorded, so it is free of copyright and sounds the same on every computer.

Knock::Sound { kind: knock; } Tick::Sound { kind: tick; volume: 60%; } Bell::Sound { kind: bell; pitch: 120%; } Hum::Sound { kind: hum; length: 3s; }
Lid { 0s..1.5s: rotate: 0deg -> 110deg ease-out; sound: Knock at end; // when the lid lands } Pendulum { forever: rotate: 6deg * sin(t * 3.1416); sound: Tick every 1s 50%; // a tick each second, at half volume } Scene { sound: Bell at 4s; sound: Hum; } // at set times, from no part in particular
WritePlays
sound: Knock;when the time block starts
sound: Knock at end;when the time block ends: a part landing
sound: Knock at 1.5s;this long after the block starts (outside a time block, at that time)
sound: Tick every 1s;again and again while the block runs
… 60%quieter, after any of the above
Sound propertyValues
kindone of the built-in sounds below
pitch50% to 200%; higher or lower than normal
volume0% to 100%
lengthfor the sustained kinds, such as length: 3s;
GroupKinds
Clock and mechanismtick tock ratchet clank latch gear spring rattle
Machines and steamfootstep (a metal foot) clang chug (one puff) hiss rumble (an engine; takes length)
Impactsknock thud click snap pop tap
Movementwhoosh swish whirr servo slide creak squeak (these take length)
Signalschime bell ding beep blip success alert
Ambiencehum (takes length)
  • Picking a sound: while typing sound: or kind:, the hints list the sounds; arrow through them to hear each one, or choose Pick a sound… for the catalogue, where you click a sound to hear it and adjust pitch and volume before inserting it. A new sound is declared in the scene for you. The speaker button in the header opens the catalogue too.
  • Volume: the speaker button on the preview opens the volume slider and a mute button; its icon shows the level. Below them, Sounds in this animation lists every sound event, to hear one, remove it from the code, or add a sound at the current moment.
  • In the examples: the robot's feet clank as they land, the locomotive chuffs faster as it speeds up, the door creaks, the wiper squeaks, the clocks tick. Open any of them to see the lines that do it.
  • Left and right: each sound comes from where its part is on screen, so a sound on the left plays more in the left speaker. Sounds in a Scene rule play in the middle.
  • Exports: MP4 and WebM include the sound (AAC or Opus, whichever the browser can make); the export panel can leave it out. The 3D player plays it once the viewer clicks. GIF and PNG frames are silent. On the command line, gearscript render project.gscript -o sound.wav writes the sound track alone.

Narration and voice-overs

Narrator::Voice { voice: en-GB; rate: 95%; }
Crank { 0s..3s: rotate: 0deg -> 180deg; say: Narrator "The crank turns half a revolution."; }

say speaks a line with the browser's own voice, in the studio and in the 3D player, and shows it as a caption. voice is a language such as en-US or en-IN, or the name of a voice on the computer (the hints list them); captions: off; speaks without the words. A video file cannot record the browser's voice, so exports draw the captions into the picture, and the export panel saves them as an .srt file for YouTube or a video editor. For speech in the file, keep Speak them into the video ticked in the export panel (it is, by default): a neural voice (Piper) runs in your browser and speaks each line into the MP4 or WebM. It is experimental, and the voice (20 to 70 MB) downloads the first time; neural: en_GB-alan-medium; on the Voice picks one. Or record a voice-over, or make one with any text-to-speech tool, and add it with Insert › 3D models and audio, and play it like any sound:

Writing lines that land: a voice speaks about 2.6 words a second at rate: 100%. Leave that long before the next line of the same voice, because a new line cuts the caption off, and make the block or Scene { duration } long enough for the last line to finish. One idea per line, 4 to 14 words, works best. Put a line in the block whose motion it describes, so words and motion move together when you change the timing.

Captions like subtitles: a long line is not shown all at once. It is split into parts of at most two short lines, at sentence ends first, then at commas, then between words, and each part shows for its share of the time the line takes to say. The same parts go into exports and caption files. Still, a paragraph in one say: reads badly and cannot follow the picture: W0551 warns about a line over 40 words, and W0550 when a line is still being spoken as the next one starts.

Captions as files: the export panel saves every caption part as .srt (YouTube, video editors) or .vtt (web players: <track src="name.vtt">). With chapters, the times run across the whole film.

VoiceOver::Sound { src: "intro.mp3"; volume: 90%; }

Audio files (MP3, WAV, OGG, M4A) also work for music and recorded effects. They play in the preview, are mixed into MP4 and WebM, and are saved in the project's .zip.

Charts

A Plot draws a function or your data on axes with ticks and labels. It is a part like any other, so it can sit next to the mechanism it explains, fade in, and draw itself on.

Wave::Plot { size: (420mm, 240mm); function: sin(x - t * 2) * exp(-x / 8); x-axis: 0..10 step 2 "time (s)"; y-axis: -1.2..1.2 step 0.5 "position"; reveal: min(1, t / 3s); } Sales::Plot { size: (360mm, 240mm); data: (1, 12) (2, 18) (3, 9) (4, 22); x-label: "quarter"; at: (480mm, 0, 0); }
PropertyValues
function or dataa formula in x (add t to make it move), or (x, y) pairs
kindline (functions), bars (data), points, area
x-axis, y-axisa range, a tick step and a title: 0..10 step 2 "time (s)". Leave y-axis out and it fits the values
revealhow much is drawn, 0 to 1; min(1, t / 3s) draws it on over 3 s
stroke, axes: none, grid: noneline width; hide the axes to lay a second series over the first (same size and ranges)

Style the series with color and the numbers with text-color. A chart's anchors include origin, the point (0, 0), for attaching markers.

Recipes

Complete projects you can open straight in the studio. Each shows one idea.

Errors

Every problem names its file, line and column, says what went wrong and suggests a fix. Errors stop the preview from updating (it keeps showing the last good build); warnings do not. Copy as JSON produces:

[ { "severity": "error", "code": "E0412", "file": "scene", "line": 7, "col": 1, "message": "G3 is driven twice with ratios that disagree (-2.000 vs -1.500).", "fix": "Remove one mesh, or change teeth so both paths give the same ratio." } ]

Codes

CodesAreaTypical causes
W0100VersionThe project's first line says it was written for a newer major version of GearScript than this studio.
E0100–E0122Syntax, let, repeatA missing ; or }, an unknown unit, a let name with a capital, a let defined twice, repeat outside 1–200
W0105Hyphengap-10mm is one name, because names may contain hyphens (like ease-in-out). When the first part is a let or a part, the warning suggests gap - 10mm.
E0221–W0223sunA sun that is not (height, direction), a height outside −30° to 90°, a sun motion with more than a height, or a sun motion without sun in the Scene block.
E0230, E0231fixedAn unknown screen place (write fixed: top-left; or fixed: (50%, 10%);); fixed on a group member, or on a part joined to the scene with a hinge, attach or another connection.
W0109, W0148, W0149Layout slipsA group member written A::Sphere (read as A: Sphere); a missing @scene line; a chapter title without quotes. Each builds anyway, and Fix rewrites it.
W0232fixed marginfixed: bottom-left margin 5%; or a separate margin: 5%;. Both are read as fixed: bottom-left 5%;, and Fix rewrites them that way.
W0550, W0551NarrationA spoken line is cut off by the next one (start the next later, or say less); a line over 40 words (split it into several say: lines, one per block).
E0626, E0627Text stylesAn unknown text-transform (write uppercase, lowercase, capitalize or none); a letter-spacing that is not a length or a percentage.
W0628Words over wordsTwo Texts cover each other on the picture while both are showing. The message names them and the first moment. Move one, give it another fixed: place, or let them take turns with opacity. Texts at the same fixed: place stack by themselves, and a quick crossfade between two captions is fine.
W0624, W0625Words on the picturecaption-band outside 0 to 40%; a Text holding more than 16 words, which covers the animation (use narration, Texts that take turns, or fixed: band;).
W0519PacingA spoken line takes longer to say than the motion lasts, so the picture stands still under the words, or it runs past the end of the animation. Stretch the motion over the line (about 2.5 words a second), or say less.
W0560OverlapTwo parts that are not connected pass through each other while one of them moves (parts that both stay still are left alone, so a band round a tower is fine). The message names both and the first moment it happens. Move one, connect them, or add overlap: allow; to one of them if it is meant.
E0130–E0139defineA type name in lower case or the same as a shape, an unknown or missing parameter, too many values, a group inside a group, a type built on itself
E0201–E0215PartsUnknown shape type, a required property missing (Box needs size), fewer than 6 teeth, a name used twice, two Observers, orient without about
E0300–E0311Values and anchorsA vector where a number was expected, an anchor a shape does not have (the message lists the valid ones)
E0320NamesA name that is misspelled or declared further down
E0330–E0345attach, hinge, sliderAttaching a part to itself, a hinge where neither side is a line, a hinge between parts that are attached
E0350–E0354pin and loopsA badly written pin, a loop that cannot close (with the gap in mm), a linkage that locks (with the time), a loop where every joint is driven
E0400ConflictsOne piece driven by two things: two parts fused together that each have a motion rule, or a rule plus a hinge, mesh or belt. Several rules for the same part are fine: they add up. The message lists every source.
E0410–E0419mesh, couple, beltMeshing a non-gear, different modules, gears at the wrong distance, gear loops whose ratios disagree, a part with no axis, a badly written connection
E0420RangesA hinge rotation or slider move outside its range
E0500–E0541MotionAn unknown target, a time range that ends before it starts, bad rotate / move / follow / orbit forms, roll on a part with no radius, a speed change in a forever block, an acceleration that changes
E0540–E0547SoundNo Sound with that name, an unknown kind, a sound: with a bad "at" or "every", a Sound inside a group, a rule aimed at a Sound
E0600–E0608, W0607–W0610StyleUnknown colour (including a bad rgb(), or a hex code that is not 3, 4, 6 or 8 digits) or material, a gradient with one colour, a trail value that is neither an anchor nor a colour, a luminosity that is not a level, colour and range, a shadow or wireframe value that is not one of its words, a value outside 0–1, more than eight luminous parts
E0998SimulationA problem that only appears when the animation plays; check the last motion rule you changed
W0202–W0602WarningsA property a shape ignores, a mesh nothing turns, a selector or class that matches nothing, a camera property that does not exist, a move on a hinged part

Chapters

A longer video is easier to make in parts. Each chapter is a complete animation, with its own time starting at 0 s and any length, and the export joins them into one video. You can preview, fix and export any chapter on its own.

  • Start: press Chapters in the bar above the code. What you have becomes Chapter1, and Chapter2 continues it with the same scene and style, ready for new motion.
  • The strip above the code shows common and every chapter as a timeline: each block is as wide as the chapter is long (within limits), with its length, a mark for a fade or crossfade, and a progress line while the film plays. Drag a block to move the chapter. Click one to edit it; double-click (or ⋯) for its settings: name, title, what it starts from, how it starts, and a fade-out at the end. The × on a chapter deletes it: an empty one goes at once, one with code asks first (Shift-click skips the question), and Undo in the note that follows brings it back. + adds a chapter that continues this one, a copy, or a blank one. Right-click a chapter to move, duplicate or delete it.
  • Film, under the preview, plays every chapter in order with its transitions, as the export will. It is the default (the studio remembers if you pick Chapter), and clicking a chapter while the film plays moves the film to that chapter. Chapter plays only the one you are editing. The tracks panel shows the chapters as a row along the top while the film plays.
  • Problems lists errors in every chapter, marked with the chapter's name; click one to go there.
  • Pasting a reply: a reply with chapters is merged by chapter name. A chapter with a name you have replaces yours, a new name is added, and chapters the reply leaves out stay as they are; @common changes only when the reply has one. When a paste would change more than one of your chapters, a dialog lists them first, so you can untick any you want to keep. One Undo puts it all back. To change just one chapter with an AI, use Prompt AI › Ask about: This chapter.

In the code

@gearscript 1 @common @scene Scene { frame: youtube; sky: studio; } // every chapter shares the picture Narrator::Voice { voice: en-GB; } @style Gear { color: brass; material: metal; } @chapter Intro "Two gears" fade 0.8s @scene G1::Gear { teeth: 36; } G2::Gear { teeth: 12; mesh: G1; } @motion G1 { 0s..4s: rotate: 0deg/s -> 40deg/s; say: Narrator "Two gears, one driving the other."; } @chapter Ratio extends Intro crossfade 1s fade-out 1s @scene Observer { position: (0, 60mm, 420mm); look-at: G2; } // replaces the camera it inherits @motion G1 { forever: rotate: 40deg/s; }
On a chapter lineDoes
@chapter Namestarts a chapter; the name starts with a capital letter. Its @scene, @style and @motion follow (leave out any it does not need)
"Title"the title used in the chapter list for a video description (the name, spaced out, if left out)
extends Otherstarts from Other's scene and style, not its motion. Add parts and later style rules; a new Observer replaces the one it inherits
cut · fade 0.8s #000000 · crossfade 1show this chapter starts: straight in, through a colour, or blended over the end of the chapter before (a crossfade overlaps the two)
fade-out 1sfades to black (or a colour) at the end: for the last chapter

@common comes first and holds what every chapter shares: put Scene { frame: … } there, since one video has one shape. Chapters save as one .gscript file (or a .zip with 3D models), and the 3D player carries every chapter.

The preview shows a crossfade with a still picture of the chapter before; the export blends the moving pictures.

Export and projects

  • Whole film or this chapter: with chapters, the export panel asks which. MP4 and WebM exports run to 20 minutes at 30 fps (longer at a lower frame rate); GIFs are for short clips.
  • Chapter list: with chapters, Copy the chapter list gives lines such as 0:00 What a gearbox does for a YouTube description (YouTube makes chapters when there are at least three, each 10 s or longer).
  • Tracks (the button at the right of the timeline) shows every motion rule, spoken line and sound on a time axis. Click a bar to jump to that moment and to its line in the code.
  • Export has five formats; MP4 and WebM carry the project's sounds. If you change the code or an export option after rendering, Save is switched off until you render again, so a saved file always matches what you see. Long animations export their first part at true speed (a GIF holds 300 frames; a video 3,600), and the panel says how much fits. MP4 (H.264, plays everywhere) and WebM (VP9, small) videos: 640 to 1920 px wide, 24, 30 or 60 fps, three quality levels, up to 3,600 frames. GIF: 320 to 800 px, 15 to 25 fps, up to 300 frames; it has only 256 colours, so videos look smoother. PNG frames: every frame as a numbered PNG in one .zip (up to 1,800 frames), with full transparency; video editors (Premiere, DaVinci Resolve, After Effects, Final Cut) import it as an image sequence. With a transparent background a GIF keeps it too, with each pixel fully clear or solid. 3D player: one .html file with the animation inside; anyone can open it in a browser to watch, pause, scrub, orbit with Free and change what is shown from the right-click menu, and it can be put on any website or embedded with an <iframe>. Pick the shape for videos and GIFs: like the preview, 16:9, 1:1, 4:5 or 9:16. Exports always use the Observer camera. Video needs a current Chrome, Edge, Safari or Firefox.
  • Shadows: the main light casts soft shadows, including onto the ground under the model. Turn them off in Settings or the right-click menu if the preview is slow.
  • No accounts: there is nothing to sign up for. Everything, including every export, works straight away, and your projects stay in your browser. Where the studio runs on a GearScript server, it asks once whether it may count which features are used; a copy opened on its own asks nothing and sends nothing.
  • File menu: New project (Ctrl Alt N) starts from a blank page or a starting point: a Short or Reel, an explainer, an assembly sequence or a slide graphic. Open recent lists the last eight projects in this browser (the code; 3D models and audio files are added again). One-file projects save as .gscript; the installed Studio opens .gscript files from your computer.
  • Project name: the bar above the code shows it. Click it to rename. An orange dot means there are unsaved changes.
  • Save (the button in the header, or Ctrl S) uses the project name. Its arrow (or Ctrl Shift S) saves as an .gscript file, a .zip, three loose files (into a folder you pick, in Chrome and Edge) or JSON for other tools. In Chrome and Edge the first save asks where to put the file, and later saves write straight back to it, like a desktop app; other browsers download a copy each time. Before an example or another project replaces unsaved work, and before you close the tab, the studio asks whether to save.
  • By default, Save in the One file view writes name.gscript; in the Three files view it writes name.zip holding name.scene, name.style and name.motion. Open reads either (and .json projects saved for tools), takes the project name from the file name and switches to the matching view.
  • Is the preview up to date? The small badge at the top left of the preview tells you: a blue spinner while your latest edits are waiting to run, a green tick once the preview shows them, and a red mark when the code has errors (the preview then keeps the last version that compiled).
  • Right-click the preview (or press the Menu key or Shift F10 on it) for play and restart, speed, show or hide labels, the floor grid, joint axes, trails and the scene summary, the background, the camera (with Reset the view, also the circular-arrow button above the preview, which brings a Free camera back to where the scene's Observer stands), expand, save the current frame as a PNG and export a GIF. Changes there are saved to Settings, and Settings changes show there.
  • Examples and new projects open in this tab; with unsaved work the studio asks first and offers a new tab instead.
  • Download Studio (in the More menu) installs GearScript Studio as an app, the same way Chrome and Edge offer with the install icon in the address bar: it gets its own window and icon, and works offline. Where the browser cannot install (Firefox, or a page shown inside another site), the dialog explains what to do, and offers the whole studio as one HTML file that opens from disk with no internet.
  • After you save an export, a small note may ask whether you would like to say thanks (a coffee, a star, or sharing the link). It shows after the first save and then now and again, and never again once you tick Don’t show this again or use one of its links.
  • Your edits are kept in this browser between visits.

Command line and AI agents

The cli folder in the download has a command-line tool. It reads the compiler straight out of studio.html, so it can never disagree with the studio. It needs Node 18 or later: run npm install in the folder once.

CommandDoes
node gearscript.js check car/ --jsonCompiles and lists problems with codes, lines and fixes. The exit code is 1 when there are errors.
node gearscript.js check car.gscript --fixApplies the sure fixes problems carry, round after round until none are left: comments written with #, other names for a property (position, colour, rotation), easings such as easeInOut, ignored properties, true/no for on/off settings. Prints each change.
node gearscript.js check car.gscript --fix --suggestionsAlso applies the guesses (Did you mean …?, a value back in its range), after the sure fixes in each round, like the studio's Fix as much as possible. Stops when a round changes nothing, when the text comes back to one it had, or after 10 rounds.
node gearscript.js schemaPrints the language as JSON (shapes, properties, anchors, values) for editors and AI tools; also docs/gearscript-schema.json and the MCP tool gearscript_schema.
node gearscript.js sample car/ --at 0,2 --points FL.centerSolves the motion and prints positions, rotations and anchor points as JSON, so you can check the motion with numbers
node gearscript.js render car/ -o car.gifRenders the GIF in a headless browser (needs Playwright)
node gearscript.js eval tasks.json answers/Scores AI answers against the test set in evals/
node gearscript.js mcpRuns as an MCP server with check, sample, render and spec tools

A project is a folder holding .scene, .style and .motion files, or a saved .gscript.json. node gearscript.js examples out/ writes every studio example as a folder to start from.

The Prompt AI button

In the studio, Prompt AI copies a short version of the syntax as instructions any chat assistant can follow. The reply format follows your view: in the three-file view it asks for three code blocks (scene, style, motion); in the one-file view, for a single block with @scene, @style and @motion sections, so the reply pastes straight back. The panel shows which format it will ask for and lets you switch. Type what you want to animate, tick Include my current code if it should edit your project, and paste the result into the assistant. It replies with three code blocks: paste each into its tab. node gearscript.js prompt prints the same text (add --single for the one-file format).

The dialog can add your current code, the problems (ticked when there are any, so the AI fixes them first), and where every part is at the current time. The prompt carries the rules for narration and chapters only when your request or your code uses them, and ends with a short checklist of the slips AI replies make most. In a project with chapters, Ask about: This chapter sends @common and the chapter on screen only, and asks for that chapter back; the reply is merged by chapter name, so the others stay as they are.

AI on this site

Where the studio runs with a GearScript server that has an AI key, the AI panel also has Generate: it sends the same prompt to the AI and puts the answer straight into the editor, and Undo the AI change puts your project back. When the code has errors, Fix with AI appears in the Problems panel and sends the errors with the code. Each visitor has a daily number of requests. Without the server, Copy prompt works with any assistant.

Using GearScript from an AI agent

  1. Give the agent SPEC.md: the whole language on a few pages.
  2. Connect the MCP server, or let the agent run the command-line tool.
  3. The agent writes the files, calls check until there are no errors, calls sample to confirm the motion, then render.

The evals folder holds 16 tasks with prompts, numeric checks and reference answers. Use it to measure how often a model gets a mechanism right first time.

Current limits

What language 1 does not do, so you know before you start:

  • No forces or collisions. Motion is described, not simulated: there is no mass, gravity unless you write it, bouncing or friction.
  • Accelerations are constant within a block. Use several blocks, or a change between speeds, for anything else.
  • Loops are solved, not simulated. Linkages with pins work, including four-bars and slider-cranks. Ball joints, cams and gears inside a loop do not yet.
  • roll assumes straight-line travel on level ground.
  • Solid functions (signed-distance solids) and boolean operations are not in yet. Curves are.
  • Motion rules move whole parts, not individual group members. Use a hinge or a separate part instead.
  • Rendering is clean and simple: soft shadows, metals, glass and light from luminous parts, but no image textures.
  • Exploded views are for showing: joints and the camera follow the assembled positions, and trails do not explode.
  • Overlap warnings are sampled at up to 24 moments, so a very fast pass-through between samples can be missed.