Guide
Scene JSON for agents
How to write a scene: the conventions, a recipe, converting a layout, and every rejection with its fix
What it is
A scene is a JSON section of the run request that places every rack, tracker and shading object in 3D. This page is the path from a layout to a scene that validates and runs, written for a program or an agent producing the JSON. The field pages (scene, scene.types, scene.placements, scene.objects, scene.outlines, scene.frame) hold each key's detail.
The frame
Every scene uses one convention: x east, y north, z up, azimuths in degrees clockwise from true north, and lengths in one unit of your choice. The frame block is optional; when sent it must read exactly:
"frame": {"axes": {"x": "east", "y": "north", "z": "up"},
"azimuth_zero": "true_north", "azimuth_direction": "clockwise",
"lengths": "unitless"}
The recipe
- Pick one length unit, metres or feet, and use it for every length in the scene.
- Put the origin at or near the site: x east, y north, z up, azimuths in degrees clockwise from true north. The frame block states this and cannot say anything else.
- Write one device type per rack or tracker size:
kind(gftfixed rack orsatsingle-axis tracker),lengthalong the row or axis,widthacross it (the collector width),modulesandstringson one device. - Write one placement per device: the type, the centre
x,y,z(a tracker's axis midpoint at hub height, a rack's plane centre),azimuth_deg,tilt_degand, for a rack,roll_deg. - Add the objects that cast shade: a building as one or more
prism(a base polygon with a height per vertex), awallor atree_line(a polyline with a height per vertex; a tree line lets light through), acylinderfor a tree, a tank or a pole. POST /api/scene/validatewith the scene as the body. Fix each rejection (table below) and read the warnings; the response counts the placements, objects, modules and orientation bins.- Set the array section for the scene:
array.inter_row_shadingtrue for rows on the ground or on a flat roof, false for modules flush on a pitched roof; the tracker operation (rotation limits, backtracking, stow) inarraywhen the scene places trackers, which rooftop racking refuses; the stringing and the inverter sized for the scene's modules (the run takes the nameplate from them, not from the scene). Abifacialsection gives every orientation group its own rear side; leave it out on a roof and on east-west racking. POST /api/runwith the scene inscene. Readsummary.warnings(the pitch check, the module totals) andsummary.scene(the bins, the origin).POST /api/scene/day?date=MM-DDwith the same request returns one day solved: the sun, each bin's tracker rotation, the trackers' state (tracking: tracking, backtracking, flat (irradiance-optimised), best angle (irradiance-optimised) or stow, read from the first tracker bin) and each table's beam shaded fraction per timestep.
One setup per run
A run is one plant: one module, one inverter design and one racking geometry. For several roof planes, products or row spacings, make one run each and add the results.
- Keep the whole site's objects (the house, the neighbours, the trees) in every plane's run, so each plane sees the same shade.
- A scene whose placements face several ways runs as one plant: its orientations are blended by module count and the result is not split per orientation.
Starting from an example
GET /api/scene-examples lists five maintained scenes with the array settings each assumes (array_patch, a shallow merge into array) and notes; GET /api/scene-examples/{id} returns the scene body.
| Example | What it shows |
|---|---|
residential |
A gable-roof house with eight one-module placements flush on the south roof, a neighbouring house (two sloped prisms each) and two trees as cylinders; row-to-row shading off |
commercial |
A flat roof with three arrays of 15° rows in two row lengths, an HVAC unit splitting the middle row and rooftop units along the edges; row-to-row shading on |
ground_fixed |
515 fixed racks in three sizes at 25° on rolling ground, each centre 1.5 above it, in three blocks with irregular edges |
ground_tracker |
324 single-axis trackers in two lengths on rolling ground, each axis following the ground (tilted where it falls), in four blocks split by roads |
east_west |
42 east-west ridges of 40 modules (1,680 modules) with faces at 10° on flat ground, 5.4 apart, in a block with one ragged edge, a two-storey building to the south-east and a tree to the west; array.track_mode ewf, row-to-row shading on |
Start from the closest one: keep the frame block, replace the types, placements and objects, and merge its array_patch.
Placing devices from your own layout
A PVsyst variant. POST /api/parse-scene with the .VCx file (multipart file) returns the scene, an array_patch carrying the tracker operation and the import notes, which list the shapes it could not read. Merge the patch into array before running.
A rectangle per device (CAD drawing, GIS layer, KML polygon). For each device's rectangle in plan:
- Centre: the rectangle's centroid, as x east and y north from the origin.
- Length: the long side. For a tracker it lies along the axis;
azimuth_degis the bearing of the long side, 180 for a level north-south axis, or the bearing toward the lower end when the axis is tilted. Width: the short side, the tracker's plane at 0° rotation. - A fixed rack's short side in plan is its width times the cosine of its tilt: width = short side / cos(tilt).
azimuth_degis the bearing perpendicular to the long side toward which the plane faces (180 for a south-facing row north of the equator). - Height:
zis the hub height of a tracker or the plane centre of a rack, measured in the datum the whole scene shares. On sloping ground add the ground elevation under the device to its height above the ground; a tracker whose axis follows the ground tilts by the fall along its length (tilt_degpositive towardazimuth_deg). - One type per distinct length and width;
modulesfrom the drawing's module count per device.
Longitude and latitude (KML, KMZ, GeoJSON). Convert each point to east and north offsets from a reference point near the site (λ₀, φ₀) in metres: x = (λ − λ₀) · (π/180) · R · cos φ₀ and y = (φ − φ₀) · (π/180) · R, with R = 6 371 000 m. Within a few kilometres of the reference point the error is below 0.2 % of the distance. KML altitudes are absolute or relative to the ground (altitudeMode): a relative altitude is a height above the ground, so add the ground elevation under the point on sloping ground. Bearings from these offsets are true bearings.
Projected coordinates (a CAD drawing in UTM or a state plane grid). Subtract the site's projected coordinates so the numbers stay small, and keep the drawing's unit. Grid north is not true north: a bearing measured on the grid is a true bearing plus the grid convergence γ, the angle from true north to grid north (positive when grid north lies east of true north; for UTM, γ ≈ (λ − λ₀) · sin φ with λ₀ the zone's central meridian). Rotate each offset (x, y) to (x cos γ + y sin γ, −x sin γ + y cos γ), and add γ to each bearing read from the grid. At 1° of convergence, an unrotated layout faces every device 1° off.
The georeference. origin (latitude, longitude, elevation) records the reference point you used; the run rejects one more than 10 km from the site. The sun position, the weather and the elevation come from site alone, so set site to the system's location either way.
Rejections and their fixes
Rejections are 422 problems (application/problem+json) whose field names the offending key (types.0.kind from the validate endpoint, scene.types.0.kind from a run) and whose errors list every failure.
| Message (start) | Fix |
|---|---|
version: Input should be 2 |
Send "version": 2; earlier scene formats are not accepted. |
frame.…: Input should be … |
Send the frame block exactly as above, or leave it out. |
…: Extra inputs are not permitted |
Remove the key. A unit in a key name (length_m) is refused: lengths are unitless. |
scene.length_unit: the scene is unitless |
Delete it; every length is in one unit of your choice. |
scene.archetype: archetype was removed |
Delete archetype; mix gft and sat types freely. For a roof, set array.inter_row_shading to false. |
scene.types[i].kind: roof types were removed (also .roof) |
Model a roof array as a gft type placed on the roof plane (tilt = the roof pitch). |
scene.….clearance: clearance was removed |
Delete it: z places the device. |
scene.types[i].sat: the sat block was removed |
Delete it; set the rotation limits, backtracking and stow in array. |
types: List should have at least 1 item (or at most 10) |
Give 1 to 10 device types. |
types.i.kind: Input should be 'gft' or 'sat' |
Use gft for a fixed rack (a roof array too) or sat for a single-axis tracker. |
scene.types[i].id: duplicate id (also placements, objects) |
Make the ids unique, or leave placement ids out (they default to p1, p2, …). |
scene.placements[i].type: unknown type |
Name a type id that exists in types. |
scene.placements[i].tilt_deg: must be within [0, 90] (a rack) or [-45, 45] (a tracker axis); …azimuth_deg or …roll_deg: Input should be less than or equal to … |
Racks tilt 0–90°, tracker axes −45–45°; azimuths 0–360°; rolls −45–45°. |
scene.….x: must be finite |
Replace NaN or infinite numbers. |
…width: Input should be greater than 0 (also length, a cylinder's height) |
A type's length and width and a cylinder's height are positive; modules and strings are at least 1. |
objects.i: Input tag '…' found using 'kind' does not match |
Use prism, wall, tree_line or cylinder. |
…vertices: List should have at least 3 items |
A prism or an outline needs 3 vertices, a wall or tree line 2. |
…vertices.k: List should have at least 4 items |
Object vertices are [x, y, z, h]: a base point and the height above it. Outline vertices are [x, y, z]. |
scene.objects[i].vertices: h must be positive on at least one vertex |
Give the object a height. |
scene.objects[i].radius: give exactly one of radius or diameter |
Keep one of the two. |
objects.i.wall.transmittance: Extra inputs are not permitted |
A wall is opaque; use a tree_line for a screen that lets light through. |
scene: sat (tracker) types are not available with rooftop racking |
Set array.track_mode to fixed or single_axis, or make the types gft. |
scene: ewf types cannot share a scene with gft or sat types |
Put the east-west ridges in a scene of their own. |
scene: ewf types need array.track_mode ewf, and ewf racking needs ewf types |
Set array.track_mode to ewf for ewf types; for gft and sat types use fixed, single_axis or rooftop racking. |
scene.types[i].modules: must be even for an ewf type (half on each face) (also strings) |
Count both faces of the ridge: an even number. |
scene.origin: is N km from the site |
Move origin within 10 km of site, or leave it out. |
scene.bins: N orientation bins exceed the limit of 400 |
Round the tilts and azimuths: placements within 1° of tilt and 2.5° of azimuth share a bin. |
Warnings
Warnings do not stop the run. POST /api/scene/validate returns the scene's own; POST /api/run adds the ones that need the array section to summary.warnings.
| Warning | What to check |
|---|---|
types T: no placement uses it |
Delete the type or place it. |
placements[i] P: roll_deg is ignored on a sat type |
A tracker rolls about its own axis; delete the roll. |
objects[i] O: non-convex prism, shaded as its convex hull |
Split an L-shaped or U-shaped building into convex prisms. |
placements A and B overlap in plan (N pairs in all) |
Two devices occupy the same ground: a duplicated row or a wrong centre. An overlap still runs and shades twice. |
pitch from the placements differs from width / GCR by up to N % |
The row-to-row model uses array.gcr; set it to width / pitch of your layout. |
ridge spacing from the placements differs from 2 × width × cos(tilt) / GCR by up to N % |
On east-west racking the row-to-row model reads array.gcr as the two faces' footprint over the ridge spacing; set it to 2 × width × cos(tilt) / spacing of your layout. |
placements[i] P: roll_deg is ignored on an ewf type |
A ridge's faces take the placement's tilt only; delete the roll. |
scene tables carry N modules; the racking section declares M |
The scene's module counts weight the blend; the stringing and inverter set the nameplate. Size them to the scene's total. |
Limits
- Up to 10 device types, 40 000 placements, 2 000 objects and 200 outlines per scene; up to 400 orientation bins in a run.
- The origin within 10 km of the site.
- The day review (
POST /api/scene/day) returns each table's beam shaded fraction up to 2 000 tables; above that it returns the field's metrics and the trackers' rotations only. - JSON Schemas of the scene and of the whole request:
GET /api/schema/scene.jsonandGET /api/schema/run.json.
Run time
- A scene run's time grows with its placements and with the objects that can reach them. Each object piece is tested against each table from each sun position it could shade from; pieces too low or too far to reach a table are skipped.
- On 100 fixed racks each object adds about 0.17 s of compute (the October 2026 cost scan: 0 objects 6.7 s, 100 objects 27.5 s, 1,000 objects 181 s).
- Fewer, larger objects run faster than many small ones. From a surface model, merge neighbouring cells of similar height into one prism, model a tree as one cylinder and leave out cells lower than the modules' lowest edge.
References
- ISO 19111:2019, Geographic information — referencing by coordinates
- Snyder 1987, Map Projections: A Working Manual, U.S. Geological Survey Professional Paper 1395
- Open Geospatial Consortium 2008, OGC KML, version 2.2.0 (OGC 07-147r2)