@thatopen/services 0.3.14 → 0.3.16

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.
Files changed (26) hide show
  1. package/dist/built-in/index.d.ts +165 -2
  2. package/dist/cli/commands/revit.d.ts +1 -1
  3. package/dist/cli/lib/revit-addin.d.ts +1 -1
  4. package/dist/cli.js +24 -13
  5. package/dist/index.cjs.js +2 -2
  6. package/dist/index.es.js +46 -36
  7. package/docs/ai-quickstart.md +130 -130
  8. package/docs/builtin/paths.json +94 -86
  9. package/docs/cloud/GaussianSplatConverter.md +43 -43
  10. package/docs/cloud/PointCloudConverter.md +53 -53
  11. package/docs/revit-collab-quickstart.md +256 -205
  12. package/package.json +1 -1
  13. package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -0
  14. package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -160
  15. package/src/built-in/UIManager/src/models-list/example.ts +122 -122
  16. package/src/built-in/UIManager/src/topics-panel/example.ts +170 -0
  17. package/src/built-in/index.ts +1048 -881
  18. package/src/cli/templates/app/README.md +217 -217
  19. package/src/cli/templates/app/src/app.ts +37 -37
  20. package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
  21. package/src/cli/templates/app/src/main.ts +199 -199
  22. package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
  23. package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
  24. package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
  25. package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
  26. package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
@@ -370,6 +370,11 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
370
370
  success: boolean;
371
371
  error?: string;
372
372
  }>;
373
+ /** Fires when `_derivedFileIds` finishes a (re)sync — consumers that filter
374
+ * or resolve by source↔derived link (e.g. `top-files-panel` hiding an
375
+ * already-linked `.frag` row) should reload on this, since it can complete
376
+ * after `onFilesChanged` already fired with the (still unsynced) list. */
377
+ readonly onDerivedFileIdsChanged: OBC.Event<Record<string, string>>;
373
378
  readonly conversionProgress: Map<string, number>;
374
379
  enabled: boolean;
375
380
  projectName: string;
@@ -390,6 +395,14 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
390
395
  }[];
391
396
  get viewableExtensions(): Set<string>;
392
397
  private _files;
398
+ /** ifcFileId → its linked derivedFileId (FRAG today; other source formats
399
+ * once they get a converter too), read from each IFC's own version
400
+ * metadata. Kept as an in-memory cache — synced by `_syncDerivedFileIds`
401
+ * whenever `_files` is (re)set, or right after a conversion succeeds —
402
+ * rather than fetched live per call, so `resolveViewFile` can stay a plain
403
+ * synchronous lookup (same contract as before, no ripple into
404
+ * `sendMachineEvent`'s callers). */
405
+ private _derivedFileIds;
393
406
  private _folders;
394
407
  private _fileHistory;
395
408
  private _metadataSchema;
@@ -423,6 +436,8 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
423
436
  get machineState(): CDEMachineState;
424
437
  get files(): CDEFile[];
425
438
  set files(value: CDEFile[]);
439
+ /** ifcFileId → derivedFileId — see `_derivedFileIds`. */
440
+ get derivedFileIds(): Record<string, string>;
426
441
  get folders(): ItemFolder[];
427
442
  set folders(value: ItemFolder[]);
428
443
  get showArchived(): boolean;
@@ -463,10 +478,12 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
463
478
  loadUserConfig(): Promise<{
464
479
  visibleColumns?: string[];
465
480
  metadataViews?: CDEMetadataView[];
481
+ hideDerivedFiles?: boolean;
466
482
  } | null>;
467
483
  saveUserConfig(config: {
468
484
  visibleColumns?: string[];
469
485
  metadataViews?: CDEMetadataView[];
486
+ hideDerivedFiles?: boolean;
470
487
  }): Promise<string | undefined>;
471
488
  loadMetadataSchema(): Promise<void>;
472
489
  initMetadataViews(views: CDEMetadataView[], activeId: string | null): void;
@@ -481,7 +498,23 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
481
498
  file: CDEFile;
482
499
  existing: CDEFile;
483
500
  }[];
