reveal-3d

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Reveal 3D Viewer

Reveal 3D查看器

Add a Cognite Reveal 3D viewer to a Dune app. Renders CAD models from CDF, with support for FDM-linked assets or direct model/revision IDs.
FDM instance to visualize: $ARGUMENTS

在Dune应用中添加Cognite Reveal 3D查看器。可渲染来自CDF的CAD模型,支持关联FDM的资产或直接使用模型/版本ID。
待可视化的FDM实例:$ARGUMENTS

Before you start

开始之前

Read these files before touching anything:
  • package.json
    — note
    react
    /
    react-dom
    versions and existing deps
  • vite.config.ts
    — you will replace it entirely (new Dune apps have a standalone config, not a shared base config)
  • src/main.tsx
    — you will prepend two lines to it

在进行任何操作前,请先阅读以下文件:
  • package.json
    — 注意
    react
    /
    react-dom
    版本及已有的依赖
  • vite.config.ts
    — 你需要完全替换该文件(新版Dune应用使用独立配置,而非共享基础配置)
  • src/main.tsx
    — 你需要在文件开头添加两行代码

Step 1 — Install packages

步骤1 — 安装依赖包

bash
pnpm add @cognite/reveal three process util assert ajv
pnpm add "@cognite/dune-industrial-components@github:cognitedata/dune-industrial-components#semver:*"
pnpm add -D @types/three
pnpm install --no-frozen-lockfile
If running inside Cursor (sandbox): the GitHub package install requires
git init
, which the Cursor sandbox blocks. If you see
git init ... Operation not permitted
, the install must be run with full permissions. In a Shell tool call, pass
required_permissions: ["all"]
.
After install — check
three
version matches what
@cognite/reveal
requires:
bash
node -e "const r=require('./node_modules/@cognite/reveal/package.json'); console.log(r.peerDependencies?.three)"
node -e "console.log(require('./node_modules/three/package.json').version)"
If the installed
three
version is lower than
@cognite/reveal
's peer requirement, update it:
bash
pnpm add three@^<required-version>    # e.g. three@^0.180.0
pnpm install --no-frozen-lockfile
Verify
package.json
now has all of:
@cognite/reveal
,
three
(at the right version),
process
,
util
,
assert
,
ajv
,
@cognite/dune-industrial-components
.
Why
ajv
?
@cognite/dune-industrial-components
requires
ajv@>=8
. The monorepo root has
ajv@6
as a transitive dep. Without a direct
ajv@^8
in the app, pnpm picks up the root's v6 and you get a peer warning that can cause schema validation failures.
Do not install
vite-plugin-node-polyfills
. It introduces a different set of transitive-dep conflicts. Use explicit
process
,
util
,
assert
package aliases instead.

bash
pnpm add @cognite/reveal three process util assert ajv
pnpm add "@cognite/dune-industrial-components@github:cognitedata/dune-industrial-components#semver:*"
pnpm add -D @types/three
pnpm install --no-frozen-lockfile
如果在Cursor(沙箱)中运行: 安装GitHub包需要执行
git init
,但Cursor沙箱会阻止此操作。若出现
git init ... Operation not permitted
错误,必须使用完整权限运行安装命令。在Shell工具调用中,传入
required_permissions: ["all"]
安装后 — 检查
three
版本是否符合
@cognite/reveal
的要求:
bash
node -e "const r=require('./node_modules/@cognite/reveal/package.json'); console.log(r.peerDependencies?.three)"
node -e "console.log(require('./node_modules/three/package.json').version)"
若已安装的
three
版本低于
@cognite/reveal
的对等依赖要求,请更新版本:
bash
pnpm add three@^<required-version>    # 示例:three@^0.180.0
pnpm install --no-frozen-lockfile
验证
package.json
中已包含所有依赖:
@cognite/reveal
、符合版本要求的
three
process
util
assert
ajv
@cognite/dune-industrial-components
为什么需要
ajv
@cognite/dune-industrial-components
要求
ajv@>=8
。但 monorepo 根目录的传递依赖为
ajv@6
。若应用中未直接安装
ajv@^8
,pnpm会使用根目录的v6版本,导致对等依赖警告,进而可能引发 schema 验证失败。
请勿安装
vite-plugin-node-polyfills
。它会引入另一组传递依赖冲突。请改用显式的
process
util
assert
包别名。

