@easytwin/devkit 0.1.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/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@easytwin/devkit",
3
+ "version": "0.1.0",
4
+ "description": "EasyTwin DevKit 核心:lib(函数库)+ bin(easytwin 命令)双导出",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist",
9
+ "skills"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "bin": {
18
+ "easytwin": "./dist/bin.js"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "dependencies": {
24
+ "commander": "^12.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.10.2",
28
+ "tsup": "^8.3.5",
29
+ "typescript": "^5.7.2",
30
+ "vitest": "^2.1.8"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "http://gitlab.prod.dtstack.cn/visdev/visApplication/easytwin-group/easytwin-sdk-plugin.git"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit"
44
+ }
45
+ }
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: easytwin-bootstrap
3
+ description: 当用户要开始使用 EasyTwin DevKit、配置应用级凭证(App ID / App Secret / Base URL)、运行 easytwin init、或校验 easytwin.config.json 是否存在/有效时使用。
4
+ ---
5
+
6
+ # 初始化与凭据
7
+
8
+ `easytwin init` 交互式生成项目级 `easytwin.config.json`,并把它追加进 `.gitignore`(无则创建)。
9
+
10
+ ## 配置契约
11
+
12
+ ```json
13
+ {
14
+ "appId": "必填",
15
+ "appSecret": "必填",
16
+ "baseUrl": "可选,缺省官方固定域名"
17
+ }
18
+ ```
19
+
20
+ - 环境变量 `EASYTWIN_APP_ID` / `EASYTWIN_APP_SECRET` / `EASYTWIN_BASE_URL` 优先于文件(CI 与不落盘场景)。
21
+ - 该文件含密钥,**永不入库**:`init` 已负责 gitignore,不要把它提交进版本库。
22
+
23
+ ## 步骤
24
+
25
+ 1. 若项目还没有 `easytwin.config.json`,运行 `easytwin init` 并按提示输入。
26
+ 2. 校验:`easytwin scene list` 能列出场景即凭据有效;报错则检查 appId / appSecret 或环境变量覆盖。
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: easytwin-core
3
+ description: 当用户要理解 EasyTwin 引擎内核(类层级、RuntimeEngine 生命周期、Time/WebCanvas/相机/物理基础设施)、排查引擎级行为,或基于基类开发/扩展自定义组件时使用。对应 @easytwin/runtime 的 src/core/。
4
+ ---
5
+
6
+ # 引擎内核(easytwin-runtime core)
7
+
8
+ 对应 npm 包 `@easytwin/runtime` 的 `src/core/` 目录:引擎本体、场景、组件基类与基础设施。写渲染功能的主流程见 `easytwin-render`;本技能是引擎核心的完整 API 参考。
9
+
10
+ ## 核心关系速览
11
+
12
+ - `RuntimeEngine`(每 DOM 容器一个):持有 manager 注册表与 `Time`,驱动渲染循环;`engine.getManager(SceneManager)` 取 manager,`engine.mainScene` 取主场景。
13
+ - `RuntimeScene`(一个场景):持有 THREE 场景(`sceneObject`)、相机(`camera`)、控制器(`control`)、CSS 渲染器(`cssRenderer`)、物理(`physicsScene`)、地理(`geoTransform`)。
14
+ - manager:`ResourceManager` / `SceneManager` / `CustomComponentManager` / `EditorActionsManager` / `PhysicsManager`,均为 `AbstractManager` 子类。
15
+ - 组件类层级:`EventEmitter` → `SceneObject` → `Component` → `VirtualRootComponent` / `VirtualChildComponent`;任意组件内 `this.scene` / `this.engine` 直达场景与引擎。
16
+
17
+ ## 何时读哪个 reference
18
+
19
+ - 需要"RuntimeEngine 全量 API、Time、WebCanvas、SystemInfo/StatsTool"时读 `references/engine.md`
20
+ - 需要"类层级与基类(EventEmitter/SceneObject)、如何访问 engine/scene"时读 `references/base-classes.md`
21
+ - 需要"VirtualRootComponent / VirtualChildComponent 基类(虚拟子要素、data.children 同步、自定义属性/蓝图过滤、开发示例)"时读 `references/virtual-components.md`
22
+ - 需要"相机体系(Camera/SceneCamera/CameraControls/focus)"时读 `references/camera.md`
23
+ - 需要"物理体系(PhysicsManager/PhysicsScene/物理开关)"时读 `references/physics.md`
24
+
25
+ 组件生命周期钩子、状态/数据 API 与序列化结构分别在 `easytwin-render` 的 `references/lifecycle-events.md` 与 `references/scene-and-assets.md`,本技能不重复(虚拟子要素的序列化结构见本技能 `references/virtual-components.md`)。
@@ -0,0 +1,32 @@
1
+ # 类层级与基类
2
+
3
+ ## 类层级
4
+
5
+ ```
6
+ EventEmitter(packages/eventemitter:on/off/once/emit/removeAllListeners)
7
+ └─ SceneObject(core/base.ts:scene/engine getter,实现 ICanAccessEngine)
8
+ ├─ Component(core/Component.ts,抽象)
9
+ │ ├─ VirtualRootComponent(core/VirtualRootComponent.ts)
10
+ │ └─ 普通自定义组件(直接继承 Component)
11
+ └─ VirtualChildComponent(core/VirtualChildComponent.ts,extends SceneObject)
12
+ ```
13
+
14
+ - `ICanAccessEngine`:`get/set engine`。
15
+ - `Component` 的抽象成员与生命周期钩子(`onInitialize/onAwake/onEnable/onStart/onUpdate/onLateUpdate/onFixedUpdate/onDisable/onDestroy`)、状态/数据 API、指针/物理回调,以及 `VirtualRootComponent` / `VirtualChildComponent` 的完整说明在 `references/virtual-components.md`,本文件不重复。
16
+
17
+ ## 从任意位置访问引擎与场景
18
+
19
+ - 组件内:`this.engine` / `this.scene`(SceneObject 提供)。
20
+ - 场景内:`scene.engine`。
21
+ - 引擎内:`engine.mainScene`、`engine.getManager(...)`、`engine.getManagerByName(...)`。
22
+ - 组件查找:`scene.findRuntimeComponentById(id)` / `rootComponent.findById/ByName/ByType/ByPath`(见 easytwin-render/scene-and-assets.md)。
23
+
24
+ ## 组件通用成员(边界速查)
25
+
26
+ - 层级:`children` / `parent`、`addChild` / `removeChild`、`traverse(cb)`。
27
+ - 名称/可见性:`rename(name)`、`active` / `lock` / `collapsed`、`setActive/setLock/setCollapsed`(带后端同步)。
28
+ - 查找:`findBy(cond)` / `findById` / `findByName` / `findByType` / `findByPath(path)`。
29
+ - 编辑/预览:`startPreview/endPreview`、`startEdit/endEdit`、`editMode`、`isPreviewing` / `isEditing`。
30
+ - 变换控制(编辑器):`onTransformControlUpdate(control, offsetMatrix)` / `onTransformControlDragEnd(control)`,由 `TwinTransformControls` 驱动(见 camera.md 末尾)。
31
+
32
+ > 以上成员的逐条说明见 lifecycle-events.md;此处仅列边界便于快速定位。
@@ -0,0 +1,33 @@
1
+ # 相机体系
2
+
3
+ ## Camera(scene.camera)
4
+
5
+ 统一管理当前相机,方便做切换过渡:
6
+
7
+ - `scene.camera.main`:当前生效相机(`OrthographicCamera | PerspectiveCamera`);被组件接管时指向组件自带相机。
8
+ - `scene.camera.switchTo(camera)`:切到自定义相机(组件自带),置 `_isControlled = true`。
9
+ - `scene.camera.switchToScene()`:切回场景相机(`SceneCamera.nativeCamera`),置 `_isControlled = false`。
10
+
11
+ ## SceneCamera(scene.camera._sceneCamera)
12
+
13
+ - `sceneCameraMode: "perspective" | "orthographic"`:setter 同步另一模式的视口/矩阵参数,并触发 `onCameraModeChanged`。
14
+ - `nativeCamera`(当前模式对应相机)/ `perspectiveCamera` / `orthographicCamera`。
15
+ - `onCameraModeChanged`(`Signal`):Camera 内部监听;`_isControlled` 时忽略。
16
+
17
+ ## CameraControls(scene.control,readonly)
18
+
19
+ - 默认相机控制器(模块 `@/control/camera_controls`,已 `install({ THREE })`),负责浏览/环绕/缩放。
20
+ - `scene.focus(object, { distance?, duration?, focusCenter?: [number, number] })`:object 接受 `Object3D | Object3D[] | RuntimeComponent[]`(RuntimeComponent 走 `getSelectObject()`)。
21
+ - `scene.getCameraInfo()` → `{ position, rotation, target }`(`CameraInfo`,IVector3)。
22
+
23
+ ## 视图与渲染
24
+
25
+ - `scene.shotCanvas(): Promise<Blob | null>`(先 `update(0.1)` 再 `toBlob`);`scene.updatePixelRatio(pixelRatio?)`。
26
+ - 坐标转换(NDC/屏幕/世界)与 2D CSS 见 easytwin-render/references/rendering-apis.md。
27
+
28
+ ## TwinTransformControls(编辑器变换控件)
29
+
30
+ - 编辑器拖动组件时的 gizmo;`signals`:`objectChange` / `draggingChanged` / `draggingStart` / `draggingEnd` / `objectDragUpdate`。
31
+ - `TransformPivotMode = "BoundingBoxCenter" | "IndividualOrigins"`。
32
+ - 与组件联动:拖动中 `onTransformControlUpdate(control, offsetMatrix)`,拖动结束 `onTransformControlDragEnd(control)`(见 lifecycle-events.md)。
33
+ - 拖动时自动 `scene.control.enabled = false`,结束后恢复。
@@ -0,0 +1,88 @@
1
+ # RuntimeEngine 与基础设施
2
+
3
+ ## 概览
4
+
5
+ 一个 `RuntimeEngine` 对应一个 DOM 容器(`engine.container`):
6
+
7
+ - 注册并初始化五个 manager:`ResourceManager` / `SceneManager` / `CustomComponentManager` / `EditorActionsManager` / `PhysicsManager`;
8
+ - 启动 `requestAnimationFrame` 渲染循环,每帧 `Time.update()` → `SceneManager.updateScene(deltaTime)`;
9
+ - 统一提供场景切换、资源缓存、蓝图事件出口。
10
+
11
+ ## create 与 EngineConfiguration
12
+
13
+ 唯一入口 `RuntimeEngine.create(config)`(静态、异步):
14
+
15
+ ```ts
16
+ const engine = await RuntimeEngine.create({
17
+ containerId: "twin-root",
18
+ baseOSSUrl: "https://assets.easyv.cloud",
19
+ customComponentDeps: { "@easytwin/runtime": Runtime, react: FakeReact },
20
+ sceneResources: [],
21
+ componentRels: [],
22
+ });
23
+ ```
24
+
25
+ | 字段 | 必填 | 说明 |
26
+ | --- | --- | --- |
27
+ | `containerId` | 是 | 容器元素 id;找不到即抛 `cannot find dom with id '<id>'` |
28
+ | `baseOSSUrl` | 是 | 服务端资产根地址;draco/ktx2/webp 等系统库 URL 由此拼出 |
29
+ | `customComponentDeps` | 是 | 自定义组件脚本 import 的依赖表(键=模块名,值=实现) |
30
+ | `localOSSUrl` | 否 | 本地资产根地址;非空时自定义组件脚本优先读本地 |
31
+ | `sceneResources` | 否 | 场景资源清单 `ListSceneResourceVo[]`,默认 [] |
32
+ | `componentRels` | 否 | 组件引用关系 `GetComponentRelVo[]`,默认 [] |
33
+ | `enableResourcePersistence` | 否 | ResourceManager 是否用 localforage 持久化,默认 false |
34
+
35
+ 静态共享:`RuntimeEngine.sceneResources` / `RuntimeEngine.componentRels`(create 时从 config 写入)。
36
+
37
+ ## manager 访问
38
+
39
+ - `engine.getManager(SceneManager)` — 按类;`engine.getManagerByName("SceneManager")` — 按名。
40
+ - manager 基类 `AbstractManager`:`name` / `engine` / `init()` / `destroy()`。
41
+
42
+ ## 场景与组件便捷入口
43
+
44
+ - `engine.mainScene` = `getManager(SceneManager).mainScene`(可能 undefined)。
45
+ - `engine.createComponent(params, sync?)` / `engine.createModelComponent(name, url, sync?, onProgress?)`:转发到 mainScene,参数见 easytwin-render/references/scene-and-assets.md。
46
+
47
+ ## 时间(engine.time)
48
+
49
+ `Time`:
50
+
51
+ | 成员 | 说明 |
52
+ | --- | --- |
53
+ | `deltaTime` | 本帧增量(秒),受 `maximumDeltaTime` 与 `timeScale` 钳制 |
54
+ | `actualDeltaTime` | 真实帧增量,不受钳制 |
55
+ | `elapsedTime` / `actualElapsedTime` | 引擎启动以来累计(秒) |
56
+ | `elapsedMilliTime` | `elapsedTime * 1000` |
57
+ | `frameCount` | 累计帧数 |
58
+ | `timeScale` | 时间缩放,默认 1.0 |
59
+ | `maximumDeltaTime` | 单帧增量上限(秒),默认 0.333333 |
60
+ | `reset()` | 重置基准系统时间 |
61
+
62
+ 另有 `engine.isPaused` / `engine.debugMode`。
63
+
64
+ ## 蓝图事件出口
65
+
66
+ - `engine.registerEngineEmitFunc(func)` / `engine.emitFunc`:`EngineEmitFunc = (type, data, sourceId) => void`;组件内经 `emitEasyvFunc` 触发(见 easytwin-render/lifecycle-events.md)。
67
+
68
+ ## 事件(EventEmitter)
69
+
70
+ - `engine.on(...)`:预置事件名 `saveError` / `component-editing` / `component-previewing` / `disableDelete` / `enableDelete` / `informationBar`(另接受任意字符串)。
71
+
72
+ ## 资源缓存
73
+
74
+ - `engine.clearSceneSpecificResources()`:切场景时清理场景特定资源,保留通用缓存。
75
+ - `engine.getResourceCacheStats()` / `engine.isResourceCached(address)` / `engine.clearAllResourceCache()`(完全重置)。
76
+
77
+ ## 销毁
78
+
79
+ `engine.destroy()`:`cancelAnimationFrame` → 逆序销毁各 manager(启用持久化时跳过 ResourceManager)→ 清空注册表 → `emitFunc = undefined` → `THREE.Cache.clear()`。
80
+
81
+ ## WebCanvas
82
+
83
+ `scene.canvas`(`WebCanvas`):`width` / `height`(setter 同步底层 canvas)、`resizeByClientSize(pixelRatio = window.devicePixelRatio)`(按 clientWidth/clientHeight × pixelRatio 重设)。
84
+
85
+ ## SystemInfo / StatsTool
86
+
87
+ - `SystemInfo`(静态):`platform`(`Platform` 枚举:Android / IPhone / IPad / Mac / Unknown)、`operatingSystem`、`devicePixelRatio`。
88
+ - `StatsTool`(场景性能统计):`triangleCount` / `textureRAM` / `textureVRAM` / `lightCount` / `componentCount` / `modelCount` / `materialCount` / `textureMap` / `geometryMap` / `materialMap`,`update()` 重算。
@@ -0,0 +1,30 @@
1
+ # 物理体系(Rapier)
2
+
3
+ 基于 `@dimforge/rapier3d-compat`。
4
+
5
+ ## PhysicsManager(引擎级)
6
+
7
+ `engine.getManager(PhysicsManager)`(name `"PhysicsManager"`):
8
+
9
+ - `enablePhysics()` / `disablePhysics()`:启停 `world.step()`。`disablePhysics` 保留 rapier 对象便于复用;另有重置系列做完整重建。
10
+ - `gravity`(`RapierVec3 = { x, y, z }`,默认 `{ x: 0, y: -9.81, z: 0 }`)、`fixedDeltaTime`(默认 1/60;单帧最多 5 个物理子步)。
11
+
12
+ ## PhysicsScene(scene.physicsScene)
13
+
14
+ - `gravity`(get/set → PhysicsManager)、`fixedDeltaTime`(get)。
15
+ - 仅用于兼容旧版本代码(第三人称相机仍依赖该对象)。
16
+
17
+ ## 物理开关(scene)
18
+
19
+ - `scene.physicsEnabled`:第一人称相机预览用。
20
+ - `scene.physicsEnabled2`:全局物理开关。
21
+ - 实际生效条件 = 两者**或**(源码 `_internalPhysicsEnabled` 注释)。
22
+
23
+ ## 刚体与碰撞体
24
+
25
+ - 刚体组件:`DynamicRigidbodyComponent` / `StaticRigidbodyComponent` / `CharacterControllerComponent`。
26
+ - `RigidbodyType = "static" | "dynamic" | "kinematicPosition" | "kinematicVelocity"`。
27
+ - 碰撞体:`ColliderObj`(基类)与 `BoxColliderObj` / `CapsuleColliderObj` / `SphereColliderObj` / `CharacterColliderObj`;`PhysicsMaterial`(摩擦/弹性/混合规则)。
28
+ - 回调(需绑定碰撞体):`onCollisionEnter/Stay/Exit(other, self)`、`onTriggerEnter/Stay/Exit(other, self)`,参数为 `ColliderObj`。
29
+
30
+ > 注意 README:碰撞与射线检测(暂未支持);旧 `userData._target` 方案已划线,不建议依赖。
@@ -0,0 +1,169 @@
1
+ # VirtualRootComponent / VirtualChildComponent(虚拟子要素基类)
2
+
3
+ 面向"数字要素模板""模型"等带内部结构的组件:根组件持有 `virtualChildren: VirtualChildComponent[]`,子要素数据序列化在根组件的 `data.children` 中,与常规实体父子关系(`Component.children` / `addChild`)分离。以下按 `easytwin-runtime/src/core/VirtualRootComponent.ts`、`VirtualChildComponent.ts` 整理。
4
+
5
+ ## 类层级与定位
6
+
7
+ ```
8
+ EventEmitter → SceneObject → Component → VirtualRootComponent<SC, CD, D>
9
+ EventEmitter → SceneObject → VirtualChildComponent<D> (不继承 Component)
10
+ ```
11
+
12
+ - `VirtualRootComponent`(core/VirtualRootComponent.ts):数字要素模板、模型等组件的基类;泛型 `SC`(状态配置)/ `CD`(子要素配置)/ `D extends VirtualRootData<CD>`(data 结构)。
13
+ - `VirtualChildComponent`(core/VirtualChildComponent.ts):**直接继承 `SceneObject`**(不是 `Component`),实现 `IHierarchyNode` / `RuntimeComponent`。源码中没有任何类 `extends VirtualChildComponent`——子要素实例由根组件在 `syncChildren` 中 new 出来并放入 `virtualChildren` 持有(见文末示例)。
14
+ - 两种关系并存:常规父子走 `children` / `addChild`;虚拟子要素走 `virtualChildren` / `addVirtualChild`。编辑器侧 `shownChildren = [...virtualChildren, ...children]`。
15
+
16
+ ## 序列化数据类型
17
+
18
+ ```ts
19
+ interface VirtualChildData<D = any> {
20
+ id: string;
21
+ name: string;
22
+ active: boolean;
23
+ lock: boolean;
24
+ collapsed: boolean;
25
+ config: D; // 子要素自定义配置
26
+ customAttribute: Record<string, any>; // 自定义属性(蓝图过滤器依据)
27
+ }
28
+
29
+ interface VirtualRootData<D = any> {
30
+ children: VirtualChildData<D>[];
31
+ positionMode?: "xyz" | "lonLat";
32
+ }
33
+ ```
34
+
35
+ - 子要素持久化数据挂在根组件 `data.children`,由 `syncChildren` 同步到 `virtualChildren` 实例。
36
+ - `VirtualChildComponent` 上 `config` / `customAttribute` 的 setter 会立即 `updateData(serialize(), true)` 写回后端(同步);根组件经 `updateVirtualChildByData` 转发。
37
+
38
+ ## VirtualRootComponent API
39
+
40
+ ### 生命周期与渲染
41
+
42
+ | 成员 | 说明 |
43
+ | --- | --- |
44
+ | `_processActive()` / `_processInActive()` | 覆写:除自身激活/失活外,递归子 `virtualChildren`(子实体 `_active` 为 true 才跟随) |
45
+ | `renderData(data)` | 覆写:先 `syncChildren(data)` 再 `onRenderData(data)`;`easyV` 模式下按反序列化时缓存的 `_initialActive` 重置自身激活,并对非 `Model` 组件强制"模板显示 + 子要素全隐藏"(保证子要素可经蓝图过滤器独立显隐) |
46
+ | `abstract syncChildren(data)` | 必须实现:把 `data.children` 同步到 `virtualChildren` 实例(按 id 复用/销毁,见文末示例) |
47
+
48
+ ### data.children 数据操作(深拷贝改写 `_data` 后经 `updateData` 写回)
49
+
50
+ | 成员 | 说明 |
51
+ | --- | --- |
52
+ | `setVirtualChildByData(children, sync)` | 整体替换 `data.children`(编辑器) |
53
+ | `addVirtualChildByData(child, sync)` | 追加一条;`customAttribute` 缺省时自动复制第一个子要素的 key(值为 `""`) |
54
+ | `updateVirtualChildByData(id, child, sync)` | 按 id 替换一条子要素数据 |
55
+ | `removeVirtualChildByData(id, sync)` | 按 id 删除一条 |
56
+
57
+ ### 实例操作 / 遍历 / 查找
58
+
59
+ | 成员 | 说明 |
60
+ | --- | --- |
61
+ | `addVirtualChild(child)` / `removeVirtualChild(child)` | 直接增删 `virtualChildren` 并同步 `sceneObject` 层级 |
62
+ | `traverseVirtualChildren(cb)` | 深度遍历(含嵌套子要素) |
63
+ | `findVirtualChildById(id)` | 深度查找(含嵌套) |
64
+ | `filterByAttribute(attributes)` | 按 `customAttribute` 过滤,任一 key 相等即命中(Set 去重) |
65
+ | `filterVirtualChildByParam(param)` | 蓝图过滤器统一入口:`EasyvFilterAttributeParam = { attributes; unionFlag? } \| { key; include }`;含 `key` 走 `filterVirtualChildByKey`,否则走 `filterVirtualChildByAttribute` |
66
+ | `filterVirtualChildByKey(key, include)` | 子要素属性 `key` 为字符串且包含 `include` |
67
+ | `filterVirtualChildByAttribute(param, unionFlag)` | `unionFlag=false`:所有 key 全等才命中;`true`:任一 key 相等即命中 |
68
+
69
+ ### 自定义属性(编辑/运行时)
70
+
71
+ | 成员 | 说明 |
72
+ | --- | --- |
73
+ | `addCustomAttributeKey(key)` / `removeCustomAttributeKey(key)` / `renameCustomAttributeKey(oldKey, newKey)` | 编辑器专用,批量改所有子要素并同步后端 |
74
+ | `updateCustomAttribute(childId, key, value)` | 编辑器专用,同步后端 |
75
+ | `updateCustomAttributeRuntime(childId, key, value)` | 运行时专用,**不同步**后端 |
76
+ | `setCustomAttribute(childId, customAttribute, sync)` | 整体替换某子要素的 `customAttribute` |
77
+
78
+ ### 蓝图 / easyv
79
+
80
+ - `easyvSwitchState(param)`:有 `stateId` 且 `toChild` 时,对 `filterVirtualChildByParam(toChild)` 的结果逐个调 `child.easyvUpdateByStateConfig(state.config, param)`;否则 `selectState(stateId)`。
81
+ - 构造器经 `getDefaultEasyvEventListener` 注册默认蓝图动作:`setState` / `show` / `hide` / `show/hide` / `updateCustomAttributes`(`updateCustomAttributes` 仅 VirtualRootComponent 注册,参数为单个或数组 `EasyvUpdateCustomAttributeParam = { id; key; value }`,转调 `updateCustomAttributeRuntime`)。
82
+ - `show` / `hide` 对根组件操作的是**子要素的 `active`**:带 `attributes` 时按 `filterByAttribute` 过滤后设置,否则作用于全部 `virtualChildren`。
83
+
84
+ ### 选中 / 射线
85
+
86
+ - `select()` / `unSelect()`:空实现,子类可覆写。
87
+ - `intersectObject(raycaster, ev)`:对 `sceneObject.children` 中 visible 者做射线相交。
88
+
89
+ ## VirtualChildComponent API
90
+
91
+ ### 构造与关系
92
+
93
+ ```ts
94
+ constructor(scene: RuntimeScene, attachComponent: VirtualRootComponent)
95
+ ```
96
+
97
+ - `attachComponent: VirtualRootComponent | null`:所属根组件,所有数据同步经它转发。
98
+ - `parent: VirtualRootComponent | VirtualChildComponent | null`:setter 会**自动维护 `attachComponent`**(父为根取父,父为子则取 `父.attachComponent`),同时增删父的 `virtualChildren` 并处理激活联动;跨场景移动会被拒绝。
99
+ - `sceneObject: THREE.Object3D`:构造器内 `new Object3D()`,`userData._target = this`。
100
+ - `shownChildren` = `virtualChildren`(嵌套子要素)。
101
+
102
+ ### 持久化数据成员
103
+
104
+ | 成员 | 说明 |
105
+ | --- | --- |
106
+ | `id` / `name` / `active` / `lock` / `collapsed` / `config` / `customAttribute` | 与 `VirtualChildData` 对应;`config` / `customAttribute` 的 setter 触发 `updateData(serialize(), true)` 同步后端 |
107
+ | `abstract readonly type` / `version` | 必须实现 |
108
+ | `active` setter | 同步 `sceneObject.visible`,并按父级 `_activeInScene` 触发 `_processActive` / `_processInActive`(**不写后端**;持久化用 `setActive`) |
109
+ | `activeInScene` | 只读:是否已在场景中全局激活 |
110
+ | `showTransformControl` / `showBoundingBox` | 编辑器显隐控制(`RuntimeComponent` 契约成员) |
111
+
112
+ ### 数据同步
113
+
114
+ | 成员 | 说明 |
115
+ | --- | --- |
116
+ | `serialize(): VirtualChildData<D>` | 组装当前数据 |
117
+ | `updateData(data, sync)` | 经 `attachComponent.updateVirtualChildByData(id, data, sync)` 写回;同步 `sceneObject.visible` 并触发 `onEnable` / `onDisable`;`sync=false` 时首次调用缓存旧数据到 `_cachedOriginalData`(撤回用),`sync=true` 时清空 |
118
+ | `syncData(data)` | **直接赋值** id/name/active/lock/config/customAttribute,供根组件 `syncChildren` 复用实例;注意源码中**不恢复 `collapsed`** |
119
+ | `rename` / `setLock` / `setActive` / `setCollapsed` | 改字段并 `updateData(serialize(), true)` 同步后端;`setActive` 额外写 `sceneObjectVisibleWithLayout` |
120
+ | `updateCustomAttribute(key, value, sync)` | 改单个 key 并 `updateData(serialize(), sync)` |
121
+
122
+ ### 生命周期 / 状态
123
+
124
+ - 激活:`_processActive()` 置 `_activeInScene = true` 并调 **`onEnable()`**(无 `onAwake`),再递归子 `virtualChildren`;`_processInActive()` 对称。
125
+ - 钩子:`protected onEnable()` / `protected onDisable()`(可覆写);`easyvUpdateByStateConfig(config, param)` / `easyvSwitchState(param)`(默认空实现,可覆写);`startEditor()` / `endEditor()`。
126
+
127
+ ### 抽象成员(必须实现)
128
+
129
+ | 成员 | 说明 |
130
+ | --- | --- |
131
+ | `render()` | 根据 `this.config` 更新渲染对象 |
132
+ | `destroy()` | 销毁自身(移除 sceneObject / 资源) |
133
+ | `onTransformControlUpdate(control, offsetMatrix)` | 变换控制更新 |
134
+ | `onTransformControlDragEnd(control)` | 变换控制拖拽结束 |
135
+
136
+ ### 指针 / 选中 / 射线
137
+
138
+ - 指针回调:`onPointerUp` / `onPointerDown` / `onPointerEnter` / `onPointerExit` / `onPointerClick` / `onPointerDrag(pointer)`(默认空实现;注意 README 注明碰撞与射线检测暂未支持)。
139
+ - `select()` / `unSelect()`;`canSelectInEditor()`(默认 true)、`getSelectObject()`(默认返回 `sceneObject`)。
140
+ - `intersectObject(raycaster, ev)`:`raycaster.intersectObjects(sceneObject.children.filter(c => c.visible))`。
141
+ - `findRuntimeComponentById(id)`:沿 `shownChildren` 深度查找(含自身)。
142
+
143
+ ## 开发示例(参考 easytwin-components-example/src/core)
144
+
145
+ 根组件 `ExampleComponent extends VirtualRootComponent<ExampleComponentStateConfig, ExampleChildConfig, ExampleComponentData>`,`syncChildren` 按 id 复用 / 销毁:
146
+
147
+ ```ts
148
+ syncChildren(data: ExampleComponentData): void {
149
+ const oldChildren = this.virtualChildren;
150
+ const newChildren = [];
151
+ for (let i = 0; i < data.children.length; i++) {
152
+ const newChildData = data.children[i];
153
+ const reusedCompIndex = oldChildren.findIndex((a) => a.id === newChildData.id);
154
+ if (reusedCompIndex === -1) {
155
+ const c = new ExampleChildComponent(this.scene, this);
156
+ c.syncData(newChildData); // 新实例,直接灌数据
157
+ newChildren.push(c);
158
+ } else {
159
+ const reusedComp = oldChildren.splice(reusedCompIndex, 1)[0];
160
+ reusedComp.syncData(newChildData); // 复用实例,覆盖数据
161
+ newChildren.push(reusedComp);
162
+ }
163
+ }
164
+ oldChildren.forEach((c) => c.destroy()); // 剩余未复用实例销毁
165
+ this.virtualChildren = newChildren;
166
+ }
167
+ ```
168
+
169
+ 子要素 `ExampleChildComponent extends VirtualChildComponent<ExampleChildConfig>`:构造器接收 `(scene, attachComponent)`,把 mesh 挂到 `this.sceneObject`,`render()` 里按 `this.config` 更新,`destroy()` 移除自身资源;根组件 `onRenderData` 中对 `this.virtualChildren` 逐个调 `render()`。
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: easytwin-develop
3
+ description: 当用户开始 EasyTwin 第三方应用开发、需要了解整体开发工作流(配置凭证→拉场景→写渲染→上传)、或询问"接下来该做什么/从哪开始"时使用。其余 EasyTwin 技能的总纲。
4
+ ---
5
+
6
+ # EasyTwin 开发工作流
7
+
8
+ EasyTwin DevKit 是 EasyTwin 第三方应用开发工具集。典型工作流:
9
+
10
+ 1. **配置凭证**:运行 `easytwin init` 生成项目级 `easytwin.config.json`(App ID / App Secret)。详见 `easytwin-bootstrap`。
11
+ 2. **拉取场景**:`easytwin scene list` 列出场景,`easytwin scene pull <id>` 拉取场景 JSON 到本地,作为开发参考与预览输入。详见 `easytwin-scene`。
12
+ 3. **编写渲染功能**:用 twin runtime(npm 包 `@easytwin/runtime`)开发渲染代码;引擎内核(基类/Time/相机/物理)见 `easytwin-core`。详见 `easytwin-render`(主力技能)。
13
+ 4. **上传产物**:`easytwin upload <dir>` 全量覆盖上传到服务端,不可逆。详见 `easytwin-upload`。
14
+
15
+ ## 何时读哪个技能
16
+
17
+ | 场景 | 技能 |
18
+ | --- | --- |
19
+ | 初始化 / 校验凭证 | easytwin-bootstrap |
20
+ | 列场景、拉场景 JSON | easytwin-scene |
21
+ | 用 twin runtime 写渲染 | easytwin-render |
22
+ | 理解引擎内核 / 基类 / 相机 / 物理 | easytwin-core |
23
+ | 上传开发产物 | easytwin-upload |
24
+
25
+ > 场景 JSON 具体格式尚未定稿,相关说明在 easytwin-scene 中为占位。
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: easytwin-render
3
+ description: 当用户要用 EasyTwin twin runtime 编写渲染功能(安装/引入 @easytwin/runtime、初始化引擎、加载场景、开发自定义组件、调用渲染 API)时使用。这是 EasyTwin 渲染开发的唯一入口技能。
4
+ ---
5
+
6
+ # 用 twin runtime 开发渲染功能
7
+
8
+ twin runtime 是 npm 包 `@easytwin/runtime`(ESM,peer 依赖 `three@0.183.0`):在已存在的 DOM 容器上创建 `RuntimeEngine`,加载场景 JSON,通过组件生命周期与渲染 API 开发功能。
9
+
10
+ ## 快速上手
11
+
12
+ 1. 安装:`npm i @easytwin/runtime`(项目需自装 peer 依赖 `three@0.183.0`)。
13
+ 2. 页面里先放好容器 `<div id="twin-root"></div>` ——容器必须已存在于 DOM,`RuntimeEngine.create` 找不到会抛错。
14
+ 3. 初始化并加载场景:
15
+
16
+ ```ts
17
+ import { RuntimeEngine, SceneManager, LoadSceneMode } from "@easytwin/runtime";
18
+
19
+ const engine = await RuntimeEngine.create({
20
+ containerId: "twin-root",
21
+ baseOSSUrl: "https://assets.easyv.cloud",
22
+ customComponentDeps: { "@easytwin/runtime": Runtime, react: FakeReact },
23
+ sceneResources: [],
24
+ componentRels: [],
25
+ });
26
+ await engine.getManager(SceneManager).loadScene(sceneJson, "publish", LoadSceneMode.Single);
27
+ ```
28
+
29
+ 4. 在组件生命周期/事件里写渲染逻辑;自定义组件继承 `Component` / `VirtualRootComponent`,见 `references/lifecycle-events.md`。
30
+
31
+ > TODO(外部输入): 官方"script 引入全局对象"的加载方式尚未确认。当前以 npm 包 `@easytwin/runtime` + `RuntimeEngine.create` 为准;若官方后续给出 script 方式,在此补充并同步更新 references。
32
+
33
+ ## 参考(references/)
34
+
35
+ API 细节按域拆分,按需加载,不要一次全读:
36
+
37
+ - 需要"安装、初始化引擎、engine/scene/manager 关系、销毁"时读 `references/intro.md`
38
+ - 需要"场景加载/序列化结构/资产加载"时读 `references/scene-and-assets.md`
39
+ - 需要"组件生命周期、状态/数据、事件"时读 `references/lifecycle-events.md`
40
+ - 需要"相机/坐标、2D、动画、物理、地理、工具函数"时读 `references/rendering-apis.md`
41
+
42
+ > 引擎内核的完整 API(RuntimeEngine 全量、Time、WebCanvas、相机、物理)见 `easytwin-core`。
@@ -0,0 +1,81 @@
1
+ # 引入与初始化
2
+
3
+ ## 包信息
4
+
5
+ - npm 包:`@easytwin/runtime`(ESM 模块)。
6
+ - peer 依赖:`three@0.183.0`(需自行安装);内部依赖 rapier3d、@tweenjs/tween.js、mobx、localforage、proj4、stats-gl 等,由包自身携带。
7
+ - 根导出:`manager`、`core`、`loader`、`utils`、`packages`、`server`、`type`、`three-extension`、`constants`、`module` 命名空间;另有 `THREE`、`Mobx`、`TweenJs`、`Semver` 命名空间与若干直接导出(详见 rendering-apis.md)。
8
+
9
+ ## 初始化 RuntimeEngine
10
+
11
+ 唯一入口:`RuntimeEngine.create(config)`(静态、异步)。
12
+
13
+ ```ts
14
+ import { RuntimeEngine, SceneManager, LoadSceneMode } from "@easytwin/runtime";
15
+
16
+ const engine = await RuntimeEngine.create({
17
+ containerId: "twin-root", // 必填:容器 id,须已存在于 DOM
18
+ baseOSSUrl: "https://assets.easyv.cloud", // 必填:资产根地址
19
+ customComponentDeps: { /* 见下 */ }, // 必填:注入给自定义组件的依赖
20
+ sceneResources: [], // 可选:场景资源清单,默认 []
21
+ componentRels: [], // 可选:组件引用关系,默认 []
22
+ enableResourcePersistence: false, // 可选:ResourceManager 是否持久化(localforage),默认 false
23
+ localOSSUrl: undefined, // 可选:本地资产根地址
24
+ });
25
+ ```
26
+
27
+ `EngineConfiguration` 字段:
28
+
29
+ | 字段 | 必填 | 说明 |
30
+ | --- | --- | --- |
31
+ | `containerId` | 是 | 容器元素 id;`document.getElementById(containerId)` 找不到即抛错 |
32
+ | `baseOSSUrl` | 是 | 服务端资产根地址;draco / ktx2 / webp / LCC 等系统库 URL 由此拼出 |
33
+ | `customComponentDeps` | 是 | 自定义组件脚本 import 的依赖表,键为模块名,值为实现 |
34
+ | `localOSSUrl` | 否 | 本地资产根地址;非空时自定义组件脚本优先读 `${localOSSUrl}/${type}@${version}.js` |
35
+ | `sceneResources` | 否 | 场景资源清单(ListSceneResourceVo[]) |
36
+ | `componentRels` | 否 | 组件引用关系(GetComponentRelVo[]) |
37
+ | `enableResourcePersistence` | 否 | 是否让 ResourceManager 用 localforage 持久化,默认 false |
38
+
39
+ `create` 做的事:注册并初始化 ResourceManager / SceneManager / CustomComponentManager / EditorActionsManager / PhysicsManager,启动 `requestAnimationFrame` 渲染循环,返回 `Promise<RuntimeEngine>`。
40
+
41
+ ## customComponentDeps 注入
42
+
43
+ 自定义组件脚本里 `import` 的模块由引擎用此表解析(真实项目配置示例):
44
+
45
+ ```ts
46
+ const customComponentDeps = {
47
+ "@easytwin/runtime": Runtime, // 组件里 import { ... } from "@easytwin/runtime"
48
+ "@easytwin/runtime-frontend": { EasyVIcon: {}, MobxReactLite: { observer: (c: any) => c } },
49
+ react: FakeReact, // 无 React 环境时用最小 shim
50
+ };
51
+ ```
52
+
53
+ ## engine / scene / manager 关系
54
+
55
+ - 一个 `RuntimeEngine` 对应一个 DOM 容器,持有若干 manager 与若干 `RuntimeScene`。
56
+ - 取 manager:`engine.getManager(SceneManager)`(按类)/ `engine.getManagerByName("SceneManager")`(按名)。
57
+ - 主场景:`engine.mainScene`(即 `SceneManager.mainScene`)。
58
+ - 便捷方法:`engine.createComponent(params, sync?)`、`engine.createModelComponent(name, url, sync?, onProgress?)` 转发到 mainScene(参数见 scene-and-assets.md)。
59
+ - 引擎状态:`engine.time`(Time:`deltaTime` / `elapsedTime` / `frameCount` / `timeScale`)、`engine.isPaused`、`engine.debugMode`。
60
+ - 蓝图事件出口:`engine.registerEngineEmitFunc(func)`,回调签名 `EngineEmitFunc = (type: string, data: any, sourceId: string) => void`;组件里经 `emitEasyvFunc` 触发(见 lifecycle-events.md)。
61
+ - 资源缓存:`engine.clearSceneSpecificResources()`、`engine.getResourceCacheStats()`、`engine.isResourceCached(address)`、`engine.clearAllResourceCache()`。
62
+
63
+ ## RuntimeScene 与 mode
64
+
65
+ `SceneManager.loadScene(sceneJson, mode, loadSceneMode)` 的 `mode` 来自 `RuntimeSceneMode`:
66
+
67
+ ```ts
68
+ RuntimeSceneMode = { Editor: "editor", Preview: "preview", EasyV: "easyV", Publish: "publish" }
69
+ ```
70
+
71
+ - `editor`:EasyTwin 编辑页;`preview`:预览页;`easyV`:EasyV 编辑页;`publish`:EasyV 发布页。
72
+ - 部分能力(如 ViewHelper、TransformControls、编辑模式钩子)仅在 `editor` 模式注册。
73
+
74
+ ## 销毁
75
+
76
+ - `engine.destroy()`:取消 RAF、逆序销毁各 manager(启用持久化时保留 ResourceManager)、清空 `THREE.Cache`;销毁后需重建 engine。
77
+ - 单个场景 `scene.destroy()`:退出编辑/预览态、失活组件、递归销毁子组件、移除 easyv 监听、调用 onDestroy 并释放 DOM/资源。`LoadSceneMode.Single` 加载新场景时会先销毁旧场景。
78
+
79
+ ## 参考源码
80
+
81
+ - `src/core/RuntimeEngine.ts`、`src/core/RuntimeScene.ts`、`src/core/Time.ts`。