Skip to content

Commit 3587b3d

Browse files
agviegasclaude
andcommitted
docs: the Rhino to Revit path, written from a run that worked
The collaboration side has had a guide an assistant can follow since it was recorded. The interoperability side had nothing: flow-architecture explains what a translator is and why, and neither it nor anything else says a Rhino plug-in exists, how to install it, that it runs a local server, or what is on it. An assistant asked to "import the model into Rhino" had no way to know the capability was there at all. This is the same journey done end to end on the sample project, with the numbers from that run rather than from intent: import 907 meshes, place four columns at 450x450x3500, publish 8 items and 2.5 kB. The traps are the point, and each one cost something: - The reference model arrives on a LOCKED layer and cannot go back. A .frag is a tessellation -- what a Revit wall looks like, not what it is -- so a round trip could only return a worse copy of a model Revit already owns. - `script` runs Rhino's own command line and is the right tool for drawing, but never for a command that prompts: the import command wants a project id the first time, and a scripted command has nobody to answer, so Rhino sits there with no error at all. - Sizes and coordinates are the DOCUMENT's units. The sample is in millimetres, where a 0.45 column is an invisible splinter. - The converter reads POINTS, not solids. Somebody modelling a beautiful steel section converts nothing, with nothing on screen to explain it. - Do not publish what you imported. The first publish sent up 915 items and 3.4 MB, of which 907 were the building the columns were drawn against. - Group before concluding anything about a model. Taking the column section from the first rows of a listing gave 750x750 based at -2500 twice running; those were the basement, four of seventy. The building's column is 450x450 at Z 0. There is no "make a column" endpoint and the guide does not ask for one. It shows the Rhino commands, the worked four-column sequence, and a table so the same shape covers whatever somebody actually wants. Teaching the plug-in what a column is would put an opinion about buildings in the one layer whose job is to move data without having any. Also: the History layout in the collaboration guide now names at-icons:git-branch, and links here from the "changes from another tool" section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent fe5dd67 commit 3587b3d

2 files changed

Lines changed: 272 additions & 0 deletions

File tree

docs/revit-collab-quickstart.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ contract, and **[the architecture notes](https://raw.githubusercontent.com/ThatO
266266
Publishing a proposal is the smallest of the three jobs described there: it needs no ownership, no
267267
history, and no collaboration layer.
268268

269+
If the tool is **Rhino**, the whole path is already written and worked through end to end:
270+
**[Rhino → Revit, start to finish](https://raw.githubusercontent.com/ThatOpen/platform_services/main/docs/rhino-interop-quickstart.md)**. Read that
271+
instead of assembling it from the two documents above — it covers installing the plug-in, driving
272+
Rhino without a keyboard, and the traps that cost a night.
273+
269274
**This is where you use the add-in's local API** — the CLI has no proposal commands yet.
270275

271276
**List what is waiting:**
@@ -353,6 +358,9 @@ answered both, and the answer is written in `app.layouts` right there in `main.t
353358
A panel that opens on the opposite side from every other one, at a different size, is the first
354359
thing anybody notices, and it reads as bolted on however good the contents are.
355360

361+
For the layout's `icon`, use **`at-icons:git-branch`** — That Open's own icon set, and the branch
362+
mark is what the panel actually shows.
363+
356364
What makes it a history viewer rather than an empty viewer is a built-in component that already
357365
exists, so nobody writes this part:
358366

docs/rhino-interop-quickstart.md

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
## 6. Accept it in Revit
219+
220+
The Revit add-in's local API, `%APPDATA%\ThatOpen\revit-addin.json`, header `X-RevitFlow-Token`.
221+
222+
**List what is waiting:**
223+
```
224+
POST /ops
225+
```
226+
227+
**Check one before offering it:**
228+
```
229+
POST /op-check { "opId": "op-…" }
230+
```
231+
232+
It answers with the version the proposal was authored against and the version the model is on now.
233+
A proposal does not go stale — it says "move this BY two feet", not "put it at this coordinate" — but
234+
show the user both numbers and let them decide.
235+
236+
**Apply it, once they have said yes:**
237+
```
238+
POST /apply-ops { "opId": "op-…" }
239+
```
240+
241+
Everything in the proposal happens in **one** Revit transaction: one undo, one entry in the history.
242+
There is also a **Proposals** button on the ribbon, which shows the queue and lets a person accept or
243+
refuse. Prefer it when there is somebody at the machine — accepting somebody else's change is a
244+
decision, and a decision wants the thing being decided in front of you.
245+
246+
The change is in their local only. They still sync.
247+
248+
## 7. Sync, and look at it
249+
250+
Revit's own Synchronize with Central. The add-in rides it: the work reaches the team and the commit
251+
lands in the history, with the new columns in it, viewable in the app like any other change.
252+
253+
## Traps, all of them paid for
254+
255+
- **Rhino must be closed to install or update the plug-in.** It holds its own files, and a partial
256+
copy leaves a plug-in that is neither version.
257+
- **`script` deadlocks on any command that asks a question.** Use the endpoints.
258+
- **Units are the document's**, everywhere. Ask `POST /status` rather than assuming metres.
259+
- **The converter reads points, not solids.** Somebody modelling a beautiful steel section will
260+
convert nothing at all, with no error to explain it.
261+
- **Do not publish what you imported.** If the item count is in the hundreds, reference geometry is
262+
going back up.
263+
- **Group before concluding anything about the model.** The first rows of a listing are a sample,
264+
and on this project the sample was the exception twice running.

0 commit comments

Comments
 (0)