@thatopen/services 0.5.0 → 0.7.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/README.md +62 -62
- package/dist/built-in/index.d.ts +569 -0
- package/dist/cli.js +12 -23
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +41 -49
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +37 -0
- package/docs/builtin/paths.json +4 -0
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/docs/flow-architecture.md +389 -0
- package/docs/flow-plugin-guide.md +337 -0
- package/docs/platform-token.md +81 -0
- package/docs/revit-collab-quickstart.md +363 -173
- package/docs/rhino-interop-quickstart.md +391 -0
- package/package.json +1 -1
- package/resources/AGENTS.md +40 -40
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -0
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
- package/src/built-in/index.ts +570 -0
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Rhino → Revit, start to finish
|
|
2
|
+
|
|
3
|
+
You are an assistant. Somebody is modelling in Rhino for a project whose model lives in Revit, and
|
|
4
|
+
they want what they draw to become real Revit elements. This is the whole path, in the order it
|
|
5
|
+
happens, with the numbers from a run that actually worked.
|
|
6
|
+
|
|
7
|
+
It assumes the collaboration side is already set up — a central published, a project on the
|
|
8
|
+
platform. If it is not, that is
|
|
9
|
+
**[the Revit collaboration quick start](https://raw.githubusercontent.com/ThatOpen/platform_services/main/docs/revit-collab-quickstart.md)**, and it comes first.
|
|
10
|
+
|
|
11
|
+
## What is actually happening
|
|
12
|
+
|
|
13
|
+
Five steps, and it is worth saying them plainly before touching anything, because the shape is what
|
|
14
|
+
people get wrong:
|
|
15
|
+
|
|
16
|
+
1. Rhino **publishes** its model to the platform as a `.frag`. That is all the plug-in does. It
|
|
17
|
+
mentions Revit nowhere.
|
|
18
|
+
2. A **converter** — code in the user's own app, not in either plug-in — reads that `.frag` and
|
|
19
|
+
decides what it means in Revit's vocabulary.
|
|
20
|
+
3. The converter **queues a proposal**. It changes nothing.
|
|
21
|
+
4. Somebody **in Revit accepts it**. Applying is a decision, and the decision belongs to the person
|
|
22
|
+
whose model it is.
|
|
23
|
+
5. They **sync**, and it becomes a commit like any other.
|
|
24
|
+
|
|
25
|
+
**No element is ever tied to an element.** The only thing linking a Rhino object to a Revit column
|
|
26
|
+
is the converter's own note of what it asked for. Anybody proposing to store a link between the two
|
|
27
|
+
models is proposing the thing this architecture exists to avoid.
|
|
28
|
+
|
|
29
|
+
## Before anything
|
|
30
|
+
|
|
31
|
+
Log in: **[Logging in to That Open Platform](https://raw.githubusercontent.com/ThatOpen/platform_services/main/docs/platform-token.md)**. The Rhino plug-in reads
|
|
32
|
+
`~/.thatopen/config.json`, the same file the CLI writes. It deliberately does not read the Revit
|
|
33
|
+
add-in's session — same machine, but one plug-in reading another's private state is coupling nobody
|
|
34
|
+
can undo later.
|
|
35
|
+
|
|
36
|
+
Install the plug-in. **Rhino has to be closed**; the installer holds its files.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
thatopen rhino install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Driving Rhino without a keyboard
|
|
43
|
+
|
|
44
|
+
The plug-in runs a local HTTP server, exactly like the Revit add-in. This is what lets you do the
|
|
45
|
+
whole job while the user watches, instead of dictating commands for them to type.
|
|
46
|
+
|
|
47
|
+
The port and the token are written to `%APPDATA%\ThatOpen\rhino-addin.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{ "port": 5620, "token": "…", "pid": 5436 }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Every request is a POST with a JSON body and the header `X-RhinoFlow-Token`. **Never print the
|
|
54
|
+
token**, and never write it into a script you are about to show.
|
|
55
|
+
|
|
56
|
+
There is also a `script` endpoint that runs Rhino's own command line. Use it for drawing, which is
|
|
57
|
+
what it is good at. **Never use it for a command that asks a question** — the import command wants
|
|
58
|
+
the project id the first time it runs, and a scripted command has nobody to answer, so Rhino sits
|
|
59
|
+
there with no error and no progress. That is why `import-model` and `publish` take their answers as
|
|
60
|
+
parameters instead of being reachable through `script`.
|
|
61
|
+
|
|
62
|
+
## 1. Bring the team's model into Rhino
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
POST /import-model { "project": "<projectId>", "document": "<doc>" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{ "ok": true, "model": "rstadvancedsampleproject.frag",
|
|
70
|
+
"layer": "ThatOpen reference — rstadvancedsampleproject",
|
|
71
|
+
"added": 907, "skipped": 0 }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
It lands on a **locked layer**, with no properties, and it cannot be sent back. Say why if the user
|
|
75
|
+
asks, because it looks like a limitation and it is a decision: a `.frag` is a tessellation — what a
|
|
76
|
+
Revit wall LOOKS like, not what it IS — so editing one in Rhino and returning it could only produce
|
|
77
|
+
a worse version of a model Revit already owns. The lock stops somebody editing a mesh that is really
|
|
78
|
+
somebody else's wall and then proposing that as a change.
|
|
79
|
+
|
|
80
|
+
If the folder holds more than one model the call comes back with `available` listing them and no
|
|
81
|
+
import. Ask which; do not pick the first. Somebody spending an afternoon modelling against the wrong
|
|
82
|
+
building is a bad afternoon.
|
|
83
|
+
|
|
84
|
+
## 2. Find out where things are, before you place anything
|
|
85
|
+
|
|
86
|
+
Do not invent coordinates. Ask the model:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
POST /objects { "limit": 3000 }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each object comes back with `min`, `max`, `center`, its layer, and the user strings the import
|
|
93
|
+
stamped on it — including `ThatOpen:Category`, which is Revit's own category name.
|
|
94
|
+
|
|
95
|
+
**Group before you conclude.** Reading the first few rows of that list is not measuring, and it will
|
|
96
|
+
mislead you. Measured on the sample project: the first four columns in the list were 750×750, based
|
|
97
|
+
at Z −2500, 2500 tall. They were the basement — four out of seventy. The building's actual column is
|
|
98
|
+
**450×450, based at Z 0, 3500 tall**, which is 63 of the 70. Taking the section from the first rows
|
|
99
|
+
produced columns that were too fat and buried, twice.
|
|
100
|
+
|
|
101
|
+
The same query gives you the grid. Sort each row of columns by X, look at the gaps, and the midpoint
|
|
102
|
+
of a wide one is a defensible place for a new column — it sits under the beam spanning that bay.
|
|
103
|
+
|
|
104
|
+
## 3. Place the columns
|
|
105
|
+
|
|
106
|
+
There is no "make me a column" endpoint, and there should not be. This plug-in moves data and
|
|
107
|
+
decides nothing about what any of it means; the moment it grows a command called `columns` it has an
|
|
108
|
+
opinion about buildings, and that opinion belongs in the converter. Rhino's own commands do the job:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
POST /script { "script": "_-Layer _New \"flow:columns\" _Current \"flow:columns\" _Enter" }
|
|
112
|
+
POST /script { "script": "_-Point -5544,-1413,0" }
|
|
113
|
+
POST /script { "script": "_-Box -5769,-1638,0 -5319,-1188,0 3500" }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Verified: that produces a point at the given coordinate and a 450×450×3500 extrusion centred on it.
|
|
117
|
+
`_-Box` takes two opposite corners of the base and then a height, so a 450 square centred on
|
|
118
|
+
`(x, y)` runs from `(x−225, y−225)` to `(x+225, y+225)`.
|
|
119
|
+
|
|
120
|
+
**Place BOTH, and understand why.** The converter reads **points** on a layer and turns each into a
|
|
121
|
+
Revit column. It does not read solids. But a point is invisible at building scale, so a Rhino file of
|
|
122
|
+
"the new columns" looks like an empty file — impossible to work with and impossible to show anybody.
|
|
123
|
+
So the point is the data, and the box is the picture. Put them on sibling layers (`flow:columns` and
|
|
124
|
+
`flow:columns (view)`) so the converter's layer holds nothing but points.
|
|
125
|
+
|
|
126
|
+
Nothing downstream reads the box. If somebody later rewrites the converter to read solids, the point
|
|
127
|
+
layer goes away and everything else still works.
|
|
128
|
+
|
|
129
|
+
**Sizes and coordinates are in the DOCUMENT's units.** `POST /status` reports them. The sample
|
|
130
|
+
project is in millimetres, where a column of `0.45` is an invisible splinter. Converting from metres
|
|
131
|
+
behind the user's back would put everything a thousand times too far away.
|
|
132
|
+
|
|
133
|
+
### The whole thing, worked
|
|
134
|
+
|
|
135
|
+
Four columns down one bay of the sample project, at the midpoints of a 7315 mm gap that repeats row
|
|
136
|
+
after row. This is exactly what was run:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
_-Layer _New "flow:columns" _Enter
|
|
140
|
+
_-Layer _New "flow:columns (view)" _Enter
|
|
141
|
+
|
|
142
|
+
_-Layer _Current "flow:columns" _Enter
|
|
143
|
+
_-Point -5544,-5769,0
|
|
144
|
+
_-Point -5544,-1413,0
|
|
145
|
+
_-Point -5544,530,0
|
|
146
|
+
_-Point -5544,6829,0
|
|
147
|
+
|
|
148
|
+
_-Layer _Current "flow:columns (view)" _Enter
|
|
149
|
+
_-Box -5769,-5994,0 -5319,-5544,0 3500
|
|
150
|
+
_-Box -5769,-1638,0 -5319,-1188,0 3500
|
|
151
|
+
_-Box -5769,305,0 -5319,755,0 3500
|
|
152
|
+
_-Box -5769,6604,0 -5319,7054,0 3500
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
One `POST /script` per line, or several lines in one call. Check `objectsAfter` in the reply: it is
|
|
156
|
+
the only thing that tells a command that did nothing from one that worked.
|
|
157
|
+
|
|
158
|
+
### And then anything else
|
|
159
|
+
|
|
160
|
+
Nothing above is about columns. It is: make a layer, put geometry on it, and let the converter say
|
|
161
|
+
what it means. The same shape covers whatever the user actually wants —
|
|
162
|
+
|
|
163
|
+
| want | command |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| a point | `_-Point x,y,z` |
|
|
166
|
+
| a box | `_-Box x1,y1,z1 x2,y2,z2 <height>` |
|
|
167
|
+
| a line | `_-Line x1,y1,z1 x2,y2,z2` |
|
|
168
|
+
| a circle | `_-Circle x,y,z <radius>` |
|
|
169
|
+
| a layer, made current | `_-Layer _New "name" _Current "name" _Enter` |
|
|
170
|
+
|
|
171
|
+
The leading `-` is what matters: it is Rhino's command-line form, which takes its arguments inline
|
|
172
|
+
instead of opening a dialog. Without it the command puts a window on screen and waits for a person,
|
|
173
|
+
and there is no person.
|
|
174
|
+
|
|
175
|
+
Two rules carry over to anything you draw here. The geometry the converter reads goes on **its own
|
|
176
|
+
layer, holding nothing else**, because the converter selects by layer and cannot tell your helper
|
|
177
|
+
geometry from your data. And the coordinates are always in the **document's units**.
|
|
178
|
+
|
|
179
|
+
## 4. Publish
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
POST /publish { "project": "<projectId>", "document": "<doc>", "name": "rhino-columns.frag" }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{ "ok": true, "file": "rhino-columns.frag", "items": 8, "withGeometry": 4, "bytes": 10529 }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Check that number.** Eight items is four points and four boxes. The first version of this uploaded
|
|
190
|
+
915 items and 3.4 MB, because the 907 reference meshes went back up with everything else — a
|
|
191
|
+
tessellated copy of the Revit model, republished under the Rhino model's name. Objects stamped
|
|
192
|
+
`ThatOpen:Source` are excluded now, but if you ever see the item count in the hundreds, that is what
|
|
193
|
+
happened.
|
|
194
|
+
|
|
195
|
+
Points count as items without geometry. That is correct: they are the data the converter reads, not
|
|
196
|
+
something to draw.
|
|
197
|
+
|
|
198
|
+
## 5. The converter
|
|
199
|
+
|
|
200
|
+
This is the part that is **not** in either plug-in, and should not be. It reads the published
|
|
201
|
+
`.frag`, decides what it means, and queues a proposal.
|
|
202
|
+
|
|
203
|
+
The worked example is `RhinoColumnsConverter`: points on a named layer become Revit columns of a
|
|
204
|
+
given family and type. It is written to be read and then rewritten — the next person wants walls
|
|
205
|
+
from curves, or louvres from blocks — but four steps survive any rewrite:
|
|
206
|
+
|
|
207
|
+
1. read the source model's own data, in the source's own units
|
|
208
|
+
2. ask what this converter already made, which comes from the queue and nowhere else
|
|
209
|
+
3. decide: new ones are added, known ones updated, vanished ones removed
|
|
210
|
+
4. queue one operation
|
|
211
|
+
|
|
212
|
+
It persists nothing of its own. Run it on another machine, or as a cloud component, and it behaves
|
|
213
|
+
identically, because everything it remembers it reads back from what it wrote to the platform.
|
|
214
|
+
|
|
215
|
+
The byte contract is **[Writing a flow plugin](https://raw.githubusercontent.com/ThatOpen/platform_services/main/docs/flow-plugin-guide.md)**, and the reasoning
|
|
216
|
+
behind the shape is **[the architecture notes](https://raw.githubusercontent.com/ThatOpen/platform_services/main/docs/flow-architecture.md)**.
|
|
217
|
+
|
|
218
|
+
Everything below is what a run of this cost to find out. None of it is guessable, and all of it
|
|
219
|
+
fails quietly when it is wrong.
|
|
220
|
+
|
|
221
|
+
### What Rhino actually publishes
|
|
222
|
+
|
|
223
|
+
One item per Rhino object, category `IFCBUILDINGELEMENTPROXY`, carrying these attributes:
|
|
224
|
+
|
|
225
|
+
| attribute | example | note |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `Layer` | `flow:columns` | |
|
|
228
|
+
| `Type` | `Point` | `Point`, `Extrusion`, `Curve`, … |
|
|
229
|
+
| `Name` | | the object's name, usually empty |
|
|
230
|
+
| `Position` | `"-5544.4,-1413,0"` | a **string**, in the document's units |
|
|
231
|
+
| `Rhino:Definition` | | block definition, when there is one |
|
|
232
|
+
|
|
233
|
+
**The layers are published as items too**, category `IFCBUILDINGSTOREY`, geometry-less, carrying a
|
|
234
|
+
single `Name` attribute holding the layer's full path. They have no `Layer` and no `Position`. So a
|
|
235
|
+
converter that matches the layer name loosely, or that assumes every item it sees has a position,
|
|
236
|
+
reads a layer as a column. Test both fields, positively:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
if (attrs.Layer !== "flow:columns") continue;
|
|
240
|
+
if (attrs.Type !== "Point") continue; // and this is what separates points from everything else on it
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`Type` is Rhino's own geometry class name: `Point` for a point, `Extrusion` for a `_-Box`, `Curve`
|
|
244
|
+
for a line. It is the only thing distinguishing the data from the helper geometry if somebody ends up
|
|
245
|
+
putting both on one layer.
|
|
246
|
+
|
|
247
|
+
Reading it in the app, where the client is already authenticated:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const res = await client.downloadFile(fileId);
|
|
251
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
252
|
+
const model = FRAGS.EditUtils.getModelFromBuffer(bytes, false);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`getModelFromBuffer(bytes, raw)`: `raw` is false for anything downloaded, because what the platform
|
|
256
|
+
stores is compressed. Attribute rows come back as JSON triples, `[name, value, type]`, one per call
|
|
257
|
+
to `row.data(j)`.
|
|
258
|
+
|
|
259
|
+
### Units, and why the envelope has no units field
|
|
260
|
+
|
|
261
|
+
A proposal's numbers are in the terms of **what the target published**. Read Revit's export metadata
|
|
262
|
+
and it says so itself:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
"data": { "units": "ft", "axes": "z-up", "note": "Revit's own terms, untouched. Operations are written in these." }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Rhino publishes its document units in the same place, `model.metadata()`, under `data.units`. On the
|
|
269
|
+
sample project that is `Millimeters`. So the converter divides by **304.8**, and it is the converter's
|
|
270
|
+
job precisely because it is the only piece that knows both sides.
|
|
271
|
+
|
|
272
|
+
Check the conversion against something real instead of trusting the constant. An existing column in
|
|
273
|
+
the Revit export sits at `-30.19028688187479` ft. Times 304.8 that is **-9202 mm**, which is exactly
|
|
274
|
+
the bay edge measured in Rhino. That is a conversion proved, not assumed.
|
|
275
|
+
|
|
276
|
+
**Refuse unknown units.** Guessing a scale factor puts the columns a thousand times too far away, and
|
|
277
|
+
nothing about the result says why.
|
|
278
|
+
|
|
279
|
+
### Building the payload
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const model = EU.getModelFromBuffer(EU.newModel({ raw: true }), true);
|
|
283
|
+
return EU.edit(model, requests, { raw: false }).model;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`newModel` hands back uncompressed bytes, so `raw` is true on the way in and false on the way out.
|
|
287
|
+
|
|
288
|
+
**Every `CREATE_ITEM` needs its own `localId`.** Three requests sharing one id collapse into a single
|
|
289
|
+
item, with no error and no warning. The first attempt at this produced 1 item from 3 requests, which
|
|
290
|
+
would have queued a proposal for one column out of four. Count from 1, and leave `localId: 0` for the
|
|
291
|
+
`UPDATE_METADATA` request that carries the envelope.
|
|
292
|
+
|
|
293
|
+
### Writing it to the platform
|
|
294
|
+
|
|
295
|
+
Three calls, in this order: create the visible index if it does not exist, upload the payload as a
|
|
296
|
+
hidden child of it, then write the entry including the id the upload returned.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const created = await client.createFile({
|
|
300
|
+
file, name: "revitflow_ops.json",
|
|
301
|
+
parentFolderId: folderId, // parentFolderId, NOT folderId
|
|
302
|
+
projectId, // required
|
|
303
|
+
versionTag: "1",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const { hiddenFileId } = await client.createHiddenFile(
|
|
307
|
+
new File([payload], `${opId}.frag`), opsId,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
await client.updateFile(opsId, { file, versionTag: `v-${Date.now()}` });
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Two failures paid for, both at runtime, both from code that compiled and read fine:
|
|
314
|
+
|
|
315
|
+
- **"A projectId is required to upload files."** The folder field was misnamed at the same time and
|
|
316
|
+
said nothing at all, because an unknown key is dropped in silence. Fix the `projectId` alone and
|
|
317
|
+
the queue gets created at the root of the project, where nothing looks for it.
|
|
318
|
+
- **"Duplicated entry"** on the update. The version tag was numbered by counting the entries: the
|
|
319
|
+
file is created with tag `"1"`, the first entry makes the list one long, and the update asks for
|
|
320
|
+
`"1"` again. Counting is wrong past that too, since removing an op makes the next one reuse a tag.
|
|
321
|
+
Use a timestamp. This is what the Revit add-in settled on.
|
|
322
|
+
|
|
323
|
+
Miss the id `createHiddenFile` returns and the payload is unreachable forever: hidden files have no
|
|
324
|
+
queryable name, so the visible index is the only place it can be written down. If the index write
|
|
325
|
+
fails after the upload, that payload is orphaned. Harmless, invisible, and worth knowing.
|
|
326
|
+
|
|
327
|
+
### Do not learn the format from files on the machine
|
|
328
|
+
|
|
329
|
+
`PlatformClient.fromPlatformContext()` exists in the app, not in a script, so a Node one-liner cannot
|
|
330
|
+
read back what Rhino just published. The tempting fallback is the `.frag` files lying around under
|
|
331
|
+
`%APPDATA%\ThatOpen` and in `flow-reconcile`. They are somebody's earlier session. One of them held
|
|
332
|
+
four points on a layer called `Columnas`, left over from an older run, and reading it as current
|
|
333
|
+
would have built a converter against a layer name nobody was using any more.
|
|
334
|
+
|
|
335
|
+
The tables above are here so nobody has to do that. If you still want to see real bytes, publish
|
|
336
|
+
something small and read it back inside the app.
|
|
337
|
+
|
|
338
|
+
## 6. Accept it in Revit
|
|
339
|
+
|
|
340
|
+
The Revit add-in's local API, `%APPDATA%\ThatOpen\revit-addin.json`, header `X-RevitFlow-Token`.
|
|
341
|
+
|
|
342
|
+
**List what is waiting:**
|
|
343
|
+
```
|
|
344
|
+
POST /ops
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Check one before offering it:**
|
|
348
|
+
```
|
|
349
|
+
POST /op-check { "opId": "op-…" }
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
It answers with the version the proposal was authored against and the version the model is on now.
|
|
353
|
+
A proposal does not go stale — it says "move this BY two feet", not "put it at this coordinate" — but
|
|
354
|
+
show the user both numbers and let them decide.
|
|
355
|
+
|
|
356
|
+
**Apply it, once they have said yes:**
|
|
357
|
+
```
|
|
358
|
+
POST /apply-ops { "opId": "op-…" }
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Everything in the proposal happens in **one** Revit transaction: one undo, one entry in the history.
|
|
362
|
+
There is also a **Proposals** button on the ribbon, which shows the queue and lets a person accept or
|
|
363
|
+
refuse. Prefer it when there is somebody at the machine — accepting somebody else's change is a
|
|
364
|
+
decision, and a decision wants the thing being decided in front of you.
|
|
365
|
+
|
|
366
|
+
The change is in their local only. They still sync.
|
|
367
|
+
|
|
368
|
+
## 7. Sync, and look at it
|
|
369
|
+
|
|
370
|
+
Revit's own Synchronize with Central. The add-in rides it: the work reaches the team and the commit
|
|
371
|
+
lands in the history, with the new columns in it, viewable in the app like any other change.
|
|
372
|
+
|
|
373
|
+
## Traps, all of them paid for
|
|
374
|
+
|
|
375
|
+
- **Rhino must be closed to install or update the plug-in.** It holds its own files, and a partial
|
|
376
|
+
copy leaves a plug-in that is neither version.
|
|
377
|
+
- **`script` deadlocks on any command that asks a question.** Use the endpoints.
|
|
378
|
+
- **Units are the document's**, everywhere. Ask `POST /status` rather than assuming metres.
|
|
379
|
+
- **The converter reads points, not solids.** Somebody modelling a beautiful steel section will
|
|
380
|
+
convert nothing at all, with no error to explain it.
|
|
381
|
+
- **Do not publish what you imported.** If the item count is in the hundreds, reference geometry is
|
|
382
|
+
going back up.
|
|
383
|
+
- **Group before concluding anything about the model.** The first rows of a listing are a sample,
|
|
384
|
+
and on this project the sample was the exception twice running.
|
|
385
|
+
- **Every `CREATE_ITEM` needs its own `localId`.** Sharing one collapses them into a single item,
|
|
386
|
+
silently. Four columns asked for, one column proposed.
|
|
387
|
+
- **`parentFolderId`, not `folderId`, and `projectId` alongside it.** An unknown key is dropped
|
|
388
|
+
without a word, so only half the mistake reports itself.
|
|
389
|
+
- **Version tags are timestamps, not counts.** Numbering by how many entries there are collides on
|
|
390
|
+
the very first update.
|
|
391
|
+
- **Stray `.frag` files on the machine are not the current format.** They are an earlier session.
|
package/package.json
CHANGED
package/resources/AGENTS.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# Building on the That Open Platform — Agent Guide
|
|
2
|
-
|
|
3
|
-
You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
|
|
4
|
-
That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).
|
|
5
|
-
|
|
6
|
-
**Read this file first, then immediately load all indexes listed below before doing anything else.**
|
|
7
|
-
|
|
8
|
-
> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
|
|
9
|
-
> references ship inside the `@thatopen/services` package under `docs/`.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Session start — load all indexes in parallel
|
|
14
|
-
|
|
15
|
-
Do this before answering any question or writing any code. These are compact description indexes:
|
|
16
|
-
|
|
17
|
-
| What | Where |
|
|
18
|
-
|---|---|
|
|
19
|
-
| Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
|
|
20
|
-
| Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
|
|
21
|
-
| CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
|
|
22
|
-
| Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
|
|
23
|
-
| Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
|
|
24
|
-
| UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
|
|
25
|
-
|
|
26
|
-
Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
|
|
27
|
-
|
|
28
|
-
> **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## How to work
|
|
33
|
-
|
|
34
|
-
- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
|
|
35
|
-
**Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
|
|
36
|
-
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
|
|
37
|
-
- If something already exists in the indexes, use it — don't reimplement it.
|
|
38
|
-
|
|
39
|
-
## Hard rules (always apply)
|
|
40
|
-
|
|
1
|
+
# Building on the That Open Platform — Agent Guide
|
|
2
|
+
|
|
3
|
+
You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
|
|
4
|
+
That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).
|
|
5
|
+
|
|
6
|
+
**Read this file first, then immediately load all indexes listed below before doing anything else.**
|
|
7
|
+
|
|
8
|
+
> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
|
|
9
|
+
> references ship inside the `@thatopen/services` package under `docs/`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Session start — load all indexes in parallel
|
|
14
|
+
|
|
15
|
+
Do this before answering any question or writing any code. These are compact description indexes:
|
|
16
|
+
|
|
17
|
+
| What | Where |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
|
|
20
|
+
| Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
|
|
21
|
+
| CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
|
|
22
|
+
| Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
|
|
23
|
+
| Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
|
|
24
|
+
| UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
|
|
25
|
+
|
|
26
|
+
Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
|
|
27
|
+
|
|
28
|
+
> **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## How to work
|
|
33
|
+
|
|
34
|
+
- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
|
|
35
|
+
**Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
|
|
36
|
+
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
|
|
37
|
+
- If something already exists in the indexes, use it — don't reimplement it.
|
|
38
|
+
|
|
39
|
+
## Hard rules (always apply)
|
|
40
|
+
|
|
41
41
|
1. **All UI must be built with Lit**, using the web components from `@thatopen/ui` (`BUI`) — `bim-button`, `bim-panel`, `bim-panel-section`, `bim-toolbar`, `bim-dropdown`, `bim-input`, and the rest of `packages/core`. Always consult the design system before writing any UI: `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/DESIGN.md`.
|