@thatopen/services 0.5.0 → 0.6.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/dist/built-in/index.d.ts +184 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +5 -4
- package/docs/ai-quickstart.md +22 -0
- package/docs/builtin/paths.json +4 -0
- package/docs/flow-architecture.md +389 -0
- package/docs/flow-plugin-guide.md +337 -0
- package/docs/revit-collab-quickstart.md +294 -175
- package/package.json +1 -1
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -0
- package/src/built-in/index.ts +185 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# That Open Flow
|
|
2
|
+
|
|
3
|
+
A modular system for **collaboration, interoperability and automation** on top of That Open
|
|
4
|
+
Platform.
|
|
5
|
+
|
|
6
|
+
> Work the way you want, not the way a format tells you to.
|
|
7
|
+
|
|
8
|
+
Everything below follows from taking that literally. Most systems in this space achieve
|
|
9
|
+
interoperability by agreeing on one data model and asking everybody to move into it. This one does
|
|
10
|
+
the opposite: applications stay sovereign over their own data, and what is shared is a way of
|
|
11
|
+
recording change.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Any file, and each one collaborates its own way
|
|
16
|
+
|
|
17
|
+
The platform holds **native files** — `.rvt`, `.3dm`, whatever an application actually saves.
|
|
18
|
+
Nothing is converted on the way in.
|
|
19
|
+
|
|
20
|
+
Each **file** has its own collaboration system. Note: a file, not a format. Two Rhino models in two
|
|
21
|
+
projects can be collaborated on in two different ways, by the same person, on the same day, because
|
|
22
|
+
what suits a five-person structural team does not suit somebody sketching alone.
|
|
23
|
+
|
|
24
|
+
There is exactly one rule:
|
|
25
|
+
|
|
26
|
+
> **Collaboration is based on a history, and every history follows the same contract.**
|
|
27
|
+
|
|
28
|
+
That is the whole of the shared surface. Everything above it is free.
|
|
29
|
+
|
|
30
|
+
Two consequences, and they are the point of the design:
|
|
31
|
+
|
|
32
|
+
1. **Applications with incompatible needs coexist.** Revit needs a very specific collaboration
|
|
33
|
+
system, because Revit worksharing is a specific thing: a central file, element-level ownership,
|
|
34
|
+
a merge engine. Rhino needs none of that. Neither has to pretend to be the other.
|
|
35
|
+
2. **A project chooses.** Ownership claimed up front, or nothing locked at all, or something in
|
|
36
|
+
between. The history contract does not care which, and the platform reads all of them the same
|
|
37
|
+
way.
|
|
38
|
+
|
|
39
|
+
### The contract
|
|
40
|
+
|
|
41
|
+
A history is a list of commits. Each commit says:
|
|
42
|
+
|
|
43
|
+
| Field | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `guid` | What the commit IS. Identity. Never reused. |
|
|
46
|
+
| `parents` | The commits it was built on. Absent on a root. First is the line it continues. |
|
|
47
|
+
| `author`, `timestamp` | Who and when. |
|
|
48
|
+
| `changes` | Which items changed, and how: `create`, `update`, `delete`. |
|
|
49
|
+
| `counts` | Derived totals, so a reader need not walk the changes to summarise. |
|
|
50
|
+
| `version` | Where it landed in the CDE, when there IS such a place. Data, never identity. |
|
|
51
|
+
|
|
52
|
+
Two rules about reading it, and they are what let an application unlike Revit take part at all:
|
|
53
|
+
|
|
54
|
+
- **Order comes from the graph, not from a number.** Follow `parents`; break ties by timestamp.
|
|
55
|
+
Asking "which came first" of an integer forces every application to invent a sequence, and only
|
|
56
|
+
something like Revit's central — which serialises everybody through one line — can honestly
|
|
57
|
+
produce one.
|
|
58
|
+
- **`version` is never a name.** It is where the commit was stored, not what it is. An application
|
|
59
|
+
with no version counter simply omits it.
|
|
60
|
+
|
|
61
|
+
`parents` is a **list** because a history that can only be a line is a different thing from a graph.
|
|
62
|
+
A fork is two commits naming the same parent; a merge is one commit naming two. Revit will never
|
|
63
|
+
write more than one — its central is a single line and it merges inside itself — and the list exists
|
|
64
|
+
precisely for the applications that are not Revit.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. What a history buys
|
|
69
|
+
|
|
70
|
+
**Traceability.** Who changed what, when. The history of a single element, across every commit that
|
|
71
|
+
touched it.
|
|
72
|
+
|
|
73
|
+
**Work is not lost.** The platform stores the native files as content-addressed blocks, and blocks
|
|
74
|
+
are only ever added. Every state the model has ever been in is still there, and a commit's map of
|
|
75
|
+
which blocks made up which file is stored with it, so a past state can be rebuilt.
|
|
76
|
+
|
|
77
|
+
**One timeline, or many.** Each file has its own history. The platform can show them together,
|
|
78
|
+
filtered and searched as one.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 3. Native data, out in the open
|
|
83
|
+
|
|
84
|
+
Every application on the platform publishes its data openly: readable **without opening the
|
|
85
|
+
proprietary application**.
|
|
86
|
+
|
|
87
|
+
This is what makes everything in the next section possible. A translator, a script or a model runs
|
|
88
|
+
with Revit closed and Rhino closed; if the only way to know what is in a model were to open the
|
|
89
|
+
application that wrote it, none of it could exist.
|
|
90
|
+
|
|
91
|
+
The constraint that follows is worth stating, because it decides where fixes go: **anything reading
|
|
92
|
+
a model cannot ask its application a question.** Every fact it needs has to be in what that model
|
|
93
|
+
already published. When something turns out to be missing, the fix goes in the EXPORT, never in a
|
|
94
|
+
back channel to a running application.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 4. Proposals
|
|
99
|
+
|
|
100
|
+
The other half of the history is that anyone can write into it — as a **proposal**.
|
|
101
|
+
|
|
102
|
+
A proposal is shaped exactly like a commit. It differs in three ways:
|
|
103
|
+
|
|
104
|
+
**It need not come from the native application.** Another application proposing a change
|
|
105
|
+
(interoperability), a script (automation), a model reading the project and suggesting something
|
|
106
|
+
(AI). The history does not distinguish between them, and does not need to.
|
|
107
|
+
|
|
108
|
+
**It has not happened.** A commit records a change to the native model; a proposal asks for one. It
|
|
109
|
+
is accepted or rejected. Where the format is proprietary, like Revit, a person working in that
|
|
110
|
+
application accepts it, because materialising it means editing a `.rvt` and only Revit can do that.
|
|
111
|
+
Where the format is open, acceptance can be programmatic — a cloud script can apply it with nobody
|
|
112
|
+
watching.
|
|
113
|
+
|
|
114
|
+
**It addresses published data.** A proposal is a patch over the fields an application already
|
|
115
|
+
publishes, in the terms that application already uses.
|
|
116
|
+
|
|
117
|
+
### Whoever proposes is not whoever accepts
|
|
118
|
+
|
|
119
|
+
Deliberately. Somebody working in Rhino can propose that their objects become columns in a Revit
|
|
120
|
+
model, under whatever rules they like. The person responsible for that Revit model is the one who
|
|
121
|
+
decides whether it happens.
|
|
122
|
+
|
|
123
|
+
That separation is what makes it safe to let anything author a proposal — including a model that
|
|
124
|
+
learned the pattern from the project's own history. Nothing needs write access to a native file to
|
|
125
|
+
take part.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 5. Two layers, in this order
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
interoperability deferred edits proposed by anyone
|
|
133
|
+
─────────────────────────
|
|
134
|
+
collaboration who owns what, and what counts as a commit
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Collaboration comes first.** It answers two questions for a file: who may change what, and what a
|
|
138
|
+
change becomes once made.
|
|
139
|
+
|
|
140
|
+
**Interoperability sits on top.** A proposal does not happen when it is written. It happens when
|
|
141
|
+
somebody applies it in the native tool, and that application is an ordinary change: it takes the
|
|
142
|
+
same locks, respects the same ownership, and produces a normal commit.
|
|
143
|
+
|
|
144
|
+
That ordering is why interoperability needed almost no new machinery. It borrows the layer below
|
|
145
|
+
rather than building a parallel one.
|
|
146
|
+
|
|
147
|
+
> **Writing a proposal requires nothing but the platform. Applying one requires being a participant
|
|
148
|
+
> in that file's collaboration layer.**
|
|
149
|
+
|
|
150
|
+
| Direction | What a tool must implement |
|
|
151
|
+
|---|---|
|
|
152
|
+
| Tool → model (propose a change) | Publish a proposal. Nothing else. |
|
|
153
|
+
| Model → tool (see the result) | Read a `.frag`. Nothing else. |
|
|
154
|
+
| Tool → tool (edit what it receives) | A full collaboration layer for that tool. |
|
|
155
|
+
|
|
156
|
+
Most integrations are the first two rows, and neither raises a single question about ownership or
|
|
157
|
+
merging.
|
|
158
|
+
|
|
159
|
+
What a proposal can actually ask for is a separate and longer question, and an open one: host
|
|
160
|
+
resolution, shared coordinates, type versus instance parameters, orientation, free NURBS and breps,
|
|
161
|
+
family authoring. The rule for all of it is the part that is settled: a proposal that cannot be
|
|
162
|
+
honoured is **refused with a reason, never approximated**. A plugin author meeting one of these will
|
|
163
|
+
get a refusal naming it rather than a silently wrong element.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 6. A plugin knows only its own application
|
|
168
|
+
|
|
169
|
+
> **No plugin ever mentions another application. It publishes its own data raw, and reads a
|
|
170
|
+
> `.frag`. Everything that translates between two applications lives in the platform.**
|
|
171
|
+
|
|
172
|
+
The Revit plugin has no idea Rhino exists: it receives verbs over the Revit data it already
|
|
173
|
+
publishes. The Rhino plugin must have no idea Revit exists either — it exports what Rhino has, as
|
|
174
|
+
Rhino has it, and stops. The thing that reads a Rhino model and proposes a change to a Revit one is
|
|
175
|
+
a third thing, and it lives in the platform as an app, a cloud component or a model.
|
|
176
|
+
|
|
177
|
+
The reason is arithmetic. Put translation in the plugins and every new tool has to learn about every
|
|
178
|
+
existing one: N tools, N² translators, each needing an update when either end changes. Put it in the
|
|
179
|
+
platform and a new tool implements two things that mention nobody — "publish my data" and "read a
|
|
180
|
+
`.frag`" — while translators are written per pair, by whoever needs that pair, without touching
|
|
181
|
+
either plugin.
|
|
182
|
+
|
|
183
|
+
So the shape of the whole system is: **plugins export and import their own application's data,
|
|
184
|
+
deterministically, and nothing else. The platform is where the glue lives.**
|
|
185
|
+
|
|
186
|
+
### Which is why the proposal format knows nothing either
|
|
187
|
+
|
|
188
|
+
A proposal is a set of changes over data an application already publishes, so it says three things
|
|
189
|
+
and no more: **what kind of change, which item, which fields.**
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
add no id yet, attributes describe it, transform says where it goes
|
|
193
|
+
set names an item by its published id, writes the attributes given
|
|
194
|
+
remove names an item by its published id
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The field names are not ours. `Family`, `Type`, `CategoryId` are what Revit calls its own fields in
|
|
198
|
+
what Revit publishes, and a translator copies them without needing to know what any of them mean.
|
|
199
|
+
The plugin that receives them is the only thing that does.
|
|
200
|
+
|
|
201
|
+
This was got wrong once and is easy to get wrong again. The first version defined `flow:family`,
|
|
202
|
+
`flow:type`, `flow:categoryId` and friends in the shared protocol, as though a family and a type
|
|
203
|
+
were concepts of the interoperability layer. They are not, and putting them there taught the
|
|
204
|
+
protocol about one application, which is what this whole section exists to prevent.
|
|
205
|
+
|
|
206
|
+
What IS reserved is the `flow:` **namespace**, and an applier skips all of it rather than the names
|
|
207
|
+
it happens to know — so an author can record something for its own later use without the applier
|
|
208
|
+
being taught what it means, and without it being mistaken for a parameter of the thing being
|
|
209
|
+
changed. Today that namespace holds `flow:op` (the verb) and `flow:source` (where the change came
|
|
210
|
+
from, in the author's own terms).
|
|
211
|
+
|
|
212
|
+
Identity is the item's guid and placement is the item's transform, both of which the `.frag` already
|
|
213
|
+
carries. Nothing addresses anything by position: an item is named by its id or not at all. That is
|
|
214
|
+
not a convention to be careful about, it is the only thing the format can express, which is why a
|
|
215
|
+
re-exported model can never make a proposal point somewhere else.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 7. No element is ever tied to another element
|
|
220
|
+
|
|
221
|
+
The rule most likely to be undone by accident:
|
|
222
|
+
|
|
223
|
+
> **Proposals are generic changes over a model. They never establish a correspondence between an
|
|
224
|
+
> element in one application and an element in another — not 1:1, not N:N, not at all.**
|
|
225
|
+
|
|
226
|
+
Each application owns its ids and nothing else knows what they mean. A Revit `UniqueId` identifies
|
|
227
|
+
something in that Revit model. A Rhino object GUID identifies something in that `.3dm`. Neither is
|
|
228
|
+
ever translated into the other, and no element is ever stamped with where it came from.
|
|
229
|
+
|
|
230
|
+
What IS recorded is much weaker, deliberately: a proposal records **which native ids it affected**,
|
|
231
|
+
including the ones it created. A flat list. Not which source produced which result.
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
affectedIds: [ ... ] native ids this proposal touched or created
|
|
235
|
+
appliedInCommit: 5 the commit that materialised it
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**The ids come back in the order the changes were asked for.** A proposal that sent N changes gets N
|
|
239
|
+
ids, and the i-th id is what the i-th change did. This is a promise, not an accident: the applier
|
|
240
|
+
runs its verbs in passes (adds first, so a `set` can address something the same proposal just made)
|
|
241
|
+
but each change records its own result, so the pass order never reaches the answer. Elements a
|
|
242
|
+
delete took with it are appended AFTER all of them, because they correspond to no change and putting
|
|
243
|
+
them in line would shift every id after the delete by an amount the author cannot predict.
|
|
244
|
+
|
|
245
|
+
This is the whole mechanism by which an author knows which of the things it created is which. It is
|
|
246
|
+
a proposal returning its results, the way a database returns the rows it inserted.
|
|
247
|
+
|
|
248
|
+
### Why this is enough
|
|
249
|
+
|
|
250
|
+
Because granularity is the author's lever, not ours. A converter that wants a one-to-one
|
|
251
|
+
correspondence emits **one proposal per object** — and then "the ids this proposal affected" IS the
|
|
252
|
+
correspondence, without the system ever having modelled it. A converter that emits one proposal for
|
|
253
|
+
fifty objects gets back fifty ids with no way to tell which is which, and that is the correct
|
|
254
|
+
outcome: it chose that granularity.
|
|
255
|
+
|
|
256
|
+
So a round trip works — create an object in Rhino, materialise it natively in Revit, then modify the
|
|
257
|
+
same object in Rhino and update it in Revit — without anything anywhere holding a link between the
|
|
258
|
+
two elements.
|
|
259
|
+
|
|
260
|
+
### Why it matters
|
|
261
|
+
|
|
262
|
+
This is where IFC, Speckle and every "BIM interoperability" format before them got into trouble.
|
|
263
|
+
They maintain a canonical cross-application object identity: an entity that claims to be the same
|
|
264
|
+
thing in two tools at once. Something then has to keep it alive forever — synchronise it, reconcile
|
|
265
|
+
it when it drifts, decide what it means when one tool splits that object in two or merges it with
|
|
266
|
+
another. The identity becomes a permanent maintenance surface, and it is the part that rots.
|
|
267
|
+
|
|
268
|
+
We do not have that entity, so there is nothing to keep alive.
|
|
269
|
+
|
|
270
|
+
**If a future change starts to look like a cross-application id, a mapping table, a `sourceId` field
|
|
271
|
+
on an element, or a "link" between two models: that is this rule being undone.** The question to ask
|
|
272
|
+
is which application's problem it is really solving, and whether it can live in that application
|
|
273
|
+
instead.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 8. Revit: lean on the native system, do not rebuild it
|
|
278
|
+
|
|
279
|
+
Revit already has worksharing, and it is good. The temptation is to build a collaboration layer on
|
|
280
|
+
top of it, because our central is not a live shared file and the native mechanisms therefore do not
|
|
281
|
+
reach across machines. The decision is the opposite: **be an efficient copier of a few files, and
|
|
282
|
+
let Revit do the collaborating.** A very fast file transport can be made not to break. A layer of
|
|
283
|
+
our own logic on top of a partial API breaks in the cases nobody thought of.
|
|
284
|
+
|
|
285
|
+
What made that decision possible is a set of measurements rather than an opinion. Revit stores
|
|
286
|
+
worksharing ownership in small files NEXT TO the central, never inside the `.rvt`:
|
|
287
|
+
|
|
288
|
+
| what happens | what Revit writes to the central, immediately |
|
|
289
|
+
|---|---|
|
|
290
|
+
| somebody takes a workset | `worksetperms.dat` |
|
|
291
|
+
| somebody borrows elements by editing them | `editingperms.dat`, `editingdeltas.dat` |
|
|
292
|
+
| somebody edits what they already hold | nothing |
|
|
293
|
+
| somebody saves their local | nothing |
|
|
294
|
+
|
|
295
|
+
The `.rvt` is never touched by any of it. So **replicating ownership does not move the model**, which
|
|
296
|
+
was the objection that would otherwise have killed the idea: nobody's geometry changes under their
|
|
297
|
+
feet.
|
|
298
|
+
|
|
299
|
+
Three more facts decide the design:
|
|
300
|
+
|
|
301
|
+
- **Revit re-reads those files.** Overwrite them behind a running Revit and it reports the new
|
|
302
|
+
ownership within two to three seconds, with no reload and nothing asked of it. It polls the
|
|
303
|
+
central every five to eight seconds on its own; the read locks are visible in the worksharing log.
|
|
304
|
+
- **A borrow is a two-byte write.** `editingperms.dat` is a table of 32-byte records, one per
|
|
305
|
+
element, holding a two-byte owner field: `ffff` for free, otherwise an index into `users.dat`.
|
|
306
|
+
Borrowing 208 elements changed exactly 208 two-byte cells and nothing else — no header, no
|
|
307
|
+
counter, no checksum. Which is what makes a three-way merge of two clients' files correct rather
|
|
308
|
+
than reckless: different elements are different cells, and the only cells both sides can touch are
|
|
309
|
+
the ones where they genuinely both claimed the same element.
|
|
310
|
+
- **The whole ownership state is 35 KB compressed**, so it needs no clever transport. It also must
|
|
311
|
+
not go through the block store, which would mint a model version per borrow.
|
|
312
|
+
|
|
313
|
+
So the shape is: a file watcher publishes ownership when it changes, everyone else pulls and merges
|
|
314
|
+
it, and Revit believes what it reads. The platform's own registry stays as the ARBITER for the few
|
|
315
|
+
seconds before a replicated file arrives, because that race cannot be closed by transport alone.
|
|
316
|
+
|
|
317
|
+
**What still has to be our own, and why.** Revit publishes ownership; it does not publish INTENT.
|
|
318
|
+
There is no event for taking a workset, none for borrowing an element, and no API to relinquish on
|
|
319
|
+
somebody else's behalf (`RelinquishOwnership` takes a document and no user; `Application.Username`
|
|
320
|
+
is read-only). So a claim cannot be vetoed before it happens, only observed after — which is why the
|
|
321
|
+
platform registry exists, why arbitration is after the fact, and why a conflict is resolved by
|
|
322
|
+
telling somebody rather than by preventing them.
|
|
323
|
+
|
|
324
|
+
**What we must not let happen, and how it is now handled.** A native Sync With Central writes to
|
|
325
|
+
that machine's copy of the central and, left alone, tells the platform nothing. If somebody else
|
|
326
|
+
publishes first, the next pull overwrites that copy and the work is gone while their local still
|
|
327
|
+
believes it was published.
|
|
328
|
+
|
|
329
|
+
That is why the add-in sits on both of Revit's sync events rather than beside them with a button of
|
|
330
|
+
its own. `DocumentSynchronizingWithCentral` fires before Revit touches anything: it takes the turn
|
|
331
|
+
in the team queue and brings the central up to date in place, so Revit's own merge runs against the
|
|
332
|
+
team's current file. It is cancellable, and every refusal uses it — elements held by somebody else,
|
|
333
|
+
a pull the guardrail refused, a queue the person gave up on — so a sync that cannot be published
|
|
334
|
+
safely is stopped before Revit writes rather than left half done.
|
|
335
|
+
`DocumentSynchronizedWithCentral` then publishes what was written, and nothing else: no second
|
|
336
|
+
merge, no second SWC.
|
|
337
|
+
|
|
338
|
+
Measured while building it: Revit reports the sync finished while it still holds a file in the
|
|
339
|
+
central's worksharing folder, so the publish retries with a growing wait. And replacing the central
|
|
340
|
+
files from inside the pre-event works with the model open — verified with the central rolled back a
|
|
341
|
+
version underneath a live session: 30 files rebuilt in the event, Revit's merge on top of them.
|
|
342
|
+
|
|
343
|
+
The consequence is that the add-in has no sync button, no take button and no release button. Revit's
|
|
344
|
+
own worksharing UI is the interface, and ownership replication is what makes it true for everybody.
|
|
345
|
+
|
|
346
|
+
## 9. Rhino: deliberately unanswered
|
|
347
|
+
|
|
348
|
+
revit-flow works because Revit already had worksharing. flow arbitrates on top of it; it did not
|
|
349
|
+
have to invent it.
|
|
350
|
+
|
|
351
|
+
Rhino has none of that. A `.3dm` is one file, with no central, no per-object checkout, and no merge.
|
|
352
|
+
So "collaboration in Rhino" is not a thing that exists and gets wired up — it is a thing that would
|
|
353
|
+
have to be designed and written. Which is exactly what section 1 says is allowed: the contract is
|
|
354
|
+
the history, and how a file is collaborated on underneath is open.
|
|
355
|
+
|
|
356
|
+
**We are not answering it yet, on purpose.** Rhino authoring proposals for Revit needs no
|
|
357
|
+
collaboration layer at all, and Rhino displaying a Revit model needs only a `.frag` reader. The
|
|
358
|
+
question appears when several people want to edit one Rhino model at once, and until somebody
|
|
359
|
+
actually wants that, designing it would be guessing.
|
|
360
|
+
|
|
361
|
+
Two notes from thinking it through, so the thinking is not repeated:
|
|
362
|
+
|
|
363
|
+
- **Ownership granularity is the easy half, and layers are the natural unit.** A Rhino layer is what
|
|
364
|
+
a workset is in Revit: it is how teams already divide work, every object carries one, and the
|
|
365
|
+
exporter already records it. The lock registry needs no change beyond a different key prefix.
|
|
366
|
+
- **Merging is the hard half, and it is the real cost.** Revit reconciles concurrent element edits
|
|
367
|
+
itself. Rhino will not. A central would have to be *reconstructed* by applying per-object deltas
|
|
368
|
+
rather than being a file somebody overwrites — buildable on what already exists (stable object
|
|
369
|
+
GUIDs, regenerable definitions, per-commit deltas), but that is rebuilding worksharing, not
|
|
370
|
+
configuring it.
|
|
371
|
+
|
|
372
|
+
Worth checking before any of it: whether teams actually put several people on one `.3dm` today, or
|
|
373
|
+
divide the work by file. If it is the latter, the merge may never be needed.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 10. Formats
|
|
378
|
+
|
|
379
|
+
`.frag` is the transport for both layers: models, per-commit deltas, and proposal payloads. It
|
|
380
|
+
carries stable per-item global ids (Revit `UniqueId`, Rhino object GUID) in its `guids` vector,
|
|
381
|
+
which is what lets anything address an element it did not create.
|
|
382
|
+
|
|
383
|
+
`.3dm` is readable and writable without a Rhino licence, through McNeel's open-source **openNURBS**
|
|
384
|
+
and the **rhino3dm** bindings. That covers the document and its geometry definitions but not the
|
|
385
|
+
Rhino kernel, so a converter can run server-side while real modelling operations still need Rhino.
|
|
386
|
+
|
|
387
|
+
The concrete wire formats — where each file lives on the platform, what the proposal envelope
|
|
388
|
+
contains, how the queue and the ownership registry work — are in
|
|
389
|
+
[the plugin guide](./flow-plugin-guide.md), which is what to read if you are writing a plugin.
|