Skip to content

Commit c986ced

Browse files
marcelgruberMarcel Gruber
andauthored
docs(mcp): document plan↔world coordinate convention (pascalorg#356)
Adds a Coordinate conventions section to packages/mcp/README.md and a 71-node MCP demo scene that exercises every claim it makes. Closes pascalorg#337. Covers: right-handed scene with X/Z ground plane and Y up; metres for lengths and radians for rotations as Euler [x, y, z]; the [x, z] → (x, y, z) plan-to-world mapping with no sign flip in the stored contract; level/building-local framing (world only under identity transform); rotation-not-reflection caveat for the 2-D plan panel and the iso-default top-down azimuth offset; a worked 30° rotated slab example; and the wall-local-metres trap for door/window/place_item coordinates. Companion: examples/coordinate-conventions-demo.{json,md} with a reference compass at the origin and Demos A/B/C/D illustrating axis-aligned baseline, the rotated example, and the page-intent vs world-result L pair. Co-authored-by: Marcel Gruber <marcel@grubertech.com>
1 parent 2694bcf commit c986ced

4 files changed

Lines changed: 2263 additions & 0 deletions

File tree

packages/mcp/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,76 @@ console.log(scene)
227227
See [`examples/embed-in-agent.ts`](./examples/embed-in-agent.ts) for a
228228
compilable version.
229229

230+
## Coordinate conventions
231+
232+
Pascal is a **right-handed** scene where **X and Z form the ground plane and Y
233+
is up**. Lengths are in **metres**; rotations are **radians**, stored as Euler
234+
`[x, y, z]` tuples.
235+
236+
**Plan → world.** Every 2-D point you pass is a level/building-local
237+
ground-plane coordinate
238+
`[x, z]` — this includes `wall.start` / `wall.end` and the `polygon` / `holes`
239+
arrays of `slab`, `zone`, and `ceiling`. With the default identity building
240+
transform, it appears in world space as:
241+
242+
```
243+
[x, z] → (x, y, z) // the 2nd component is world Z (depth), not "up"
244+
```
245+
246+
There is no sign flip in the stored convention: tooling consumes the second
247+
component as world Z directly. The vertical `y` starts from the owning level's
248+
stacked height as computed by the level system from accumulated level heights,
249+
plus the element's own height; slabs additionally carry an absolute
250+
`elevation`.
251+
252+
**Heads-up when you compute coordinates outside the editor.** Pascal's
253+
viewports apply their own rotations on top of the world axes: the 2-D plan
254+
panel wraps its content in a 90° rotation (`FLOORPLAN_VIEW_ROTATION_DEG`), and
255+
the 3-D "top-down" snap preserves the camera's current azimuth, so when invoked
256+
from the iso default position, world and screen axes are offset by ~45° until
257+
you orbit to an axis-aligned view. So a layout authored as if
258+
*"Y = north, viewed top-down"* — common in land surveys, north-up site plans,
259+
and 2-D plotting libraries — will arrive **rotated** relative to its source
260+
when viewed in Pascal (and possibly further reflected, depending on which
261+
viewport and camera state you're in). The editor's own 2-D and 3-D tools are
262+
internally consistent with their stored coordinates, so this only affects
263+
geometry authored programmatically. To verify orientation before trusting
264+
externally-computed coordinates, place a scaled guide image at known anchor
265+
points and check alignment; apply whatever rotation (or reflection) your
266+
authoring side needs to match.
267+
268+
A worked demonstration of all of this — axis-aligned baseline, the rotated
269+
30° example below, and a paired "page-intent vs world-result" L for the
270+
external-coordinate gotcha — lives in
271+
[`examples/coordinate-conventions-demo.md`](./examples/coordinate-conventions-demo.md)
272+
and [`examples/coordinate-conventions-demo.json`](./examples/coordinate-conventions-demo.json).
273+
Load the JSON with
274+
`pascal-mcp --stdio --scene examples/coordinate-conventions-demo.json`.
275+
276+
**Example — a 6 × 4 m slab rotated 30° about its first corner** (coordinates
277+
rounded to 3 dp; sides ≈ 6 m / 4 m; not axis-aligned, so the mapping is
278+
actually exercised):
279+
280+
```json
281+
{
282+
"op": "create",
283+
"parentId": "<levelId>",
284+
"node": {
285+
"type": "slab",
286+
"elevation": 0.0,
287+
"polygon": [[0, 0], [5.196, 3.0], [3.196, 6.464], [-2.0, 3.464]]
288+
}
289+
}
290+
```
291+
292+
This lands flat on the ground (Y = 0), about 6 m along a heading 30° off the +X
293+
axis and 4 m along its perpendicular — i.e. occupying world (x, z) directly.
294+
295+
One separate gotcha: wall-attached coordinates are wall-local, not plan
296+
coordinates. Stored door/window `position[0]`, and `place_item` `position[0]`
297+
when the target is a wall, are metres along the wall; wall-attached rotations
298+
are wall-local too.
299+
230300
## Tools
231301

232302
All tools validate their inputs and outputs with Zod. Mutation tools are

0 commit comments

Comments
 (0)