Skip to content

Scenes as JSON

A scene is data. The scenario catalogue produces one, the playground loads one, and the Python package runs one. Exactly the same engine runs in every case, so a scene a teacher edits in the playground gives the same numbers in a student’s notebook.

In the playground, Scene as JSON → Download saves the current scenario with its parameters and textbook answers. Edit it in any text editor and Load file to run it. The engine validates the file and names the problem if it cannot build it: an unknown body id, a missing mass, an unsupported schema.

{
"schema": 1,
"title": "Block pulled by a hanging mass",
"world": { "gravity": [0, -9.8], "dt": 0.001 },
"bodies": [
{ "id": "table", "kind": "static", "shape": { "polygon": [[-3, -0.4], [3, -0.4], [3, 0], [-3, 0]] }, "friction": [0.2, 0.15] },
{ "id": "block", "kind": "rigid", "mass": 2, "shape": { "rectangle": [0.6, 0.4] }, "position": [0, 0.2] },
{ "id": "weight", "kind": "particle", "mass": 1, "shape": { "circle": 0.1 }, "position": [3.1, -1] }
],
"constraints": [
{ "rope": { "nodes": [
{ "attach": { "body": "block", "local": [0.3, 0] } },
{ "pulley": { "center": [3, 0.1], "radius": 0.1, "wrap": "clockwise" } },
{ "attach": { "body": "weight" } }
] } }
],
"focus": "block",
"answers": [ { "name": "Acceleration (m/s²)", "value": 2.0 } ]
}
FieldMeaning
idname used by forces, constraints, focus and the free-body diagram
kindparticle (no rotation), rigid, or static (never moves)
shape{"circle": r}, {"rectangle": [w, h]}, {"polygon": [[x, y], ...]}, {"halfplane": {"normal": [nx, ny], "offset": d}}
massrequired for particle and rigid
position, angle, velocity, angular_velocityinitial state; default zero. Polygon vertices may be given about any origin; the centre of mass becomes the body’s position
friction[static, kinetic]; default smooth
restitutiondefault 1 (elastic); the pair uses the smaller of the two bodies’ values
inertiaoverride the shape’s moment of inertia

{"applied": {"body", "force", "point"?, "from"?, "until"?}}, {"torque": {"body", "torque"}}, {"spring": {"a", "b", "stiffness", "rest_length"?}}, {"linear_drag": {"body", "coefficient"}}, {"quadratic_drag": {...}}, {"driving": {"body", "amplitude", "angular_frequency", "phase"?}}.

Anchors are {"fixed": [x, y]} or {"body": {"id": "...", "local": [x, y]}}.

{"pin": {"a", "b"}}, {"rod": {"a", "b", "length"?}}, {"slot": {"body", "origin", "direction", "local"?}}, {"fixed_angle": {"body", "angle"}}, and {"rope": {"nodes": [...], "length"?}} whose nodes are {"attach": {"body", "local"?}}, {"peg": [x, y]}, {"pulley": {"center", "radius", "wrap"}} for a massless pulley, or {"wheel": {"body", "radius", "wrap"}} for a rigid body the rope turns without slipping. A rope or rod without a length is taut at the start.

focus names the body the free-body diagram shows first; answers carries textbook values to show beside the simulation; view is {"center": [x, y], "half_height": h} for the camera; ignore_collisions lists body pairs that should not touch. Bodies joined by a pin or rod never collide with each other.

from soliton import mechanics
scene = mechanics.scenario("atwood", m1=2, m2=3)
scene.answers() # {'Acceleration (m/s²)': 1.96, 'Tension (N)': 23.52}
run = scene.run(until=2.0)
run.t, run.body("m2")["velocity"] # NumPy arrays, shape (N,) and (N, 2)
run.force("m2", "tension") # (N, 2)
run.energy()["dissipated"]
scene.save("atwood.json") # the file the playground loads
edited = mechanics.load("atwood.json")