Step 2 — Vite config

步骤2 — Vite配置

Read vite-config.md for the complete
vite.config.ts
. Apply it verbatim.
Key points:
  • resolve.dedupe
    includes
    react
    ,
    react-dom
    ,
    react/jsx-runtime
    ,
    three
    — pnpm symlinks can create separate module instances in a monorepo;
    dedupe
    forces one copy
  • Manual
    util/
    ,
    assert/
    ,
    process/browser
    aliases
    — not a plugin. These handle the top-level imports. The
    process
    ,
    util
    ,
    assert
    npm packages must be in
    dependencies
    (Step 1)
  • optimizeDeps.include
    lists
    process
    ,
    util
    ,
    assert
    ,
    three
    ,
    @cognite/reveal
    — pre-bundles them so esbuild handles CJS→ESM; Vite cannot auto-discover bare polyfill imports
  • worker.format: 'es'
    — Reveal spawns ES module web workers; without this they fail silently
  • Never put
    @cognite/dune-industrial-components
    in
    optimizeDeps.exclude
    — forces raw ESM, re-introduces React duplication even with dedupe

阅读vite-config.md获取完整的
vite.config.ts
代码,并完整套用。
核心要点:
  • resolve.dedupe
    包含
    react
    react-dom
    react/jsx-runtime
    three
    — 在monorepo中,pnpm的符号链接可能会创建多个模块实例;
    dedupe
    强制使用单一副本
  • 手动配置
    util/
    assert/
    process/browser
    别名
    — 不使用插件,直接处理顶层导入。
    process
    util
    assert
    npm包必须已在步骤1中添加到
    dependencies
  • optimizeDeps.include
    列出
    process
    util
    assert
    three
    @cognite/reveal
    — 预打包这些依赖,让esbuild处理CJS到ESM的转换;Vite无法自动发现裸导入的polyfill
  • worker.format: 'es'
    — Reveal会生成ES模块的Web Worker;若无此配置,Worker会静默失败
  • 切勿将
    @cognite/dune-industrial-components
    加入
    optimizeDeps.exclude
    — 这会强制使用原始ESM,即使配置了dedupe也会再次引发React重复实例问题

Step 3 — main.tsx

步骤3 — main.tsx配置

Add the
process
polyfill as the very first two lines — before any import, before React:
tsx
import process from 'process';
(window as unknown as Record<string, unknown>).process = process;

// all other imports below ↓
import { DuneAuthProvider } from '@cognite/dune';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './styles.css';

const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000 } },
});

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <DuneAuthProvider>
        <App />
      </DuneAuthProvider>
    </QueryClientProvider>
  </React.StrictMode>
);
Keep
DuneAuthProvider
from
@cognite/dune
as the auth provider — not
CDFAuthenticationProvider
.

process
polyfill添加为最开头的两行 — 早于任何import语句,早于React:
tsx
import process from 'process';
(window as unknown as Record<string, unknown>).process = process;

// 以下是所有其他导入 ↓
import { DuneAuthProvider } from '@cognite/dune';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './styles.css';

const queryClient = new QueryClient({
  defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000 } },
});

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <DuneAuthProvider>
        <App />
      </DuneAuthProvider>
    </QueryClientProvider>
  </React.StrictMode>
);
保留来自
@cognite/dune
DuneAuthProvider
作为认证提供者 — 不要使用
CDFAuthenticationProvider

Step 4 — Provider placement (critical)

步骤4 — Provider放置(关键)

Getting this wrong causes
ObjectUnsubscribedError: object unsubscribed
at model load time.
CacheProvider
and
RevealKeepAlive
must be always mounted at page/app level.
RevealProvider
is conditional (only renders when a model is selected).
App (always rendered)
  CacheProvider            ← always mounted
    RevealKeepAlive        ← always mounted
      <sidebar>            ← model picker lives here
      {selected && (
        RevealProvider     ← conditional, only when model is ready
          <RevealCanvas>
            <Reveal3DResources />
      )}