501
+ /** Which file to actually load when the user views `file` — its linked FRAG
502
+ * if it has one, else the IFC itself. Reads `_derivedFileIds` (the IFC's
503
+ * own version metadata, kept in sync — see `_syncDerivedFileIds`), NOT a
504
+ * name+folder match anymore. Stays synchronous: `_derivedFileIds` is a
505
+ * pre-synced in-memory cache, so this is a plain lookup, keeping the
506
+ * contract every existing caller (`sendMachineEvent`'s `VIEW` case,
507
+ * `top-files-panel`'s "add to view" actions) already relies on. */
484
508
  resolveViewFile(file: CDEFile): CDEFile;
509
+ /** Populates `_derivedFileIds` from every IFC's own version metadata — the
510
+ * source of truth for the IFC↔FRAG link (same mechanism as `models-list`'s
511
+ * `_syncDerivedFileIds`). Reads the unified `derivedFileId` key, falling
512
+ * back to the legacy `fragmentsFileId` key that `IfcFragmenter` still
513
+ * stamps until it's updated (see issue #3, Task 3). Called whenever
514
+ * `_files` is (re)set and right after a conversion succeeds — never
515
+ * cached indefinitely, so a reconversion or manual metadata edit is picked
516
+ * up on the next sync instead of going stale. */
517
+ private _syncDerivedFileIds;
485
518
  private _saveConversionIndex;
486
519
  private _loadConversionIndex;
487
520
  private _resumePendingConversions;
@@ -610,6 +643,59 @@ export interface MarkerGroup {
610
643
  count: number;
611
644
  status?: ClashStatus;
612
645
  }
