Diagram DSL reference

The Diagram DSL builds physics and chemistry figures from relations, not coordinates. Declare objects with their physical parameters, then constrain them — the engine solves placement, rotation, and scaling. Open the DSL panel in the editor (toolbar ▸ DSL) to render scenes live and insert them as a single group.

How it works

A constraint a.anchor = b.anchor moves element a so its anchor coincides with the reference — the right-hand element stays put. a.prop = expr assigns scalars (numbers, arithmetic on el.anchor.x/.y); a.anchor.x = b.anchor.x aligns one axis without contact. Line, rope and vector endpoints stretch instead of translate. Constraints run in multiple passes — order doesn't matter — and dependents re-route.

canvas 800x500 grid 8x8

incline ramp1 x=100 y_base=380 angle=30 run=260
block m1 on_incline=ramp1 at=0.55 w=70 h=46
pulley p1
p1.handle.endpoint = ramp1.tip      # strut pivots at the tip
rope r1 from=m1.top to=m2.top through=p1
vector N on=m1 preset=normal len=56

Elements

canvas
surface
incline / ramp
block
pulley
rope
line
vector
label
angle_arc
lens
mirror
prism
mol
lewis
spring
wave

# canvas

canvas WxH [grid CxR]

Canvas size + optional grid for cell:/region: anchors

params
WxHpixel size, e.g. 800x500
grid=CxRcolumns x rows for canvas.cell:c:r anchors
anchors
cell:c:rcentre of grid cell (c,r)
cell_tl:c:rtop-left corner of grid cell
region:nametop_left/top/top_right/left/center/right/bottom_left/bottom/bottom_right

# surface

surface id x=.. y=.. [w=400] [h=14]

Horizontal surface / tabletop / floor with hatch below

params
x,ytop-left corner
wwidth
hthickness
anchors
a, btop-left and top-right of the top edge
top/bottom/left/rightedge midpoints
edge_left / edge_rightends of the top edge
t:fracfraction along the top edge
v:i, seg:i:tvertex / edge fraction (polygon family)

# incline / ramp

incline id x=.. y_base=.. angle=.. [run=260]

Ramp = three constituent lines — inclined_line, horizontal_line, vertical_line — each with full line-family anchors. Rises to the right at `angle` degrees

params
x,y_basebottom-left corner
angleslope angle (deg)
runhorizontal run (px)
arc=0hide the automatic angle arc at the slope corner
anchors
apex / tiptop of the slope
base_l / base_rleft and right base vertices
inclined_linethe slope line — sub-refs: .t:0.3 / .t1_10 / .t1:10
horizontal_line / base_linethe bottom line
vertical_linethe back edge
<line>_middle / <line>_k_n_pointedge divisions on any line
slope:t / vertical:t / base:tfractions along each line
normal / slope_diroutward normal / up-slope unit direction
orientationconstraint: =vertical|horizontal|deg sets angle

# block

block id [on|on_incline|hang|x+y] [w=60] [h=45] [label=..]

Rectangular body; place on a surface, incline, pulley, rope, or free

params
on=elrest on element (surface id)
on_incline=elrest on incline; at=0..1 along slope
hang=el [drop=90] [side=right]hang below a pulley via rope
hang_pulley=el [gap=3]directly under a pulley
x,yfree placement (top-left)
w,hsize
labelcentred text
anchors
centercentre of mass
contactcontact-face midpoint
up_face / down_faceface centres on incline
top/bottom/left/right[:t]edge midpoints / fractions (rotated frame)
tl/tr/bl/brcorners (rotated frame)
left_edge_middle / top_edge_3_10edge family anchors
width / height / *_sizescalar props

# pulley

pulley id [mount=.. of=el] [r=16] | pulley id

Circle pulley; mounted on edge/tip/block or free-standing

params
mountedge-right | edge-left | tip | block | above
of=elelement to mount on
rradius
x,yfree centre (mount=above)
gapblock-mount drop
anchors
centerhub centre
mountmounting point
tangent_h / tangent_v / tangent_v_lnamed tangents
top / bottomrim extremes
rim:degrim point at angle (0=right, +down)
clock:Nclock position 1..12
Noclock / Ndegsame, named
arc:t / circumference:tpoint at fraction of the rim
radius / diameterscalar props
parts
handle / strut / rodmounting rod from centre — p1.handle.endpoint=ramp1.tip pivots at the tip; p1.handle.slope=210, .len=40 aim it; the pulley rides the far end

# rope

rope id from=a.b to=c.d through=pulley1[.over],pulley2[.under]

Rope routed through tangent points around pulleys (polyline → line family)

params
from / to{el.anchor} | {point:x,y} — endpoints
throughpulley list, optional .over/.under wrap
pulley=ellegacy single hop
orientation=verticalstraight drop from the side tangent
anchors
start / endendpoints
left_endpoint / right_endpointendpoints by x (connectable)
t:fracarc-length fraction along the whole rope
seg:i:tfraction along i-th straight leg
tangent:itangent point on i-th pulley
lengthtotal straight-leg length (scalar)
a / b / midline family