Why: React StrictMode double-invokes effects on every component at first mount. If
RevealKeepAlive
is inside the same conditionally-rendered component as
RevealProvider
, StrictMode fires both cleanup cycles together —
RevealKeepAlive
disposes the viewer while
RevealProvider
's async model-loading effect is still in-flight.
When
RevealKeepAlive
is at App level, its StrictMode cycle completes at startup with no viewer yet (nothing to dispose). By the time
RevealProvider
conditionally mounts,
RevealKeepAlive
's
viewerRef
is stable — and
RevealProvider
skips viewer disposal when keepAlive context is present.
Pattern that breaks:
{selected && (
  <MyViewerComponent>      ← ❌ RevealKeepAlive co-located with RevealProvider
    <CacheProvider>
      <RevealKeepAlive>
        <RevealProvider>

配置错误会导致模型加载时出现
ObjectUnsubscribedError: object unsubscribed
错误。
CacheProvider
RevealKeepAlive
必须始终在页面/应用级别挂载
RevealProvider
是条件渲染的(仅当模型被选中时才渲染)。
App(始终渲染)
  CacheProvider            ← 始终挂载
    RevealKeepAlive        ← 始终挂载
      <sidebar>            ← 模型选择器在此处
      {selected && (
        RevealProvider     ← 条件渲染,仅当模型就绪时
          <RevealCanvas>
            <Reveal3DResources />
      )}
原因: React StrictMode会在首次挂载时双重调用每个组件的effect。如果
RevealKeepAlive
RevealProvider
位于同一个条件渲染组件中,StrictMode会同时触发两个清理周期 —
RevealKeepAlive
会在
RevealProvider
的异步模型加载effect仍在进行时销毁查看器。
RevealKeepAlive
位于应用级别时,其StrictMode周期会在启动时完成(此时还没有查看器,无需销毁)。当
RevealProvider
条件挂载时,
RevealKeepAlive
viewerRef
已经稳定 — 并且当存在keepAlive上下文时,
RevealProvider
会跳过查看器销毁操作。
错误的写法:
{selected && (
  <MyViewerComponent>      ← ❌ RevealKeepAlive与RevealProvider放在一起
    <CacheProvider>
      <RevealKeepAlive>
        <RevealProvider>

Step 5 — Implementation

步骤5 — 实现

Decide the pattern first — before reading any code.
Use Pattern B (model browser) unless you can answer YES to all three of these:
  1. The app already has a
    DMInstanceRef
    in scope — passed in as a prop or route param, not something to be fetched
  2. The user has confirmed that instance has
    CogniteVisualizable.object3D → CogniteCADNode
    linkage in their CDF data model
  3. The user explicitly asked for FDM-linked 3D, not just "show a 3D viewer"
If any answer is NO or uncertain — use Pattern B. It works with every CDF project that has 3D models, requires zero FDM setup, and is much easier to debug. Pattern A silently renders nothing when FDM linkage is missing.
Pattern B: Read the "Pattern B (default)" section of references/implementation.md.
Pattern A (only if gate above passed): Read the "Pattern A (fallback)" section of references/implementation.md.
Two files to create:
src/components/ViewerContent.tsx
(canvas only, no providers) and
src/App.tsx
(owns all providers + model selection logic).
Critical rules that both patterns share:
  • ViewerContent
    must contain no providers
    CacheProvider
    ,
    RevealKeepAlive
    , and
    RevealProvider
    all live in
    App.tsx
    (see Step 5 for why)
  • resources
    prop for
    Reveal3DResources
    must be
    useMemo
    'd;
    onModelsLoaded
    must be
    useCallback
    'd — inline values cause infinite model reload loops
  • onSelect
    /
    onLoad
    callbacks passed into child components must be
    useCallback
    'd at the call site, and called from
    useEffect
    inside the child — not during render
  • sdk
    passed to
    RevealProvider
    must be
    useMemo
    'd keyed on
    client.project
  • Lazy-load
    ViewerContent
    with
    React.lazy
    +
    Suspense
    to avoid blocking the initial bundle

先确定实现模式 — 再阅读代码。
除非你能对以下三个问题全部回答YES,否则请使用模式B(模型浏览器)
  1. 应用中已存在
    DMInstanceRef
    作用域 — 作为props或路由参数传入,而非需要获取的内容
  2. 用户已确认其实例在CDF数据模型中存在
    CogniteVisualizable.object3D → CogniteCADNode
    关联
  3. 用户明确要求使用关联FDM的3D功能,而非仅要求“展示3D查看器”
若有任何问题答案为NO或不确定 — 使用模式B。它适用于所有拥有3D模型的CDF项目,无需任何FDM配置,且调试难度低。当FDM关联缺失时,模式A会静默不渲染任何内容。
模式B: 阅读references/implementation.md中的“Pattern B (default)”章节。
模式A(仅当满足上述所有条件时使用): 阅读references/implementation.md中的“Pattern A (fallback)”章节。
需要创建两个文件:
src/components/ViewerContent.tsx
(仅包含画布,无Provider)和
src/App.tsx
(管理所有Provider + 模型选择逻辑)。
两种模式共享的关键规则:
  • ViewerContent
    中不得包含任何Provider —
    CacheProvider
    RevealKeepAlive
    RevealProvider
    都必须放在
    App.tsx
    中(原因见步骤5)
  • Reveal3DResources
    resources
    属性必须用
    useMemo
    缓存;
    onModelsLoaded
    必须用
    useCallback
    缓存 — 内联值会导致模型无限重新加载循环
  • 传入子组件的
    onSelect
    /
    onLoad
    回调必须在调用处用
    useCallback
    缓存,并在子组件内部的
    useEffect
    中调用 — 不得在渲染阶段调用
  • 传递给
    RevealProvider
    sdk
    必须用
    useMemo
    client.project
    为键进行缓存
  • 使用
    React.lazy
    +
    Suspense
    懒加载
    ViewerContent
    ,避免阻塞初始包加载

Step 6 — Container height

步骤6 — 容器高度

RevealCanvas
fills its container with
width: 100%; height: 100%
. The parent must have an explicit height:
tsx
<div style={{ width: '100%', height: '70vh', position: 'relative' }}>
  <RevealProvider ...>
    <ViewerContent modelId={...} revisionId={...} />
  </RevealProvider>
</div>

RevealCanvas
会用
width: 100%; height: 100%
填充其父容器。父容器必须设置明确高度
tsx
<div style={{ width: '100%', height: '70vh', position: 'relative' }}>
  <RevealProvider ...>
    <ViewerContent modelId={...} revisionId={...} />
  </RevealProvider>
</div>

Troubleshooting

故障排查

SymptomCauseFix
git init ... Operation not permitted
during
pnpm install
Cursor sandbox blocks git operations needed to clone the GitHub packageRe-run
pnpm install --no-frozen-lockfile
with
required_permissions: ["all"]
in the Shell tool call
pnpm install
hangs for minutes with no output
Same GitHub package sandbox issue — pnpm is stuck waiting on a blocked syscallKill the process, re-run with
required_permissions: ["all"]
unmet peer three@0.180.0: found 0.177.0
(or similar version)
@cognite/reveal
requires a specific
three
version;
pnpm add three
installs latest which may differ
Check
@cognite/reveal
's peerDependencies, then
pnpm add three@^<required>
+ reinstall
unmet peer ajv@>=8: found 6.x
Monorepo root has
ajv@6
; app needs
ajv@^8
for
@cognite/dune-industrial-components
pnpm add ajv
in the app (adds
^8
)
ObjectUnsubscribedError: object unsubscribed
RevealKeepAlive
inside conditional component
Move
CacheProvider
+
RevealKeepAlive
to always-mounted App level
Maximum update depth exceeded
Inline
onLoad
/
onSelect
callback
(t) => setState(t)
re-creates every render
useCallback((t) => setState(t), [])
at the call site; model browser must call
onSelect
from
useEffect
, not render phase
Maximum update depth exceeded
(variant)
onSelect
/
onReady
called during render in an
if
-block instead of a
useEffect
Move the call into
useEffect([revision, pendingId, onSelect])
No QueryClient set
@tanstack/react-query
resolved to a different copy
Add
@tanstack/react-query
to
resolve.dedupe
and
optimizeDeps.include
process is not defined
at runtime
Missing runtime polyfillFirst two lines of
main.tsx
:
import process from 'process'; window.process = process;
Could not resolve "inherits"
Used
vite-plugin-node-polyfills
or wrong manual aliases
Remove the plugin; use package aliases (
util: 'util/'
,
assert: 'assert/'
) with those packages in
dependencies
Multiple instances of Three.js
Two Three.js copies loaded
resolve.alias.three
node_modules/three/build/three.module.js
and
three
in
resolve.dedupe
Black screen / workers fail silentlyMissing ES worker formatAdd
worker: { format: 'es' }
to vite config
Canvas 0px tallContainer has no explicit height
height: '70vh'
(or any fixed/flex height) on the parent div
No model found (FDM mode)Instance not linked via Core DM (
CogniteVisualizable.object3D
CogniteCADNode
)
Use model browser (Pattern B) with
sdk.models3D.list()
as the default instead

症状原因修复方案
pnpm install
时出现
git init ... Operation not permitted
Cursor沙箱阻止了克隆GitHub包所需的git操作在Shell工具调用中添加
required_permissions: ["all"]
,重新运行
pnpm install --no-frozen-lockfile
pnpm install
无输出且卡顿数分钟
同样是GitHub包沙箱问题 — pnpm卡在等待被阻止的系统调用终止进程,添加
required_permissions: ["all"]
后重新运行
出现
unmet peer three@0.180.0: found 0.177.0
(或类似版本不匹配)
@cognite/reveal
要求特定版本的
three
pnpm add three
会安装最新版本,可能与要求不符
查看
@cognite/reveal
的peerDependencies,然后执行
pnpm add three@^<required>
+ 重新安装
出现
unmet peer ajv@>=8: found 6.x
Monorepo根目录有
ajv@6
;应用需要
ajv@^8
以兼容
@cognite/dune-industrial-components
在应用中执行
pnpm add ajv
(会安装^8版本)
出现
ObjectUnsubscribedError: object unsubscribed
RevealKeepAlive
位于条件渲染组件内部
CacheProvider
+
RevealKeepAlive
移至始终挂载的应用级别
出现
Maximum update depth exceeded
内联的
onLoad
/
onSelect
回调
(t) => setState(t)
会在每次渲染时重新创建
在调用处使用
useCallback((t) => setState(t), [])
;模型浏览器必须在
useEffect
中调用
onSelect
,而非渲染阶段
出现
Maximum update depth exceeded
(变体)
onSelect
/
onReady
在渲染阶段的
if
块中被调用,而非在
useEffect
将调用移至
useEffect([revision, pendingId, onSelect])
出现
No QueryClient set
@tanstack/react-query
解析到了不同的副本
@tanstack/react-query
添加到
resolve.dedupe
optimizeDeps.include
运行时出现
process is not defined
缺少运行时polyfill
main.tsx
开头添加两行:
import process from 'process'; window.process = process;
出现
Could not resolve "inherits"
使用了
vite-plugin-node-polyfills
或错误的手动别名
移除该插件;使用包别名(
util: 'util/'
assert: 'assert/'
)并确保这些包已在
dependencies
出现
Multiple instances of Three.js
加载了两个Three.js副本设置
resolve.alias.three
node_modules/three/build/three.module.js
并将
three
加入
resolve.dedupe
黑屏 / Worker静默失败缺少ES Worker格式配置在vite配置中添加
worker: { format: 'es' }
画布高度为0px容器未设置明确高度为父div设置
height: '70vh'
(或任何固定/弹性高度)
无模型显示(FDM模式)实例未通过Core DM关联(
CogniteVisualizable.object3D
CogniteCADNode
默认使用模型浏览器(模式B),调用
sdk.models3D.list()
获取模型

API reference

API参考

Page-level (always rendered):
CacheProvider
,
RevealKeepAlive
Viewer-level (conditional):
RevealProvider
,
RevealCanvas
,
Reveal3DResources
,
InstanceStylingProvider
Hooks:
useModelsForInstanceQuery
,
use3dModels
,
useFdmAssetMappings
,
useReveal
,
useOptionalRevealKeepAlive
Types:
AddCadResourceOptions
,
TaggedAddResourceOptions
,
ViewerOptions
,
DMInstanceRef
(from
@cognite/reveal
)
All exports from
@cognite/dune-industrial-components/reveal
.
页面级别(始终渲染):
CacheProvider
RevealKeepAlive
查看器级别(条件渲染):
RevealProvider
RevealCanvas
Reveal3DResources
InstanceStylingProvider
钩子:
useModelsForInstanceQuery
use3dModels
useFdmAssetMappings
useReveal
useOptionalRevealKeepAlive
类型:
AddCadResourceOptions
TaggedAddResourceOptions
ViewerOptions
DMInstanceRef
(来自
@cognite/reveal
以上所有API均从
@cognite/dune-industrial-components/reveal
导出。