646
+ /** `clashes.frag`'s path relative to `__project_data/`, for consumers that
647
+ * persist it via `projectStorageContext` instead of talking to `client`
648
+ * directly (see `buildClashBuffer`). */
649
+ export declare const CLASH_DATA_PATH = "collider/clashes.frag";
650
+ export interface ExecutionEntry {
651
+ jobId: string;
652
+ executionId: string;
653
+ startedAt: string;
654
+ }
655
+ /** `cachedFileId`, when given, skips folder/file resolution (3 of the 4
656
+ * network round-trips this normally takes) — the file's ID never changes
657
+ * once created, so callers that already know it (see `ClashesManager`'s
658
+ * `_clashFragFileId`) should always pass it. */
659
+ export declare function ensureClashFragFileId(client: PlatformClient, projectId: string, cachedFileId?: string): Promise<string>;
660
+ export declare function getExecutions(client: PlatformClient, projectId: string | undefined): Promise<ExecutionEntry[]>;
661
+ export interface ClashData {
662
+ jobs: ClashJob[];
663
+ runs: ClashRun[];
664
+ clashes: Clash[];
665
+ queries: ClashQuery[];
666
+ matrices: ClashMatrix[];
667
+ /** Resolved once here — callers should cache and pass back into
668
+ * `loadClashData`/`buildClashBuffer` as `cachedFileId` to skip
669
+ * re-resolving it (see `ensureClashFragFileId`). */
670
+ fileId: string;
671
+ }
672
+ export declare function loadClashData(client: PlatformClient, projectId: string, cachedFileId?: string): Promise<ClashData>;
673
+ /**
674
+ * Diffs `data` against the persisted `clashes.frag` and builds the updated
675
+ * buffer, WITHOUT uploading it — callers decide how/where to persist the
676
+ * result (direct `client.updateFile`, via `projectStorageContext`, etc).
677
+ * Still reads the current file via `client` to diff against (job/id
678
+ * reconciliation needs the previous state), same as `saveClashData` always did.
679
+ */
680
+ export declare function buildClashBuffer(client: PlatformClient, projectId: string, data: {
681
+ queries: ClashQuery[];
682
+ matrices: ClashMatrix[];
683
+ jobs: ClashJob[];
684
+ clashes: Clash[];
685
+ }, onJobsReconciled: (toAdd: ClashJob[], toRemove: Set<string>) => void, cachedFileId?: string): Promise<{
686
+ blob: Blob;
687
+ fileId: string;
688
+ }>;
689
+ /** Default persistence path: builds the buffer and uploads it straight to
690
+ * `client`. Kept as the manager's out-of-the-box behavior for any consumer
691
+ * that never overrides `ClashesManager.save` (e.g. doesn't route through
692
+ * `projectStorageContext`). */
693
+ export declare function saveClashData(client: PlatformClient, projectId: string, data: {
694
+ queries: ClashQuery[];
695
+ matrices: ClashMatrix[];
696
+ jobs: ClashJob[];
697
+ clashes: Clash[];
698
+ }, onJobsReconciled: (toAdd: ClashJob[], toRemove: Set<string>) => void): Promise<void>;
613
699
  declare class _ClashesManager extends OBC.Component {
614
700
  static readonly uuid: "2c9f4e8d-3a1b-4c5e-9d7f-0a2b6c8e1d3f";
615
701
  enabled: boolean;
@@ -655,6 +741,10 @@ declare class _ClashesManager extends OBC.Component {
655
741
  get ready(): boolean;
656
742
  private _client?;
657
743
  private _projectId?;
744
+ /** `clashes.frag`'s file ID, resolved once by `init()`/`reload()`'s
745
+ * `loadClashData` call. Never changes once the file exists — passing it
746
+ * into `buildClashBuffer` skips 3 of its 4 network round-trips. */
747
+ private _clashFragFileId?;
658
748
  private _reconnectingJobIds;
659
749
  private _queries;
660
750
  private _matrices;
@@ -670,9 +760,25 @@ declare class _ClashesManager extends OBC.Component {
670
760
  get matrices(): ClashMatrix[];
671
761
  set matrices(value: ClashMatrix[]);
672
762
  private _scheduleSave;
763
+ /** Chains onto any save already in flight instead of firing a second,
764
+ * concurrent `_doSave()` — two overlapping calls would race on
765
+ * `buildClashBuffer`'s read-diff-reconcile cycle (real network I/O;
766
+ * `onJobsReconciled` mutating `this.jobs` from two stale reads at once)
767
+ * and on the upload itself. It also keeps `flush()` correct: it just
768
+ * awaits `_savePromise`, so if that got overwritten by a newer, unrelated
769
+ * save instead of chained, flush() could resolve while an earlier save
770
+ * was still genuinely in flight. */
771
+ private _queueSave;
673
772
  private _doSave;
674
773
  /** Cancela el debounce pendiente y ejecuta el save inmediatamente. Espera a que complete. */
675
774
  flush(): Promise<void>;
775
+ /**
776
+ * Builds the current `clashes.frag` buffer (diffed/reconciled against what's
777
+ * persisted) WITHOUT uploading it. For consumers (e.g. `clashes-panel`) that
778
+ * override `save` to persist via `projectStorageContext` instead of the
779
+ * default direct-`client` path.
780
+ */
781
+ buildClashBuffer(): Promise<Blob>;
676
782
  get jobs(): ClashJob[];
677
783
  set jobs(value: ClashJob[]);
678
784
  get runs(): ClashRun[];
@@ -681,8 +787,12 @@ declare class _ClashesManager extends OBC.Component {
681
787
  set clashes(value: Clash[]);
682
788
  queryById(id: string): ClashQuery | undefined;
683
789
  private _clashStylesRegistered;
684
- private readonly STYLE_A;
685
- private readonly STYLE_B;
790
+ /** Green — elementA's highlight color. Public so consumers (e.g. "create
791
+ * topic from clash") can build a matching BCF viewpoint `componentColors`
792
+ * entry without duplicating the hex value. */
793
+ readonly STYLE_A = "#22c55e";
794
+ /** Red — elementB's highlight color. Same reasoning as `STYLE_A`. */
795
+ readonly STYLE_B = "#ef4444";
686
796
  private _clashStyle;
687
797
  highlightAllClashes(clashes: Clash[]): void;
688
798
  ghostClashes(clashes: Clash[]): Promise<void>;
@@ -863,6 +973,59 @@ export type HelloWorld = InstanceType<typeof _HelloWorld>;
863
973
  export declare const HelloWorld: typeof _HelloWorld & {
864
974
  uuid: "2c4ae432-fc24-43e9-9783-0c960c674e96";
865
975
  };
976
+ /** `main.bcf`'s path relative to `__project_data/` — shared with
977
+ * `requestBcfSave` (`UIManager/src/utils/bcf-storage.ts`) so both sides of
978
+ * the save/load round-trip agree on where the file lives. */
979
+ export declare const BCF_DATA_PATH = "topics/main.bcf";
980
+ /** Downloads `__project_data/topics/main.bcf` and loads it into `BCFTopics`,
981
+ * if it already has content. A freshly-created file is empty (0 bytes) —
982
+ * not a valid BCF zip — so there's nothing to load yet in that case. */
983
+ export declare function loadBcfTopics(client: PlatformClient, projectId: string, components: OBC.Components): Promise<void>;
984
+ /**
985
+ * Wires up everything `top-topics-panel` (and `clashes-panel`'s "create
986
+ * topic from clash" feature) needs to work out of the box: `BCFTopics`
987
+ * configuration (version, author, assignable users — all from real project
988
+ * data), loading whatever BCF was previously saved for this project, and
989
+ * resolving `Viewpoints.world` — including backfilling it onto viewpoints
990
+ * restored from a saved BCF before the 3D world existed yet (`top-viewer`
991
+ * only creates its `World` once `top-app` finishes mounting, which is later
992
+ * than `init()` typically runs).
993
+ */
994
+ declare class _TopicsManager extends OBC.Component {
995
+ static readonly uuid: "69af63aa-19ba-4fc7-b17b-75647bac1d76";
996
+ enabled: boolean;
997
+ /**
998
+ * Consumer apps call this once — same pattern as `ClashesManager.init(client)`
999
+ * — instead of wiring `BCFTopics.setup()`, loading the saved BCF, and
1000
+ * backfilling viewpoint worlds by hand.
1001
+ */
1002
+ init(client: PlatformClient): Promise<void>;
1003
+ private _firstWorld;
1004
+ }
1005
+ /**
1006
+ * Wires up everything `top-topics-panel` (and `clashes-panel`'s "create
1007
+ * topic from clash" feature) needs to work out of the box: `BCFTopics`
1008
+ * configuration (version, author, assignable users — all from real project
1009
+ * data), loading whatever BCF was previously saved for this project, and
1010
+ * resolving `Viewpoints.world` — including backfilling it onto viewpoints
1011
+ * restored from a saved BCF before the 3D world existed yet (`top-viewer`
1012
+ * only creates its `World` once `top-app` finishes mounting, which is later
1013
+ * than `init()` typically runs).
1014
+ */
1015
+ export type TopicsManager = InstanceType<typeof _TopicsManager>;
1016
+ /**
1017
+ * Wires up everything `top-topics-panel` (and `clashes-panel`'s "create
1018
+ * topic from clash" feature) needs to work out of the box: `BCFTopics`
1019
+ * configuration (version, author, assignable users — all from real project
1020
+ * data), loading whatever BCF was previously saved for this project, and
1021
+ * resolving `Viewpoints.world` — including backfilling it onto viewpoints
1022
+ * restored from a saved BCF before the 3D world existed yet (`top-viewer`
1023
+ * only creates its `World` once `top-app` finishes mounting, which is later
1024
+ * than `init()` typically runs).
1025
+ */
1026
+ export declare const TopicsManager: typeof _TopicsManager & {
1027
+ uuid: "69af63aa-19ba-4fc7-b17b-75647bac1d76";
1028
+ };
866
1029
  declare class _UIManager extends OBC.Component {
867
1030
  #private;
868
1031
  static readonly uuid: "234f1416-528d-452a-9cc9-a16c5239b2eb";
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
 
3
3
  /**
4
4
  * `thatopen revit ...` — Revit collaboration commands. They drive the local
5
- * That Open Revit add-in (BT3): publish a shared central, join one, and sync.
5
+ * That Open Revit add-in (revit-flow): publish a shared central, join one, and sync.
6
6
  * Auth comes from `thatopen login` (~/.thatopen/config.json) and is forwarded to
7
7
  * the add-in, so the user never re-enters a token here.
8
8
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Talks to the local That Open Revit add-in (BT3). The add-in runs a
2
+ * Talks to the local That Open Revit add-in (revit-flow). The add-in runs a
3
3
  * 127.0.0.1 command listener and, on startup, writes its port + token to
4
4
  * %APPDATA%\ThatOpen\revit-addin.json so the CLI can discover it. Revit-specific
5
5
  * work (create central, create local, sync) happens inside the add-in; the CLI
package/dist/cli.js CHANGED
@@ -4205,8 +4205,8 @@ class BaseXHR extends Polling {
4205
4205
  /**
4206
4206
  * Sends data.
4207
4207
  *
4208
- * @param {String} data to send.
4209
- * @param {Function} called upon flush.
4208
+ * @param {String} data - data to send.
4209
+ * @param {Function} fn - called upon flush.
4210
4210
  * @private
4211
4211
  */
4212
4212
  doWrite(data, fn) {
@@ -4959,7 +4959,7 @@ class SocketWithoutUpgrade extends Emitter {
4959
4959
  /**
4960
4960
  * Sends a packet.
4961
4961
  *
4962
- * @param {String} type: packet type.
4962
+ * @param {String} type - packet type.
4963
4963
  * @param {String} data.
4964
4964
  * @param {Object} options.
4965
4965
  * @param {Function} fn - callback function.
@@ -5194,11 +5194,12 @@ class SocketWithUpgrade extends SocketWithoutUpgrade {
5194
5194
  }
5195
5195
  let Socket$1 = class Socket extends SocketWithUpgrade {
5196
5196
  constructor(uri, opts = {}) {
5197
- const o = typeof uri === "object" ? uri : opts;
5197
+ const isOptionsOnly = typeof uri === "object";
5198
+ const o = isOptionsOnly ? { ...uri } : { ...opts };
5198
5199
  if (!o.transports || o.transports && typeof o.transports[0] === "string") {
5199
5200
  o.transports = (o.transports || ["polling", "websocket", "webtransport"]).map((transportName) => transports[transportName]).filter((t) => !!t);
5200
5201
  }
5201
- super(uri, o);
5202
+ super(isOptionsOnly ? o : uri, o);
5202
5203
  }
5203
5204
  };
5204
5205
  function url(uri, path2 = "", loc) {
@@ -5419,12 +5420,13 @@ class Encoder {
5419
5420
  class Decoder extends Emitter {
5420
5421
  /**
5421
5422
  * Decoder constructor
5422
- *
5423
- * @param {function} reviver - custom reviver to pass down to JSON.stringify
5424
5423
  */
5425
- constructor(reviver) {
5424
+ constructor(opts) {
5426
5425
  super();
5427
- this.reviver = reviver;
5426
+ this.opts = Object.assign({
5427
+ reviver: void 0,
5428
+ maxAttachments: 10
5429
+ }, typeof opts === "function" ? { reviver: opts } : opts);
5428
5430
  }
5429
5431
  /**
5430
5432
  * Decodes an encoded packet string into packet JSON.
@@ -5484,7 +5486,13 @@ class Decoder extends Emitter {
5484
5486
  if (buf != Number(buf) || str.charAt(i) !== "-") {
5485
5487
  throw new Error("Illegal attachments");
5486
5488
  }
5487
- p.attachments = Number(buf);
5489
+ const n = Number(buf);
5490
+ if (!isInteger(n) || n < 0) {
5491
+ throw new Error("Illegal attachments");
5492
+ } else if (n > this.opts.maxAttachments) {
5493
+ throw new Error("too many attachments");
5494
+ }
5495
+ p.attachments = n;
5488
5496
  }
5489
5497
  if ("/" === str.charAt(i + 1)) {
5490
5498
  const start = i + 1;
@@ -5525,7 +5533,7 @@ class Decoder extends Emitter {
5525
5533
  }
5526
5534
  tryParse(str) {
5527
5535
  try {
5528
- return JSON.parse(str, this.reviver);
5536
+ return JSON.parse(str, this.opts.reviver);
5529
5537
  } catch (e) {
5530
5538
  return false;
5531
5539
  }
@@ -5587,6 +5595,9 @@ class BinaryReconstructor {
5587
5595
  this.buffers = [];
5588
5596
  }
5589
5597
  }
5598
+ const isInteger = Number.isInteger || function(value2) {
5599
+ return typeof value2 === "number" && isFinite(value2) && Math.floor(value2) === value2;
5600
+ };
5590
5601
  function isObject(value2) {
5591
5602
  return Object.prototype.toString.call(value2) === "[object Object]";
5592
5603
  }
@@ -8213,7 +8224,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js
8213
8224
  JSZip uses the library pako released under the MIT license :
8214
8225
  https://github.com/nodeca/pako/blob/main/LICENSE
8215
8226
  */
8216
- (function(module2, exports$1) {
8227
+ (function(module2, exports) {
8217
8228
  !function(e) {
8218
8229
  module2.exports = e();
8219
8230
  }(function() {
@@ -11701,7 +11712,7 @@ async function callAddin(cmd, body = {}) {
11701
11712
  method: "POST",
11702
11713
  headers: {
11703
11714
  "Content-Type": "application/json",
11704
- "X-Bt3-Token": info.token
11715
+ "X-RevitFlow-Token": info.token
11705
11716
  },
11706
11717
  body: JSON.stringify(body),
11707
11718
  signal: AbortSignal.timeout(6e5)