# line

line id [from=a.b] [to=c.d] [arrow=1] [sw=2] | line a, b

Straight segment; endpoints stretchable via constraints

params
from / topoint refs (el.anchor or x,y)
x1,y1,x2,y2 or x,yliteral endpoints
arrow=1arrowhead at the far end
orientationvertical | horizontal
anchors
a / b / midendpoints + midpoint
start / endsame as a/b
left_endpoint / right_endpointby x — connectable
t:fracfraction along the line
lengthscalar

# vector

vector id on=el [preset=..|angle=..|toward=el|along_rope=el] [len=60] [label=..]

Force/velocity arrow anchored to a body

params
on=elbody it attaches to (anchor=.. picks the point)
presetgravity | normal | friction_up | friction_down | along_up | along_down | up
angle=degexplicit direction (0=right,-90=up)
toward=elpoint at another element
along_rope=eltension direction from a rope
from/toexplicit endpoints
lenlength
labeltip label
anchors
origin / tail / starttail point
tip / endarrowhead tip
midmidpoint
t:fracfraction along the arrow
left_endpoint / right_endpoint / lengthline family + scalar

# label

label id pos=a.b|target=el text=.. [size=14]

Text annotation, free or attached to an element

params
pospoint ref
target=elcentre under element bbox
textcontent
sizefont size
anchors
centertext centre
top/bottom/…bbox names

# angle_arc

angle_arc id at=a.b from_deg=.. to_deg=.. [r=24] [label=..]

Angle marker between two ray directions at a vertex

params
atvertex point ref
from_deg / to_degray directions (deg)
rarc radius
labele.g. "30°"
anchors
center / cvertex
radiusscalar

# lens

lens id cx=.. cy=.. [f=110] [u=180] [h_obj=70] [lens_h=170] [convex=1]

Closed-form thin-lens ray diagram (image position computed)

params
cx,cylens centre on axis
ffocal length (px); convex=0 → diverging
uobject distance
h_objobject height
lens_hlens body height
ray_end / axis_endright extent
anchors
oc / poleoptical centre
F_left / F_right / 2F_*focal points
obj_top / img_toparrow tips
top_edge / bottom_edgelens body ends
axis:t / lens:t / rayN:tparametric line points

# mirror

mirror id cx=.. cy=.. [R=150] [u=240] [h_obj=80] [half_deg=40] [convex=1]

Closed-form mirror ray diagram — concave (default) or convex=1

params
cx,cypole (vertex)
Rradius of curvature (F = R/2)
uobject distance
h_objobject height
half_degarc half-angle
convex=1convex mirror — C/F virtual behind, diverging rays, upright image
anchors
pole / C / Fvertex / centre of curvature / focus
obj_top / img_toparrow tips
axis:t / rim:deg / rayN:tparametric points

# prism

prism id cx=.. base_y=.. [A=60] [mu=1.5] [i=45] [size=200]

Closed-form refraction through a triangular prism (Snell)

params
Aapex angle (deg)
murefractive index
iincidence angle (deg)
sizetriangle size (px)
cx,base_ycentre x / base line y
anchors
apex / base_l / base_rtriangle vertices (A/B/C)
Q / Rincidence / emergence points
Vdeviation vertex
n1 / n2face normals
AB:t / AC:t / BC:t / incident / inside / emergentparametric

# mol

mol id mol=FORMULA [pos=..] [r=46] [style=ballandstick|stick|spacefill]

Molecule placeholder — sphere glyph in the SVG; the real 3D structure shows in the 3D viewer (3D toggle in the DSL panel). FORMULA must be in the built molecule library (CH4, H2O, NaCl, H2SO4, KMnO4…)

params
mol / formulamolecule key from the library
pos / centerplacement
rplaceholder radius
style3Dmol style: ballandstick | stick | spacefill
anchors
center / top / bottom / left / rightsphere anchors — connectable
rim:degrim point at angle
radiusscalar

# lewis

lewis id mol=FORMULA [pos=..] [size=90]

Lewis structure — 2D dot-and-bond diagram (lone pairs, bond order, formal charges) rendered from the molecule library. For bonding pedagogy; use mol= for the 3D view

params
mol / formulamolecule key from the library
pos / centerplacement
sizehalf-extent (px)
anchors
center / top / bottom / left / rightbbox anchors

# spring

spring id [from=a.b] [to=c.d] [coils=7] [amp=10] [length=..]

Coil spring between two points — full line-family element

params
from / topoint refs — ends stretch via constraints
coilsnumber of coil turns
amphalf-width of the coil (px)
lengthrest length on the free end
orientationvertical | horizontal
swstroke width
anchors
a / b / midendpoints + midpoint
t:fracfraction along the axis
left_endpoint / right_endpoint / lengthline family + scalar

# wave

wave id x=.. y=.. [w=320] [amp=30] [cycles=2]

Sine wave polyline with phase-aware anchors

params
x,yleft endpoint on axis
wwidth
ampamplitude
cyclesperiods
anchors
start / endfirst / last point
crest:i / trough:i / node:ii-th crest, trough, crossing
phase:degpoint at phase angle
t:fracfraction along x extent

