@vention/vention-skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +15 -0
- package/README.md +61 -0
- package/mcp.json +9 -0
- package/package.json +19 -0
- package/plugin.json +12 -0
- package/skills/vention-design/SKILL.md +522 -0
- package/skills/vention-machine-logic/SKILL.md +256 -0
- package/skills/vention-machine-logic/examples/conveyor-with-sensor/main.py +58 -0
- package/skills/vention-machine-logic/examples/homing-and-indexing/main.py +51 -0
- package/skills/vention-machine-logic/examples/parallel-conveyor-with-recipe/main.py +118 -0
- package/skills/vention-machine-logic/examples/parallel-conveyor-with-recipe/requirements.txt +1 -0
- package/skills/vention-machine-logic/examples/pick-and-place-state-machine/main.py +159 -0
- package/skills/vention-machine-logic/examples/pick-and-place-state-machine/requirements.txt +2 -0
- package/skills/vention-machine-logic/scripts/library-readme.py +233 -0
- package/skills/vention-machine-logic/scripts/vention-docs.py +176 -0
- package/skills/vention-machine-logic-hmi/SKILL.md +210 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/buf.gen.yaml +5 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/index.html +11 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/package.json +34 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/app.tsx +152 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/client.ts +17 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/main.tsx +16 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/pages/logs-page.tsx +52 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/pages/recipes-page.tsx +139 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/pages/run-page.tsx +65 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/station.ts +34 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/src/use-stream.ts +54 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/customui/tsconfig.json +24 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/models.py +13 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/project.json +6 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/proto/app.proto +192 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/requirements.txt +5 -0
- package/skills/vention-machine-logic-hmi/examples/indexing-station-hmi/server.py +242 -0
- package/skills/vention-monitoring/SKILL.md +82 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vention-design
|
|
3
|
+
description: Create, build and edit Vention machine designs and design graphs for agentic layout. Use when the user wants to start a new design, lay out an automation cell, add or reposition components in a Vention design, generate a machine layout from a description, add or place a floor plan image under a design, list a design's versions, or inspect an existing design graph. Trigger on MachineBuilder designs, design graphs, assembly graphs, extrusions, actuators, robot mounting, cell footprints, floor plans, or a machine-builder URL.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
author: Vention
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Vention Design Graphs
|
|
11
|
+
|
|
12
|
+
Build and modify Vention designs through the Vention MCP server.
|
|
13
|
+
|
|
14
|
+
## Before you start
|
|
15
|
+
|
|
16
|
+
Confirm the Vention MCP server is connected by calling `whoami`. If it fails, tell the
|
|
17
|
+
user to connect it and stop.
|
|
18
|
+
|
|
19
|
+
Everywhere below, `design` accepts either a numeric design id or a MachineBuilder URL
|
|
20
|
+
(`https://vention.com/machine-builder/<id>`). A version segment in a URL is ignored by
|
|
21
|
+
the save tool; `parent_version` is the only version input. Pass version and id values as
|
|
22
|
+
strings (`parent_version: "0"`, `floor_plan_id: "401"`) — that is the verified form.
|
|
23
|
+
|
|
24
|
+
The available design tools are:
|
|
25
|
+
|
|
26
|
+
| Tool | What it does |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `design_categories_list()` | The category codes `design_create` accepts |
|
|
29
|
+
| `design_create(name, category_code, ...)` | Create a new, empty design; returns `id`, `name`, `url` |
|
|
30
|
+
| `design_versions_list(design)` | Versions newest-first with `num_parts`, `md5`, and the version's floor plan — no graph download |
|
|
31
|
+
| `design_graph_download_url(design, graph_version?)` | Presigned GET for a version's graph; omit `graph_version` for current |
|
|
32
|
+
| `design_graph_upload_create(design)` | Presigned POST for uploading a new graph |
|
|
33
|
+
| `design_version_save(design, upload_key, parent_version, floor_plan_id?)` | Commit an uploaded graph as a new version |
|
|
34
|
+
| `design_floor_plan_upload_create(design, content_type)` | Presigned POST for a floor plan image |
|
|
35
|
+
| `design_floor_plan_create(design, metadata, file_upload_key\|image_id)` | Attach the uploaded image to the design as a floor plan |
|
|
36
|
+
|
|
37
|
+
The save and floor-plan tools are marked **EXPERIMENTAL — provided without support**,
|
|
38
|
+
and they change real designs. State plainly what a write will change before you make it.
|
|
39
|
+
Get the user's explicit go-ahead before the first write into any design you did not
|
|
40
|
+
create in this session; after that, keep narrating each write without re-asking.
|
|
41
|
+
|
|
42
|
+
There is no tool to list the user's designs, rename or delete one, or delete a floor
|
|
43
|
+
plan. If you need an existing design, ask for its id or URL — never guess one.
|
|
44
|
+
|
|
45
|
+
## Creating a design
|
|
46
|
+
|
|
47
|
+
`design_create(name, category_code, description?, tags?, visibility?)` makes a new,
|
|
48
|
+
empty design in the user's account and returns its `id`, `name` and `url`.
|
|
49
|
+
|
|
50
|
+
- `category_code` is required and must come from `design_categories_list`. Codes come in
|
|
51
|
+
two shapes: two letters for a top-level category (`MF` Manufacturing, `AS` Assembly,
|
|
52
|
+
`MH` Material Handling, `PZ` Packaging, `ME` Other Equipment), and two-plus-two for a
|
|
53
|
+
subcategory (`MF-RC` Machine Tending, `PZ-CB` Cobot Palletizer, `MH-CV` Conveyors, and
|
|
54
|
+
so on). Those shapes are just the pattern — the codes above are real, but the list is
|
|
55
|
+
longer and can change. **Never guess a code.** Call `design_categories_list` and match
|
|
56
|
+
the user's words; if several plausibly match, or the user named no category, ask.
|
|
57
|
+
- `visibility` is `TEAM` (the default), `ORGANIZATION` or `PRIVATE`. The default shares
|
|
58
|
+
the design with the user's team — if that's not obviously what they want, ask before
|
|
59
|
+
creating rather than after.
|
|
60
|
+
- A created design starts with **no graph and no versions at all**:
|
|
61
|
+
`design_versions_list` returns `[]` and `design_graph_download_url` errors with
|
|
62
|
+
`NotFound: Design has no assembly graph`. Both are expected, not failures.
|
|
63
|
+
|
|
64
|
+
### Saving the first version
|
|
65
|
+
|
|
66
|
+
There is no parent version to start from, so pass **`parent_version: "0"`** on the
|
|
67
|
+
first save into a freshly created design. That is verified to work and produces version
|
|
68
|
+
`1`. You must supply a complete graph yourself, including the `config` vertex — an
|
|
69
|
+
API-created design does not come with one (see the note under the schema below).
|
|
70
|
+
|
|
71
|
+
## Understanding the design graph
|
|
72
|
+
|
|
73
|
+
A Vention design is stored as an assembly graph: a set of parts (extrusions, plates,
|
|
74
|
+
actuators, robots, end effectors, loose hardware) plus the mating relationships
|
|
75
|
+
("edges") that position them relative to one another. Editing a design means
|
|
76
|
+
transforming that graph, not editing a 3D scene directly. There is no concept of an
|
|
77
|
+
instanced/referenced sub-assembly in the graph — everything is flattened to individual
|
|
78
|
+
parts, every time.
|
|
79
|
+
|
|
80
|
+
Graphs are multi-megabyte files. They move by reference, never inline.
|
|
81
|
+
**Never read a graph into the conversation — download it and process it with code.**
|
|
82
|
+
|
|
83
|
+
### Schema (as observed; may evolve — treat as a map, not a spec)
|
|
84
|
+
|
|
85
|
+
Field names and shapes below come from reading real graphs, and the app may add or
|
|
86
|
+
change keys. What is dependable is the *structure* — vertices with ids, edges
|
|
87
|
+
referencing vertex ids, the `nextVID`/`nextEID` counters, and the column-major
|
|
88
|
+
`local_matrix`. Preserve keys you don't understand rather than dropping them.
|
|
89
|
+
|
|
90
|
+
Top level:
|
|
91
|
+
```
|
|
92
|
+
{
|
|
93
|
+
"format_version": <int>, // schema version; can differ design-to-design
|
|
94
|
+
"format_version_initial": <int>,
|
|
95
|
+
"label": <string>,
|
|
96
|
+
"attributes": {
|
|
97
|
+
"design_id": <int>,
|
|
98
|
+
"design_version": null,
|
|
99
|
+
"design_bbox": {"min": {x,y,z}, "max": {x,y,z}},
|
|
100
|
+
"cloned_from": {"design_id": ..., "graph_version": ...}, // optional
|
|
101
|
+
"measurements": {"type": "face", "visible": true},
|
|
102
|
+
"feedback": {...}, "cut_to_length": {...} // optional
|
|
103
|
+
},
|
|
104
|
+
"vertices_by_index": [ <vertex>, ... ],
|
|
105
|
+
"edges": { "by_id": { "<edge_id>": <edge>, ... } },
|
|
106
|
+
"nextVID": <int>, // next free vertex id — must be > every vertex id used
|
|
107
|
+
"nextEID": <int> // next free edge id — must be > every edge id used
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Vertex (one part/node):
|
|
112
|
+
```
|
|
113
|
+
{
|
|
114
|
+
"id": <int>,
|
|
115
|
+
"attributes": {
|
|
116
|
+
"part_number": <string>, // absent on the "config" vertex
|
|
117
|
+
"short_name": <string>,
|
|
118
|
+
"vertex_type": "part" | "imported_part" | "addon" | "panel" | "config",
|
|
119
|
+
"local_matrix": {"m": [ ...16 floats... ]}, // absent on some addon parts
|
|
120
|
+
"opacity": <number>, "visibility": <bool>, "dofs": [...], "hoops_id": <int>
|
|
121
|
+
},
|
|
122
|
+
"edges": [ <edge_id>, ... ] // ids of every edge incident on this vertex
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
- `local_matrix.m` is a **column-major 4x4** matrix flattened to 16 values; translation
|
|
126
|
+
is at indices 12 (x), 13 (y), 14 (z). To rigidly move a part, only touch those three.
|
|
127
|
+
- **Y is Vention's vertical axis — the floor is Y=0.** Not Z. (HOOPS/Three.js viewers
|
|
128
|
+
default to Y-up, and this matches it.) Confirmed the hard way: a merged design's
|
|
129
|
+
bbox looked entirely Z-negative, which read as "the whole thing is below the floor
|
|
130
|
+
on Z" — but the actual symptom (a design sitting sunk about halfway into the ground)
|
|
131
|
+
came from its Y range straddling 0. Shifting Z did nothing for the sink; shifting Y
|
|
132
|
+
(`m[13]`) so the bbox's min Y lands at 0 fixed it. **Don't infer the vertical axis
|
|
133
|
+
from bbox magnitude — check Y first.**
|
|
134
|
+
|
|
135
|
+
### Coordinate and part reference
|
|
136
|
+
|
|
137
|
+
An extrusion's own local space runs its length along local Z (0 to L), with a
|
|
138
|
+
45x45mm cross-section (local X/Y ∈ [-22.5, 22.5]) for the common `ST-EXT-001-XXXX`
|
|
139
|
+
line — `XXXX` is the length in mm and must be a multiple of 45. Never invent a length
|
|
140
|
+
that isn't.
|
|
141
|
+
|
|
142
|
+
Connector strings follow `cma_TYPE|PART_NUMBER|GENDER|FACE_ID`. On extrusions:
|
|
143
|
+
`ca1`-`ca4`/`cb1`-`cb4` are the male end faces (near/far), `ba1`-`ba4`/`bb1`-`bb4` are
|
|
144
|
+
the female T-slots along the length (near/far). `edges.by_id[...].attributes.distance1`
|
|
145
|
+
is the position in mm along the female slot. Gussets (`ST-GP-003-0001`) expose male
|
|
146
|
+
faces `a1`/`a2` (front) and `ab1`/`ab2` (back of the same arms) — using the wrong pair
|
|
147
|
+
flips the gusset's orientation.
|
|
148
|
+
|
|
149
|
+
This is enough to sanity-check a graph you're editing. For full worked recipes (a
|
|
150
|
+
table-frame leg/rail/gusset layout with exact corner-by-corner rotations, and straight/
|
|
151
|
+
curved conveyor assemblies with their full BOMs and connector maps), see
|
|
152
|
+
`vention-ai-design-skill.md` at the project root — it drives the same graph through
|
|
153
|
+
live browser manipulation (Playwright + webpack-extracted `loadScene`) rather than the
|
|
154
|
+
download/upload/save API this skill uses, but the graph schema, coordinate convention,
|
|
155
|
+
and connector system it documents are the same ones described above.
|
|
156
|
+
|
|
157
|
+
Two more notes on vertices:
|
|
158
|
+
|
|
159
|
+
- `vertex_type: "addon"` parts (cable ties, loose hardware counted for BOM only) often
|
|
160
|
+
have no `local_matrix` and no edges. Don't try to position them.
|
|
161
|
+
- A design that has ever been saved carries one `vertex_type: "config"` vertex holding a
|
|
162
|
+
`configModels` root object. It is not a physical part. **Preserve it as-is** when
|
|
163
|
+
merging content into a design; don't delete or overwrite it. A design created through
|
|
164
|
+
`design_create`, by contrast, has *no graph at all* until your first save — so when
|
|
165
|
+
you author that first graph, include a config vertex yourself:
|
|
166
|
+
`{"id": <vid>, "attributes": {"vertex_type": "config", "configModels": {}}, "edges": []}`.
|
|
167
|
+
Note that `num_parts` in save results and `design_versions_list` counts this vertex,
|
|
168
|
+
so a graph with one real part reports `num_parts: 2` (verified). Don't read `num_parts`
|
|
169
|
+
as a BOM count.
|
|
170
|
+
|
|
171
|
+
Edge (one mating/joint relationship):
|
|
172
|
+
```
|
|
173
|
+
"<edge_id>": {
|
|
174
|
+
"id": <int>,
|
|
175
|
+
"attributes": {
|
|
176
|
+
"connector1": <string>, "connector2": <string>, // "cma_1|<part_number>|male/female|<port>"
|
|
177
|
+
"user_specified": <bool>, "distance1": <number>, "edge_type": "connection"
|
|
178
|
+
},
|
|
179
|
+
"vertices": [ <vertex_id_1>, <vertex_id_2> ]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
Connector/distance attributes are relative between the two parts, not world-absolute —
|
|
183
|
+
they stay valid under a rigid translation of the parts involved.
|
|
184
|
+
|
|
185
|
+
IDs (`vertices_by_index[].id`, `edges.by_id` keys) are plain counters, unique within a
|
|
186
|
+
single graph and carrying no meaning across designs. The one invariant that matters is
|
|
187
|
+
that `nextVID`/`nextEID` sit strictly above every id in use; `max(id) + 1` is the
|
|
188
|
+
obvious way to satisfy it. Vertex ids and edge ids count independently.
|
|
189
|
+
Real graphs are not guaranteed to start counting at 1 — vertex ids beginning at
|
|
190
|
+
`4294967296` (2^32) and edge ids at `8589934593` (2^33 + 1) are common. Read the
|
|
191
|
+
existing counters rather than assuming a range.
|
|
192
|
+
|
|
193
|
+
## Reading a design
|
|
194
|
+
|
|
195
|
+
Start with `design_versions_list(design)` — it is cheap, downloads nothing, and answers
|
|
196
|
+
most questions on its own. Per version, newest first: `version`, `version_name`,
|
|
197
|
+
`num_parts`, `size_bytes`, `md5`, `created_at`, plus that version's floor plan
|
|
198
|
+
(`floor_plan_url`, `floor_plan_file_name`, `floor_plan_metadata`; all `null` when the
|
|
199
|
+
version has none). Use it to see whether a design has content, how big it is, how many
|
|
200
|
+
parts it holds, and which version you should download — before spending a download.
|
|
201
|
+
|
|
202
|
+
Two quirks: `size_bytes` and `md5` can come back `null` for a version saved seconds
|
|
203
|
+
ago and fill in shortly after, and `floor_plan_url` expires — call the tool again for a
|
|
204
|
+
fresh link rather than reusing an old one.
|
|
205
|
+
|
|
206
|
+
Then, only if you need the geometry:
|
|
207
|
+
|
|
208
|
+
1. `design_graph_download_url(design)` — omit `graph_version` for current. The result
|
|
209
|
+
gives you `graph_version` — keep it, you'll need it as `parent_version` if you save.
|
|
210
|
+
2. Download the gzip file with a shell command (the tool gives you the exact `curl`)
|
|
211
|
+
and parse it in code. If it errors `NotFound: Design has no assembly graph`, the
|
|
212
|
+
design has never had content saved — treat it as blank, but still download it once
|
|
213
|
+
more right before you save (see below) since that can change.
|
|
214
|
+
|
|
215
|
+
## Writing a design
|
|
216
|
+
|
|
217
|
+
1. Transform the graph in code, writing the result to a local file. If you're adding
|
|
218
|
+
content to an existing design (rather than working from scratch), start from its
|
|
219
|
+
*current* downloaded graph and append to it — keep its `config` vertex and any
|
|
220
|
+
existing content, and continue its `nextVID`/`nextEID` counters rather than
|
|
221
|
+
restarting them.
|
|
222
|
+
2. **Validate before uploading** — cheap and catches most mistakes:
|
|
223
|
+
- every vertex id and every edge id is unique
|
|
224
|
+
- every edge's `vertices` entries exist in `vertices_by_index`
|
|
225
|
+
- every vertex's `edges` entries exist in `edges.by_id`
|
|
226
|
+
- `nextVID` > every vertex id used, `nextEID` > every edge id used
|
|
227
|
+
3. **Settle `parent_version` before you upload.** Check the design's current version with
|
|
228
|
+
`design_versions_list` (or `design_graph_download_url`) — don't reuse a number from
|
|
229
|
+
earlier in your work, since a design can advance versions with no content change (see
|
|
230
|
+
gotcha below). If it has moved past the version you transformed in step 1, go back to
|
|
231
|
+
step 1 and redo the edit on the current graph; the work you already did is stale.
|
|
232
|
+
For the first save into a design created by `design_create`, there is no current
|
|
233
|
+
version — use `parent_version: "0"`.
|
|
234
|
+
4. `design_graph_upload_create(design)` — mint an upload. Gzip your file, then **POST**
|
|
235
|
+
it (multipart form) to `post_url` using the returned `form_fields` verbatim, with
|
|
236
|
+
the file field last. This is not a plain PUT. A `204` with an empty body is success;
|
|
237
|
+
there is no other confirmation, and the upload changes nothing on its own.
|
|
238
|
+
5. `design_version_save(design, upload_key, parent_version, floor_plan_id?)` — commit as
|
|
239
|
+
a new version. Rails assigns the number; never pick one yourself. Pass
|
|
240
|
+
`floor_plan_id` if this version should keep a floor plan (see Floor plans below).
|
|
241
|
+
If it's rejected as stale, the error names the actual current version; download that
|
|
242
|
+
version, reapply your change on top of it, **re-upload** (see the next gotcha), and
|
|
243
|
+
retry.
|
|
244
|
+
|
|
245
|
+
Saves **append a new immutable version**. They never overwrite the version you started
|
|
246
|
+
from, so the user can always roll back. Saves are only accepted on top of the current
|
|
247
|
+
version — editing an older version is not supported.
|
|
248
|
+
|
|
249
|
+
### The app normalizes what you save
|
|
250
|
+
|
|
251
|
+
A minimal hand-authored graph is accepted and then tidied up the next time the design is
|
|
252
|
+
opened and saved in MachineBuilder. Observed across one such round trip:
|
|
253
|
+
|
|
254
|
+
- `configModels: {}` on the config vertex was replaced with a real root object
|
|
255
|
+
(a uuid keyed to `{"id", "type": "root", "properties": {"version": 37}, "parentId":
|
|
256
|
+
null, "childrenIds": []}`). So an empty object is a valid thing to author — the app
|
|
257
|
+
fills it in.
|
|
258
|
+
- `opacity: 1` and `visibility: true` were added to the part vertex.
|
|
259
|
+
- `format_version` was rewritten `28 → 29`, with `format_version_initial` left at `28`.
|
|
260
|
+
Don't hardcode a `format_version`; carry over whatever the design currently reports,
|
|
261
|
+
and expect the app to raise it.
|
|
262
|
+
- `design_bbox` was reset to all zeros, discarding the extents that were written into
|
|
263
|
+
it. Treat the bbox as app-maintained: read it for sizing decisions if it looks
|
|
264
|
+
populated, but don't count on a value you write surviving, and don't treat zeros as
|
|
265
|
+
meaning the design is empty.
|
|
266
|
+
|
|
267
|
+
Geometry itself came back untouched — part number, `short_name` and `local_matrix`
|
|
268
|
+
survived exactly. The lesson is to author the minimum honestly and let the app enrich
|
|
269
|
+
it, rather than trying to synthesize fields you haven't observed.
|
|
270
|
+
|
|
271
|
+
### Gotcha: a rejected save destroys your upload
|
|
272
|
+
|
|
273
|
+
A stale-`parent_version` rejection consumes the uploaded file anyway. Retrying the save
|
|
274
|
+
with a corrected `parent_version` and the same `upload_key` fails with
|
|
275
|
+
`NotFound: no uploaded file at this key`. Every save attempt — including a retry after
|
|
276
|
+
a rejection — needs a fresh `design_graph_upload_create` and a fresh POST. Verified.
|
|
277
|
+
|
|
278
|
+
### Gotcha: a live browser session can fork the version history
|
|
279
|
+
|
|
280
|
+
If the user has the design open in MachineBuilder while you save via the API, its tab
|
|
281
|
+
keeps auto-saving on its own timeline — even with zero content changes (observed: v3 →
|
|
282
|
+
v5 with an identical empty graph). Your API save branches off whatever version was
|
|
283
|
+
current *at that moment* (e.g. v5 → v6), but the live tab has no idea your v6 exists
|
|
284
|
+
and just keeps auto-saving its own sibling branch (e.g. v5 → v5.1). The user opening
|
|
285
|
+
the design in their browser will see the live branch, not yours, even though yours has
|
|
286
|
+
your changes.
|
|
287
|
+
|
|
288
|
+
Fix: tell the user to open the specific version number you saved in MachineBuilder and
|
|
289
|
+
save from there — that makes the editor adopt it as the branch it continues on. Warn
|
|
290
|
+
them about this explicitly whenever you save into a design you know is open live.
|
|
291
|
+
|
|
292
|
+
## Floor plans
|
|
293
|
+
|
|
294
|
+
A floor plan is a plan-view image (JPEG, PNG or BMP) attached to a design so a layout
|
|
295
|
+
can be built on top of the real building. It lives entirely on the version pin —
|
|
296
|
+
**a downloaded graph contains no reference to the floor plan at all** (verified by
|
|
297
|
+
diffing a graph before and after a floor plan was added), so `floor_plan_id` on the save
|
|
298
|
+
is the whole mechanism. Nothing about it needs to go into the graph JSON.
|
|
299
|
+
|
|
300
|
+
Adding one takes **four** steps — three to create it, then a save to pin it. Stopping
|
|
301
|
+
after step 3 attaches the floor plan but displays it nowhere; that is the step most
|
|
302
|
+
easily forgotten:
|
|
303
|
+
|
|
304
|
+
1. `design_floor_plan_upload_create(design, content_type)` — mint a presigned POST.
|
|
305
|
+
`content_type` must be one of `image/jpeg`, `image/png`, `image/bmp`, and must be
|
|
306
|
+
sent back as the `Content-Type` form field (unlike the graph upload, the floor-plan
|
|
307
|
+
form fields include it). **The cap is 5 MB** (`max_bytes: 5242880`) — much tighter
|
|
308
|
+
than the graph upload's 100 MB. Check the file size before minting; if it's over,
|
|
309
|
+
downscale the image rather than retrying.
|
|
310
|
+
2. POST the image multipart to `post_url` with every `form_fields` pair verbatim and
|
|
311
|
+
the file part last. A 204 is success.
|
|
312
|
+
3. `design_floor_plan_create(design, metadata, file_upload_key: <key>)` — turns the
|
|
313
|
+
upload into a floor plan on the design. Returns `floor_plan_id`, plus `image_url` and
|
|
314
|
+
`image_file_name`. **Record the `floor_plan_id` and the image id from `image_url` in
|
|
315
|
+
your reply to the user** — see the recovery note below for why. (`image_file_name`
|
|
316
|
+
will be the upload key's UUID, not the user's filename, since the API mints the key;
|
|
317
|
+
a UI upload keeps the original name. Nothing depends on it.)
|
|
318
|
+
4. `design_version_save(design, upload_key, parent_version, floor_plan_id: "<id>")` —
|
|
319
|
+
pin it. Until a version pins it, the floor plan is displayed nowhere.
|
|
320
|
+
|
|
321
|
+
Step 4 is a normal graph save, so it needs a graph: follow "Writing a design" above. To
|
|
322
|
+
add a floor plan without changing geometry, download the current graph and re-upload it
|
|
323
|
+
unmodified with the `floor_plan_id` set.
|
|
324
|
+
|
|
325
|
+
### Pinning is per-version, and silently drops
|
|
326
|
+
|
|
327
|
+
`floor_plan_id` is not sticky. **Omit it on a later save and the new current version has
|
|
328
|
+
no floor plan** — verified: v1 saved with `floor_plan_id` had the plan, v2 saved without
|
|
329
|
+
it came back with `floor_plan_url: null`, and the design showed no floor plan even
|
|
330
|
+
though nothing was deleted. Nothing warns you.
|
|
331
|
+
|
|
332
|
+
So before any save into a design that has a floor plan, check the current version's
|
|
333
|
+
floor plan with `design_versions_list` and restate its `floor_plan_id` on your save.
|
|
334
|
+
|
|
335
|
+
### Recovering a floor_plan_id you don't have
|
|
336
|
+
|
|
337
|
+
`design_versions_list` returns `floor_plan_url`, `floor_plan_file_name` and
|
|
338
|
+
`floor_plan_metadata` — **not `floor_plan_id`**. The only place the id appears is the
|
|
339
|
+
return value of `design_floor_plan_create`, so an id created in an earlier session is
|
|
340
|
+
not directly recoverable, and there is no tool that lists a design's floor plans.
|
|
341
|
+
|
|
342
|
+
What *is* recoverable is the **image id**, from the `floor_plan_url` path
|
|
343
|
+
(`.../design_floor_plan_image/file/396/…` → `396`). So to keep a floor plan whose id you
|
|
344
|
+
have lost: read the current version's `floor_plan_metadata` and image id from
|
|
345
|
+
`design_versions_list`, call `design_floor_plan_create` again with that `image_id` and
|
|
346
|
+
that same metadata, and pin the fresh `floor_plan_id` it returns. The image is not
|
|
347
|
+
re-uploaded and the placement is preserved — you just get a new record pointing at the
|
|
348
|
+
same image.
|
|
349
|
+
|
|
350
|
+
Because of this, always surface the `floor_plan_id` to the user when you create one, so
|
|
351
|
+
it is in the conversation rather than only in a tool result.
|
|
352
|
+
|
|
353
|
+
### Reusing and repositioning an image
|
|
354
|
+
|
|
355
|
+
`design_floor_plan_create` takes **either** `file_upload_key` (a fresh upload) **or**
|
|
356
|
+
`image_id` — never both. `image_id` is the numeric id in the returned `image_url` path
|
|
357
|
+
(`.../design_floor_plan_image/file/396/...` → `image_id: "396"`), and it lets you create
|
|
358
|
+
a second floor plan from an image already uploaded. That is how you reposition or
|
|
359
|
+
rescale a plan: create a new floor plan record from the same `image_id` with different
|
|
360
|
+
`metadata`, then pin the new `floor_plan_id`. Verified. Don't re-upload the image to
|
|
361
|
+
move it.
|
|
362
|
+
|
|
363
|
+
Floor plans accumulate on the design and there is no delete tool, so don't churn out
|
|
364
|
+
records experimentally.
|
|
365
|
+
|
|
366
|
+
### The metadata field
|
|
367
|
+
|
|
368
|
+
`metadata` is required, stored **verbatim**, and **not validated by the tool** — a wrong
|
|
369
|
+
shape is accepted happily and then simply fails to place the image. So write exactly
|
|
370
|
+
what MachineBuilder itself writes. This is the shape the UI produces, read back from a
|
|
371
|
+
UI-created floor plan (verified):
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"version": 1,
|
|
376
|
+
"fileSize": 179580,
|
|
377
|
+
"dimensions": { "width": 578, "height": 596 },
|
|
378
|
+
"matrix": [12731.246649383824, 0, 0, 0,
|
|
379
|
+
0, 1, 0, 0,
|
|
380
|
+
0, 0, 13127.721458534186, 0,
|
|
381
|
+
0, 0, 0, 1]
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
- `version` — the metadata schema version. `1` at time of writing. Copy the existing
|
|
386
|
+
value when editing a design that already has a floor plan.
|
|
387
|
+
- `fileSize` — the image's size in bytes.
|
|
388
|
+
- `dimensions` — the image's pixel width and height.
|
|
389
|
+
- `matrix` — a **flat 16-float column-major 4x4**, at the top level. Note it is *not*
|
|
390
|
+
nested under a `transform` key and there is no `fileName` key. Don't add keys the app
|
|
391
|
+
doesn't write.
|
|
392
|
+
|
|
393
|
+
### How the matrix works
|
|
394
|
+
|
|
395
|
+
It scales a unit plane onto the floor, in the graph's own Y-up convention:
|
|
396
|
+
|
|
397
|
+
- `matrix[0]` = the plan's **X extent in mm**, `matrix[10]` = its **Z extent in mm** —
|
|
398
|
+
both horizontal, matching Y being vertical.
|
|
399
|
+
- `matrix[5]` = `1`. The plane has no thickness; leave it at 1.
|
|
400
|
+
- `matrix[12..14]` = translation, `0, 0, 0` in the observed case. Off-diagonal terms
|
|
401
|
+
were all zero — no rotation or shear.
|
|
402
|
+
|
|
403
|
+
The two extents encode one uniform scale. In the observed floor plan,
|
|
404
|
+
`12731.2466 / 578 == 13127.7215 / 596 == 22.026378` mm per pixel, and the extents'
|
|
405
|
+
ratio matches the image aspect ratio exactly. So to build a matrix:
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
s = millimetres per pixel # from the user, or from a known real-world dimension
|
|
409
|
+
sx = dimensions.width * s
|
|
410
|
+
sz = dimensions.height * s
|
|
411
|
+
matrix = [sx,0,0,0, 0,1,0,0, 0,0,sz,0, tx,ty,tz,1]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
That plan was 578 x 596 px at 22.026 mm/px — 12.73 m x 13.13 m of floor. **Ask the user
|
|
415
|
+
for the scale**; never assume one. The cleanest question is what a known distance on the
|
|
416
|
+
drawing measures in the real building, then divide by its pixel length.
|
|
417
|
+
|
|
418
|
+
> **Still unverified:** where the plane's origin sits — whether zero translation puts
|
|
419
|
+
> the image's corner or its centre at the world origin — and whether the app honours a
|
|
420
|
+
> non-zero translation or rotation written through the API. Only the zero-translation
|
|
421
|
+
> case has been observed. When you write a floor plan, say the placement needs a look,
|
|
422
|
+
> ask the user to confirm the image lands where they expect, and offer to adjust. Don't
|
|
423
|
+
> call a floor plan correctly placed because the API calls succeeded.
|
|
424
|
+
|
|
425
|
+
## Duplicating / placing a copy of an existing design
|
|
426
|
+
|
|
427
|
+
There's no sub-assembly instancing in this graph format — "place a copy of design X"
|
|
428
|
+
means deep-copying its parts and joints, not inserting a reference:
|
|
429
|
+
|
|
430
|
+
1. Download the source design's current graph.
|
|
431
|
+
2. For each copy you want: deep-copy every vertex and every edge, assigning fresh ids
|
|
432
|
+
(continue counting up from the target's `nextVID`/`nextEID`), remapping the vertex
|
|
433
|
+
ids referenced in each edge's `vertices` and each vertex's `edges` list consistently.
|
|
434
|
+
3. Rigidly offset each copy so copies don't overlap: add a delta to `m[12]`/`m[13]`/
|
|
435
|
+
`m[14]` (x/y/z translation) on every vertex in the copy that has a `local_matrix`.
|
|
436
|
+
Skip vertices without one (e.g. `addon` parts) — leave them untouched. Size the
|
|
437
|
+
offset using the source's `design_bbox` extent plus a margin, along **X or Z** — a
|
|
438
|
+
horizontal axis. Offsetting along Y (`m[13]`) would lift the copy off the floor or
|
|
439
|
+
sink it into the ground, since Y is the vertical axis.
|
|
440
|
+
Leave edge attributes (connector strings, `distance1`) unchanged — they're
|
|
441
|
+
part-relative and remain valid under a rigid translation.
|
|
442
|
+
4. Merge the copies' vertices/edges into the target's existing `vertices_by_index` /
|
|
443
|
+
`edges.by_id` (keeping the target's own `config` vertex and any existing content),
|
|
444
|
+
recompute `design_bbox` as the union, and set `nextVID`/`nextEID` past every id used.
|
|
445
|
+
5. Run the validation pass above before uploading.
|
|
446
|
+
|
|
447
|
+
If the source's `format_version` differs from the target design's, treat the merge as
|
|
448
|
+
unverified: the JSON can be structurally self-consistent and still render wrong if the
|
|
449
|
+
app expects newer-schema shapes the older source never had. Say so to the user and ask
|
|
450
|
+
them to visually confirm the result in MachineBuilder rather than asserting it worked.
|
|
451
|
+
|
|
452
|
+
## Working practice
|
|
453
|
+
|
|
454
|
+
Establish the requirement before building anything. Ask for whatever is missing:
|
|
455
|
+
|
|
456
|
+
- The part or payload — dimensions, weight, orientation
|
|
457
|
+
- The process — pick and place, palletizing, machine tending, assembly
|
|
458
|
+
- The robot, if one is specified, and its reach and payload needs
|
|
459
|
+
- The available footprint and any fixed obstacles — if they have a plan-view drawing of
|
|
460
|
+
the space, offer to attach it as a floor plan and lay the cell out on it
|
|
461
|
+
- Throughput target
|
|
462
|
+
|
|
463
|
+
Then plan the layout before writing any graph: place the robot first, then position
|
|
464
|
+
peripherals inside its reach envelope, then add structure.
|
|
465
|
+
|
|
466
|
+
## Rules
|
|
467
|
+
|
|
468
|
+
- Never invent a Vention part number. Use parts you found in an existing graph or that
|
|
469
|
+
the user supplied.
|
|
470
|
+
- Never invent a `category_code`. Read them from `design_categories_list`.
|
|
471
|
+
- Never invent load ratings, reach figures, or payload capacities.
|
|
472
|
+
- Report what you changed, in plain language, before the user opens the design.
|
|
473
|
+
- If a save fails validation, show the user the errors rather than retrying blindly.
|
|
474
|
+
- The save and floor-plan tools are experimental and act on real designs in the user's
|
|
475
|
+
account. Confirm before the first write into a design you didn't create this session.
|
|
476
|
+
|
|
477
|
+
## Safety
|
|
478
|
+
|
|
479
|
+
A generated layout is an engineering starting point, not a validated machine. Never
|
|
480
|
+
describe one as safety-validated, compliant, or ready to build. Always tell the user to
|
|
481
|
+
review the result in MachineBuilder, run the Automated Design Checker, and have any
|
|
482
|
+
safety-relevant design reviewed by a qualified engineer before anything is built.
|
|
483
|
+
|
|
484
|
+
## Common mistakes
|
|
485
|
+
|
|
486
|
+
- Downloading a graph into context instead of into a file. Graphs are far too large.
|
|
487
|
+
- Assuming a save overwrites the current version. It appends a new one.
|
|
488
|
+
- Assuming the vertical axis from a bbox's magnitude or sign instead of checking Y
|
|
489
|
+
directly. Y is the floor-level axis (Y=0); guessing Z produced a fix that moved the
|
|
490
|
+
design sideways and left it still sunk into the ground.
|
|
491
|
+
- Reusing a `parent_version` from earlier in a session instead of re-checking current
|
|
492
|
+
right before saving — sessions and live browser tabs can both advance it.
|
|
493
|
+
- Saving into a design that's open live in the user's browser without warning them
|
|
494
|
+
about version forking (see gotcha above), leaving them looking at the wrong branch.
|
|
495
|
+
- Treating a graph download error as fatal without retrying. Read the message:
|
|
496
|
+
`NotFound: Design has no assembly graph` means the design is genuinely blank — accept
|
|
497
|
+
it and move on. Anything else, including a version that was saved moments ago and
|
|
498
|
+
isn't downloadable yet, is worth one retry before you report a problem.
|
|
499
|
+
- Designing past the robot's reach envelope because the reach figure was assumed rather
|
|
500
|
+
than looked up.
|
|
501
|
+
- Treating `design_versions_list` returning `[]`, or `NotFound: Design has no assembly
|
|
502
|
+
graph`, as a failure. On a freshly created design both are the expected answer.
|
|
503
|
+
- Guessing a `parent_version` for the first save into a new design instead of using `"0"`.
|
|
504
|
+
- Retrying a stale-rejected save with the same `upload_key`. The file is already gone —
|
|
505
|
+
mint a new upload and POST again.
|
|
506
|
+
- Omitting `floor_plan_id` on a save into a design that has a floor plan, silently
|
|
507
|
+
dropping it from the new current version.
|
|
508
|
+
- Uploading a floor plan image and stopping there. Nothing is attached until
|
|
509
|
+
`design_floor_plan_create`, and nothing is visible until a save pins the id.
|
|
510
|
+
- Re-uploading a floor plan image just to move it, instead of creating a new floor plan
|
|
511
|
+
from the same `image_id` with a different transform.
|
|
512
|
+
- Sending a floor plan image over the 5 MB cap, or with a `content_type` other than
|
|
513
|
+
JPEG/PNG/BMP.
|
|
514
|
+
- Reporting a floor plan as correctly placed because the API calls succeeded. The
|
|
515
|
+
metadata is stored unvalidated; only the user looking at the design confirms placement.
|
|
516
|
+
- Inventing a floor-plan `metadata` shape. Write the UI's shape — flat top-level
|
|
517
|
+
`matrix`, `version`, `fileSize`, `dimensions` — not a plausible-looking alternative.
|
|
518
|
+
- Assuming a scale for a floor plan image. Ask what a known distance on the drawing
|
|
519
|
+
measures in the building.
|
|
520
|
+
- Hardcoding `format_version`, or trusting a `design_bbox` you wrote to survive a save.
|
|
521
|
+
- Downloading a graph to answer a question `design_versions_list` already answers
|
|
522
|
+
(part count, size, whether the design has content, which version to use).
|