Rules

Placement guide

Idioms for positioning — pins, alignment, gaps, rotations, stretching:

Pin direction: lhs moves

a.anchor = b.anchor      # moves a so its anchor lands on b's anchor
# the rhs never moves — pin the element that should TRAVEL
m1.top = s.top            # block m1 lands its top edge on surface s
# to move b instead, swap sides: b.top = a.top

Seat an edge on a line (2 pins)

# two pins on the SAME edge family rigid-seat the element:
m1.bottom_edge_1_10 = ramp1.inclined_line_3_10
m1.bottom_edge_8_10 = ramp1.inclined_line_4_10
# the solver picks rotation <=90deg — body stays outside the ramp
# prefer on_incline= when available; 2 pins = manual seat

Align without touching (axis pins)

m2.center.x = m1.center.x     # same x — stacks vertically
m2.top.y = m1.bottom.y        # level the edges — no contact
p1.center.x = ceiling.bottom.x  # centre under a point

Gaps and offsets

m1.right+20 = m2.left        # 20px gap — offset follows the
# anchor direction: right->+x, left->-x, top->-y, bottom->+y
m1.top+15 = s.top             # 15px below the surface line

Point offsets

label l pos=m1.top+(0,-10) text="m1"   # 10px above the anchor
# works in from/to/pins:  v1.origin = m1.center+(30,-40)

Endpoint stretch (lines)

# line/rope/vector endpoints move instead of translating:
l.left = m1.center           # left end sticks to the block centre
l.right = p1.9oclock         # right end on the pulley rim
frame.slope = 30             # direction pinned; endpoint sets extent
frame.length = 200           # free end sized

Rotation + slope

m1.rot = 15                  # rotate about own centre (deg)
ramp2.orientation = 40       # incline angle via constraint
l.slope = -30                # line direction, clockwise-positive
l.orientation = vertical     # snap to axis

Mirror

incline r2 angle=40 mirror=h     # flips the SHAPE; anchors follow
# mirror changes geometry only — normals/angles/signs stay consistent

Scalar props + expressions

p1.radius = m1.width / 2         # reads assigned value — order matters:
m1.width = 60                # assign plain numbers FIRST
m2.center.x = ramp1.apex.x - m2.width/2 - 10   # coords inside arithmetic
# stable props only: width/height/radius/angle/slope/length

Rope over a pulley

rope r1 from=m1.top to=m2.top through=p1
# routes over the rim — never pin both ends to rim points

Strut / handle parts

p1.handle.endpoint = ramp1.tip   # rod pivots at the tip
p1.handle.slope = 270          # direction (270 = straight up)
p1.handle.len = 25             # rod length; pulley rides the far end
# also strut/rod/arm/stem/leg/mount; X.<part> is a line element

Vectors

vector N  on=m1 preset=normal      len=56   # normal on incline
vector W  on=m1 preset=gravity     len=70   # straight down
vector Fr on=m1 preset=friction_up len=44   # along slope up
vector T  on=m2 along_rope=r1     len=64   # tension from a rope
vector F  on=m1 anchor=m1.left preset=up     # anchor picks the origin

Grid + regions

m1.center = canvas.cell:2:2          # grid cell centre (0-indexed)
label t pos=canvas.region:bottom text="FBD"   # named canvas regions

Canonical recipes

Pulley under a support

surface ceiling x=150 y=50 w=200 h=16
pulley p1 mount=strut of=ceiling anchor=ceiling.bottom angle=90 len=70
# rod from ceiling.bottom straight down 70px, pulley at its end

Atwood: two masses over one pulley

rope r1 from=m1.top to=m2.top through=p1
m1.center.x = p1.9oclock.x      # hang left mass off the left tangent
m2.center.x = p1.3oclock.x      # right mass off the right tangent
# through=p1 routes the rope over the top — no manual endpoint wiring

Block on an incline + forces

block m1 on_incline=ramp1 at=0.55 w=70 h=46
vector N  on=m1 preset=normal      len=56
vector W  on=m1 preset=gravity     len=70
vector Fr on=m1 preset=friction_up len=44

Free pulley at the ramp tip

pulley p1
p1.handle.endpoint = ramp1.tip    # strut pivots at the tip
p1.handle.slope    = 210          # rod direction (deg, clockwise)
p1.handle.len      = 40
# the pulley rides the rod's far end

Back-to-back inclines (Λ Atwood)

incline ramp1 x=100 y_base=380 angle=30 run=240
incline ramp2 x=360 y_base=380 angle=40 run=200 mirror=h
ramp1.tip = ramp2.tip      # shared apex — run AUTOSCALES so the base
# stays grounded; pin the unmirrored ramp's tip, never the reverse
pulley p1
p1.handle.endpoint = ramp1.tip
p1.handle.slope = 270            # straight up
block m1 on_incline=ramp1 at=0.5
block m2 on_incline=ramp2 at=0.5
rope r1 from=m1.top to=m2.top through=p1

Common mistakes