diff --git a/reports/animation-capabilities-20260912/.gitignore b/reports/animation-capabilities-20260912/.gitignore
new file mode 100644
index 0000000..267fb3e
--- /dev/null
+++ b/reports/animation-capabilities-20260912/.gitignore
@@ -0,0 +1,3 @@
+offline/
+*-failure.png
+failure.png
diff --git a/reports/animation-capabilities-20260912/01-animation-settings.png b/reports/animation-capabilities-20260912/01-animation-settings.png
new file mode 100644
index 0000000..4a473d1
Binary files /dev/null and b/reports/animation-capabilities-20260912/01-animation-settings.png differ
diff --git a/reports/animation-capabilities-20260912/02-editor-end-pose.png b/reports/animation-capabilities-20260912/02-editor-end-pose.png
new file mode 100644
index 0000000..b418836
Binary files /dev/null and b/reports/animation-capabilities-20260912/02-editor-end-pose.png differ
diff --git a/reports/animation-capabilities-20260912/03-model-reopened.png b/reports/animation-capabilities-20260912/03-model-reopened.png
new file mode 100644
index 0000000..b171e03
Binary files /dev/null and b/reports/animation-capabilities-20260912/03-model-reopened.png differ
diff --git a/reports/animation-capabilities-20260912/04-model-published.png b/reports/animation-capabilities-20260912/04-model-published.png
new file mode 100644
index 0000000..adc4c6d
Binary files /dev/null and b/reports/animation-capabilities-20260912/04-model-published.png differ
diff --git a/reports/animation-capabilities-20260912/05-guide-model-animation.png b/reports/animation-capabilities-20260912/05-guide-model-animation.png
new file mode 100644
index 0000000..17f4331
Binary files /dev/null and b/reports/animation-capabilities-20260912/05-guide-model-animation.png differ
diff --git a/reports/animation-capabilities-20260912/06-guide-published.png b/reports/animation-capabilities-20260912/06-guide-published.png
new file mode 100644
index 0000000..47f7486
Binary files /dev/null and b/reports/animation-capabilities-20260912/06-guide-published.png differ
diff --git a/reports/animation-capabilities-20260912/07-offline-before.png b/reports/animation-capabilities-20260912/07-offline-before.png
new file mode 100644
index 0000000..6fb9d23
Binary files /dev/null and b/reports/animation-capabilities-20260912/07-offline-before.png differ
diff --git a/reports/animation-capabilities-20260912/08-offline-animation.png b/reports/animation-capabilities-20260912/08-offline-animation.png
new file mode 100644
index 0000000..79a74c8
Binary files /dev/null and b/reports/animation-capabilities-20260912/08-offline-animation.png differ
diff --git a/reports/animation-capabilities-20260912/09-teaching-animation.png b/reports/animation-capabilities-20260912/09-teaching-animation.png
new file mode 100644
index 0000000..e33d335
Binary files /dev/null and b/reports/animation-capabilities-20260912/09-teaching-animation.png differ
diff --git a/reports/animation-capabilities-20260912/10-teaching-reset.png b/reports/animation-capabilities-20260912/10-teaching-reset.png
new file mode 100644
index 0000000..fb4851d
Binary files /dev/null and b/reports/animation-capabilities-20260912/10-teaching-reset.png differ
diff --git a/reports/animation-capabilities-20260912/11-training-action-library.png b/reports/animation-capabilities-20260912/11-training-action-library.png
new file mode 100644
index 0000000..d2bdaf7
Binary files /dev/null and b/reports/animation-capabilities-20260912/11-training-action-library.png differ
diff --git a/reports/animation-capabilities-20260912/12-training-action-bound.png b/reports/animation-capabilities-20260912/12-training-action-bound.png
new file mode 100644
index 0000000..cad3368
Binary files /dev/null and b/reports/animation-capabilities-20260912/12-training-action-bound.png differ
diff --git a/reports/animation-capabilities-20260912/animation-guide.zip b/reports/animation-capabilities-20260912/animation-guide.zip
new file mode 100644
index 0000000..9e17be2
Binary files /dev/null and b/reports/animation-capabilities-20260912/animation-guide.zip differ
diff --git a/reports/animation-capabilities-20260912/fixture.json b/reports/animation-capabilities-20260912/fixture.json
new file mode 100644
index 0000000..dcd2b78
--- /dev/null
+++ b/reports/animation-capabilities-20260912/fixture.json
@@ -0,0 +1,3236 @@
+{
+ "modelId": "416",
+ "detail": {
+ "bodyId": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "wheelId": "model-imported-model_mto6wi6k_08-cesiummilktruck-7",
+ "timeline": {
+ "version": 1,
+ "duration": 4,
+ "currentTime": 4,
+ "loop": false,
+ "zoom": 1,
+ "tracks": [
+ {
+ "pivot": [
+ 0,
+ 0.1,
+ 0
+ ],
+ "id": "rotate",
+ "name": "部件旋出",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-7",
+ "property": "rotation.x",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "1978a408-d240-4d51-95b5-8d3a1afcf602",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "f0274daa-ee99-46f1-893a-535ce0872d4c",
+ "time": 4,
+ "value": 3.141592653589793,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "id": "move",
+ "name": "旋出位移",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-7",
+ "property": "position",
+ "color": "#58c7ff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "14f32f96-fab2-4f4c-aa73-2542a28a6243",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "a9d04750-bdf9-4280-a6a5-deed5226d4c0",
+ "time": 4,
+ "value": [
+ 0,
+ 0.3,
+ 0
+ ],
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "states": [
+ {
+ "name": "污渍",
+ "color": "#795548",
+ "roughness": 0.95
+ },
+ {
+ "name": "清洁",
+ "color": "#dce8ed",
+ "roughness": 0.2
+ }
+ ],
+ "id": "state",
+ "name": "污渍到清洁",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "property": "material.state",
+ "color": "#9d77ff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "18ed5cae-8da2-49d9-b543-83d48a91ea6c",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "63dda46b-b2eb-4792-b547-38cd65088a94",
+ "time": 4,
+ "value": 1,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "flow": {
+ "points": [
+ [
+ 0,
+ 0,
+ 0.5
+ ],
+ [
+ 1,
+ 0,
+ 0.5
+ ],
+ [
+ 1,
+ 1,
+ 0.5
+ ]
+ ],
+ "color": "#16b9f3",
+ "radius": 0.045,
+ "count": 10,
+ "speed": 0.6,
+ "gateTrackId": "state",
+ "openValue": 1
+ },
+ "id": "flow",
+ "name": "回路流向",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "property": "flow",
+ "color": "#4ed6a7",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "9b26bdf5-f3f5-477d-bac8-53c8630f7277",
+ "time": 0,
+ "value": 1,
+ "easing": "linear"
+ },
+ {
+ "id": "403f9828-5089-4751-aaa0-9e9c58d4e897",
+ "time": 4,
+ "value": -1,
+ "easing": "linear"
+ }
+ ]
+ }
+ ],
+ "clips": [
+ {
+ "id": "work",
+ "name": "旋出与清洁演示",
+ "start": 0,
+ "end": 4,
+ "finish": "hold"
+ },
+ {
+ "id": "reset",
+ "name": "阀门复位演示",
+ "start": 0,
+ "end": 4,
+ "finish": "reset"
+ }
+ ]
+ },
+ "pose": [
+ -1.5407439555097887e-33,
+ 0.5,
+ -1.6554003917764114e-18
+ ],
+ "version": 2
+ },
+ "savedDocument": {
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "动画能力验收-组合动作与流向-20260912",
+ "modelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "name": "08-CesiumMilkTruck",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "code": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/a75de903d95b0590307632d7bed44111.glb",
+ "resourceUrl": "",
+ "sourceName": "08-CesiumMilkTruck.glb",
+ "sizeBytes": 369980,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 4823,
+ "triangles": 3624,
+ "nodes": 10,
+ "meshes": 5,
+ "materials": 5,
+ "textures": 1,
+ "animations": 1,
+ "animationDurationSeconds": 1.25,
+ "modelMetadata": {
+ "nodeCount": 10,
+ "meshCount": 5,
+ "materialCount": 5,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Wheels",
+ "start": 0,
+ "end": 1.25,
+ "duration": 1.25,
+ "trackCount": 2
+ }
+ ],
+ "animationDurationSeconds": 1.25
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Wheels",
+ "start": 0,
+ "end": 1.25,
+ "duration": 1.25,
+ "trackCount": 2
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "08-CesiumMilkTruck.glb",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/a75de903d95b0590307632d7bed44111.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 4,
+ "currentTime": 4,
+ "loop": false,
+ "zoom": 1,
+ "tracks": [
+ {
+ "pivot": [
+ 0,
+ 0.1,
+ 0
+ ],
+ "id": "rotate",
+ "name": "部件旋出",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-7",
+ "property": "rotation.x",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "1978a408-d240-4d51-95b5-8d3a1afcf602",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "f0274daa-ee99-46f1-893a-535ce0872d4c",
+ "time": 4,
+ "value": 3.141592653589793,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "id": "move",
+ "name": "旋出位移",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-7",
+ "property": "position",
+ "color": "#58c7ff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "14f32f96-fab2-4f4c-aa73-2542a28a6243",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "a9d04750-bdf9-4280-a6a5-deed5226d4c0",
+ "time": 4,
+ "value": [
+ 0,
+ 0.3,
+ 0
+ ],
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "states": [
+ {
+ "name": "污渍",
+ "color": "#795548",
+ "roughness": 0.95
+ },
+ {
+ "name": "清洁",
+ "color": "#dce8ed",
+ "roughness": 0.2
+ }
+ ],
+ "id": "state",
+ "name": "污渍到清洁",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "property": "material.state",
+ "color": "#9d77ff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "18ed5cae-8da2-49d9-b543-83d48a91ea6c",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "63dda46b-b2eb-4792-b547-38cd65088a94",
+ "time": 4,
+ "value": 1,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "flow": {
+ "points": [
+ [
+ 0,
+ 0,
+ 0.5
+ ],
+ [
+ 1,
+ 0,
+ 0.5
+ ],
+ [
+ 1,
+ 1,
+ 0.5
+ ]
+ ],
+ "color": "#16b9f3",
+ "radius": 0.045,
+ "count": 10,
+ "speed": 0.6,
+ "gateTrackId": "state",
+ "openValue": 1
+ },
+ "id": "flow",
+ "name": "回路流向",
+ "targetId": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "property": "flow",
+ "color": "#4ed6a7",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "9b26bdf5-f3f5-477d-bac8-53c8630f7277",
+ "time": 0,
+ "value": 1,
+ "easing": "linear"
+ },
+ {
+ "id": "403f9828-5089-4751-aaa0-9e9c58d4e897",
+ "time": 4,
+ "value": -1,
+ "easing": "linear"
+ }
+ ]
+ }
+ ],
+ "clips": [
+ {
+ "id": "work",
+ "name": "旋出与清洁演示",
+ "start": 0,
+ "end": 4,
+ "finish": "hold"
+ },
+ {
+ "id": "reset",
+ "name": "阀门复位演示",
+ "start": 0,
+ "end": 4,
+ "finish": "reset"
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ -12.62716702347298,
+ 15.724859987207516,
+ 11.185421574292878
+ ],
+ "target": [
+ 0,
+ 2.8850664334213203,
+ 0.67571590810515
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "08-CesiumMilkTruck",
+ "nodeName": "08-CesiumMilkTruck",
+ "nodePath": "@root",
+ "interactionId": "interaction-182987e1",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-0": {
+ "name": "Scene",
+ "nodeName": "Scene",
+ "nodePath": "0",
+ "interactionId": "interaction-9beb9729",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0.1328080408740111,
+ -0.00684373075768363
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.9306168791349512,
+ 1.9306168791349512,
+ 1.9306168791349512
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-1": {
+ "name": "Yup2Zup",
+ "nodeName": "Yup2Zup",
+ "nodePath": "0/0",
+ "interactionId": "interaction-1ba581e6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-2": {
+ "name": "Cesium_Milk_Truck",
+ "nodeName": "Cesium_Milk_Truck",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-004dec41",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-3": {
+ "name": "Cesium_Milk_Truck_1",
+ "nodeName": "Cesium_Milk_Truck_1",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-5f7e4bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-4": {
+ "name": "Cesium_Milk_Truck_2",
+ "nodeName": "Cesium_Milk_Truck_2",
+ "nodePath": "0/0/0/1",
+ "interactionId": "interaction-607e4d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "003928",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 1,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-5": {
+ "name": "Cesium_Milk_Truck_3",
+ "nodeName": "Cesium_Milk_Truck_3",
+ "nodePath": "0/0/0/2",
+ "interactionId": "interaction-5d7e48c8",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "484848",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 1,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-6": {
+ "name": "Node",
+ "nodeName": "Node",
+ "nodePath": "0/0/0/3",
+ "interactionId": "interaction-5e7e4a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-7": {
+ "name": "Wheels",
+ "nodeName": "Wheels",
+ "nodePath": "0/0/0/3/0",
+ "interactionId": "interaction-ce2feac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 1,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-8": {
+ "name": "Node001",
+ "nodeName": "Node001",
+ "nodePath": "0/0/0/4",
+ "interactionId": "interaction-637e523a",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6wi6k_08-cesiummilktruck-9": {
+ "name": "Wheels001",
+ "nodeName": "Wheels001",
+ "nodePath": "0/0/0/4/0",
+ "interactionId": "interaction-f417f82d",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 1,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 0,
+ "clipName": "Wheels"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "sizeBytes": 0,
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+ },
+ "modelVersionId": "479",
+ "guideCandidateId": "417",
+ "guide": {
+ "id": "417",
+ "resource": {
+ "id": "416@479",
+ "tracks": 4,
+ "clips": 2,
+ "overrides": 11
+ }
+ },
+ "guideId": "417",
+ "animationSceneId": "418",
+ "animationSceneVersionId": "481",
+ "animationTrainingId": "419",
+ "animationTrainingVersionId": "483",
+ "animationAssignmentId": "55"
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/guide-checks.json b/reports/animation-capabilities-20260912/guide-checks.json
new file mode 100644
index 0000000..1455d0f
--- /dev/null
+++ b/reports/animation-capabilities-20260912/guide-checks.json
@@ -0,0 +1,41 @@
+{
+ "checks": [
+ {
+ "name": "指导书通过正式接口固定引用模型版本并持久化动画元数据",
+ "pass": true,
+ "resource": {
+ "id": "416@479",
+ "tracks": 4,
+ "clips": 2,
+ "overrides": 11
+ }
+ },
+ {
+ "name": "正式指导书视口加载命名动作并还原组合运动与材质状态",
+ "pass": true,
+ "detail": {
+ "clips": [
+ "Wheels",
+ "旋出与清洁演示",
+ "阀门复位演示"
+ ],
+ "initial": [
+ 0,
+ 0,
+ 0
+ ],
+ "final": [
+ -1.5407439555097887e-33,
+ 0.5,
+ -1.6554003917764114e-18
+ ],
+ "state": 0.2
+ }
+ },
+ {
+ "name": "正式发布并下载带动画的离线指导书",
+ "pass": true
+ }
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/index.html b/reports/animation-capabilities-20260912/index.html
new file mode 100644
index 0000000..923299e
--- /dev/null
+++ b/reports/animation-capabilities-20260912/index.html
@@ -0,0 +1,22 @@
+
动画能力实施与测试报告 · 2026-09-12
+
+
+已实施能力
| 能力 | 实现与使用 |
+| 动作片段与机械组合运动 | 时间轴设置命名起止范围;旋转中心使用部件局部坐标;位移与 XYZ 旋转叠加,关键帧错开形成顺序动作;支持保持末帧和回到起点。蓝图按名称播放片段。 |
+| 状态与材质 | 新增缩放、显隐、不透明度、颜色、材质状态轨道;材质副本隔离,不污染其他实例。录制与保存保留中性基准,旋转中心偏移不会被重复写入位移关键帧。 |
+| 回路流向 | 配置路径点、颜色、速度、流点数量、正向/反向/停止;可关联标量轨道控制开通。用于工作原理讲解。 |
+| 跨模块复用 | 训练编辑器从正式目录选动作,保存步骤绑定;教学任务固定模型/场景/训练版本;指导书在线模型与离线 ZIP 使用同一动画求值规则。原生 GLB 动画仍可选择。 |
+实测结果
模型编辑与发布
| 检查 | 结果 |
|---|
| 真实工程保存组合旋转、位移、材质状态和流向轨道 | 通过 |
| 刷新重开后动作片段、复位基准和流向配置恢复 | 通过 |
| 正式发布固定动画模型版本 | 通过 |
查看原始检查记录 · 浏览器脚本异常 0 条
指导书在线使用与交付
| 检查 | 结果 |
|---|
| 指导书通过正式接口固定引用模型版本并持久化动画元数据 | 通过 |
| 正式指导书视口加载命名动作并还原组合运动与材质状态 | 通过 |
| 正式发布并下载带动画的离线指导书 | 通过 |
查看原始检查记录 · 浏览器脚本异常 0 条
离线播放
| 检查 | 结果 |
|---|
| 下载的离线包可播放命名组合动作,画布发生变化并可暂停 | 通过 |
| 断开业务服务与公网后仍可加载模型、引擎和动画 | 通过 |
查看原始检查记录 · 浏览器脚本异常 0 条
正式教学视口
| 检查 | 结果 |
|---|
| 真实教学任务固定模型版本、动作、材质、枢轴和流向配置 | 通过 |
| 正式教学预览实际播放指定片段并保持末帧,四条轨道全部绑定 | 通过 |
| 切换步骤后播放复位片段,结束恢复起点并关闭流向 | 通过 |
| 同一教学视口原生 GLB 动画与自编片段切换兼容(浏览器运行时检查) | 通过 |
查看原始检查记录 · 浏览器脚本异常 0 条
训练编辑器绑定
| 检查 | 结果 |
|---|
| 训练编辑器从正式模型库选择命名动作并通过真实保存接口持久化 | 通过 |
| 训练编辑草稿不会覆盖已创建任务的固定发布快照 | 通过 |
查看原始检查记录 · 浏览器脚本异常 0 条
+补充验证:前端类型检查及生产构建通过;生成的模型编辑器 public/dist 文件哈希一致。后端 18 条校验、快照与考试提示隔离用例通过;原型 5 项合同测试、Web 4 项场景/缓存/落地测试通过。构建仅有现有的大分块提示。
+测试采用真实登录、GLB、保存/发布/下载接口。部分配置和视口内部状态由 Playwright 调用页面模块核对;教学原生 GLB 切换为浏览器运行时兼容检查。离线用例阻断业务接口及公网请求,外部请求数为 0。
+可继续使用的测试数据
+训练当前保留实测保存的草稿;已有任务仍使用旧发布快照,这正是版本隔离测试验证的行为。试验性教学任务未产生学员成绩。
+使用顺序与边界
- 模型编辑器时间轴添加轨道,选择部件、属性及关键帧;进入「动作与轨道设置」配置片段、枢轴、材质状态或流向。
- 保存并发布模型,在场景选择固定版本;训练的「步骤动画」选择正式动作并保存。
- 发布训练、下发新任务后在教学端使用;指导书关联模型片段后可在线播放或导出离线 ZIP。
+本次没有数据库表改动。未加入新的密钥文件、独立服务或认证方式。代码尚未提交 Git。
+污渍/清洁通过颜色和粗糙度等 PBR 参数表达;贴图擦除、软管/密封圈弹性变形、真实流体压力计算、整片视频导出不在本次实施范围。表格中的具体装备动作仍需配置部件、路径和时序,本报告不代表已经制作完成全部动画素材。
+
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/model-checks.json b/reports/animation-capabilities-20260912/model-checks.json
new file mode 100644
index 0000000..8dfd2e4
--- /dev/null
+++ b/reports/animation-capabilities-20260912/model-checks.json
@@ -0,0 +1,47 @@
+{
+ "checks": [
+ {
+ "name": "真实工程保存组合旋转、位移、材质状态和流向轨道",
+ "pass": true
+ },
+ {
+ "name": "刷新重开后动作片段、复位基准和流向配置恢复",
+ "pass": true,
+ "detail": {
+ "a": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": [
+ -1.5407439555097887e-33,
+ 0.5,
+ -1.6554003917764114e-18
+ ],
+ "clips": [
+ {
+ "id": "work",
+ "name": "旋出与清洁演示",
+ "start": 0,
+ "end": 4,
+ "finish": "hold"
+ },
+ {
+ "id": "reset",
+ "name": "阀门复位演示",
+ "start": 0,
+ "end": 4,
+ "finish": "reset"
+ }
+ ],
+ "flows": 1
+ }
+ },
+ {
+ "name": "正式发布固定动画模型版本",
+ "pass": true,
+ "versionId": "479"
+ }
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/offline-checks.json b/reports/animation-capabilities-20260912/offline-checks.json
new file mode 100644
index 0000000..0bb49d8
--- /dev/null
+++ b/reports/animation-capabilities-20260912/offline-checks.json
@@ -0,0 +1,14 @@
+{
+ "checks": [
+ {
+ "name": "下载的离线包可播放命名组合动作,画布发生变化并可暂停",
+ "pass": true
+ },
+ {
+ "name": "断开业务服务与公网后仍可加载模型、引擎和动画",
+ "pass": true
+ }
+ ],
+ "errors": [],
+ "external": []
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/report-cover.png b/reports/animation-capabilities-20260912/report-cover.png
new file mode 100644
index 0000000..4fc25b5
Binary files /dev/null and b/reports/animation-capabilities-20260912/report-cover.png differ
diff --git a/reports/animation-capabilities-20260912/summary.json b/reports/animation-capabilities-20260912/summary.json
new file mode 100644
index 0000000..4d10cd7
--- /dev/null
+++ b/reports/animation-capabilities-20260912/summary.json
@@ -0,0 +1,10 @@
+{
+ "date": "2026-09-12",
+ "passed": 14,
+ "failed": 0,
+ "backendTests": 18,
+ "prototypeContractTests": 5,
+ "webTests": 4,
+ "build": "passed",
+ "ddlChanges": false
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/teaching-checks.json b/reports/animation-capabilities-20260912/teaching-checks.json
new file mode 100644
index 0000000..6fd2157
--- /dev/null
+++ b/reports/animation-capabilities-20260912/teaching-checks.json
@@ -0,0 +1,67 @@
+{
+ "checks": [
+ {
+ "name": "真实教学任务固定模型版本、动作、材质、枢轴和流向配置",
+ "pass": true,
+ "modelVersionId": "479",
+ "assignmentId": "55"
+ },
+ {
+ "name": "正式教学预览实际播放指定片段并保持末帧,四条轨道全部绑定",
+ "pass": true,
+ "detail": {
+ "status": "已加载发布训练场景 · 1 个对象",
+ "time": 4,
+ "bindings": 4,
+ "unresolved": 0,
+ "wheel": [
+ -1.5407439555097887e-33,
+ 0.5,
+ -1.6554003917764114e-18
+ ],
+ "base": [
+ 0,
+ 0,
+ 0
+ ],
+ "roughness": 0.2,
+ "actualRoughness": 0.2,
+ "flowVisible": true,
+ "step": "animation-step"
+ }
+ },
+ {
+ "name": "切换步骤后播放复位片段,结束恢复起点并关闭流向",
+ "pass": true,
+ "detail": {
+ "status": "已加载发布训练场景 · 1 个对象",
+ "time": 0,
+ "bindings": 4,
+ "unresolved": 0,
+ "wheel": [
+ 0,
+ 0,
+ 0
+ ],
+ "base": [
+ 0,
+ 0,
+ 0
+ ],
+ "roughness": 0.95,
+ "actualRoughness": 0.95,
+ "flowVisible": false,
+ "step": "reset-step"
+ }
+ },
+ {
+ "name": "同一教学视口原生 GLB 动画与自编片段切换兼容(浏览器运行时检查)",
+ "pass": true,
+ "detail": {
+ "name": "Wheels",
+ "time": 0.8832000000000001
+ }
+ }
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/teaching-inspection.json b/reports/animation-capabilities-20260912/teaching-inspection.json
new file mode 100644
index 0000000..3f31125
--- /dev/null
+++ b/reports/animation-capabilities-20260912/teaching-inspection.json
@@ -0,0 +1,20 @@
+{
+ "status": "已加载发布训练场景 · 1 个对象",
+ "time": 4,
+ "bindings": 4,
+ "unresolved": 0,
+ "wheel": [
+ -1.5407439555097887e-33,
+ 0.5,
+ -1.6554003917764114e-18
+ ],
+ "base": [
+ 0,
+ 0,
+ 0
+ ],
+ "roughness": 0.2,
+ "actualRoughness": 0.2,
+ "flowVisible": true,
+ "step": "animation-step"
+}
\ No newline at end of file
diff --git a/reports/animation-capabilities-20260912/training-editor-checks.json b/reports/animation-capabilities-20260912/training-editor-checks.json
new file mode 100644
index 0000000..b1cd3b2
--- /dev/null
+++ b/reports/animation-capabilities-20260912/training-editor-checks.json
@@ -0,0 +1,24 @@
+{
+ "checks": [
+ {
+ "name": "训练编辑器从正式模型库选择命名动作并通过真实保存接口持久化",
+ "pass": true,
+ "binding": {
+ "assetId": "animation-content-model-416-479-work",
+ "name": "动画能力验收-组合动作与流向-20260912 · 旋出与清洁演示",
+ "modelAssetId": "content-model-416-479",
+ "clipName": "旋出与清洁演示",
+ "sourceStart": 0,
+ "finish": "hold",
+ "duration": 4,
+ "trackCount": 4,
+ "revision": 1
+ }
+ },
+ {
+ "name": "训练编辑草稿不会覆盖已创建任务的固定发布快照",
+ "pass": true
+ }
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/reports/guide-template-actions-20260912/1440.png b/reports/guide-template-actions-20260912/1440.png
new file mode 100644
index 0000000..15ccb04
Binary files /dev/null and b/reports/guide-template-actions-20260912/1440.png differ
diff --git a/reports/guide-template-actions-20260912/1920.png b/reports/guide-template-actions-20260912/1920.png
new file mode 100644
index 0000000..2b1177c
Binary files /dev/null and b/reports/guide-template-actions-20260912/1920.png differ
diff --git a/reports/guide-template-actions-20260912/dark.png b/reports/guide-template-actions-20260912/dark.png
new file mode 100644
index 0000000..08ed113
Binary files /dev/null and b/reports/guide-template-actions-20260912/dark.png differ
diff --git a/reports/guide-template-actions-20260912/index.html b/reports/guide-template-actions-20260912/index.html
new file mode 100644
index 0000000..77124ff
--- /dev/null
+++ b/reports/guide-template-actions-20260912/index.html
@@ -0,0 +1 @@
+模板库操作区样式修复模板库操作区样式修复
2026-09-12 · 开发环境 · 内部维护账号 · Edge / Playwright
原因:操作区默认 opacity:0、max-height:0,仅在 hover / focus-within 展开;展开的高度限制也裁切了按钮底边。
修复:预览与下载始终显示,操作区按内容自然撑高,移除悬停折叠及移动端残留高度限制。
验证:1920、1440、750 宽度下操作区可见、按钮未裁切、悬停前后卡片高度稳定;下载菜单可打开,明暗主题均已检查。无业务数据写入。pnpm run build 与 git diff --check 通过。
检查结果 JSON1920 · 默认显示全部操作

1440 · 窄桌面

暗色主题
diff --git a/reports/guide-template-actions-20260912/results.json b/reports/guide-template-actions-20260912/results.json
new file mode 100644
index 0000000..98d64ed
--- /dev/null
+++ b/reports/guide-template-actions-20260912/results.json
@@ -0,0 +1,26 @@
+[
+ {
+ "width": 1920,
+ "normalVisible": true,
+ "buttonsNotClipped": true,
+ "hoverHeightStable": true
+ },
+ {
+ "width": 1440,
+ "normalVisible": true,
+ "buttonsNotClipped": true,
+ "hoverHeightStable": true
+ },
+ {
+ "width": 750,
+ "normalVisible": true,
+ "buttonsNotClipped": true,
+ "hoverHeightStable": true
+ },
+ {
+ "downloadMenuOpens": true
+ },
+ {
+ "darkActionsVisible": true
+ }
+]
\ No newline at end of file
diff --git a/tools/animation-capabilities-live.mjs b/tools/animation-capabilities-live.mjs
new file mode 100644
index 0000000..91dd677
--- /dev/null
+++ b/tools/animation-capabilities-live.mjs
@@ -0,0 +1,70 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+import {chromium,expect} from '@playwright/test';
+import {login,call} from './role-permissions-live.mjs';
+const report=path.resolve('reports/animation-capabilities-20260912');fs.mkdirSync(report,{recursive:true});
+const fixturePath=path.join(report,'fixture.json');
+const fixture=fs.existsSync(fixturePath)?JSON.parse(fs.readFileSync(fixturePath,'utf8')):{};
+const save=()=>fs.writeFileSync(fixturePath,JSON.stringify(fixture,null,2));
+const session=await login('root');
+if(!fixture.modelId){
+ const source=await call('tran/v1/content/projects/156',session.accessToken);assert.equal(source.status,200);
+ const copy=await call('tran/v1/content/projects/156/copy',session.accessToken,{name:'动画能力验收-组合动作与流向-20260912',version:source.data.project.version});
+ assert.equal(copy.status,200,copy.message);fixture.modelId=copy.data.project.id;save();
+}
+const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
+const context=await browser.newContext({viewport:{width:1920,height:1080}});
+await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session);
+const page=await context.newPage(),checks=[],errors=[];
+page.on('pageerror',error=>errors.push(error.message));
+const shot=async name=>page.screenshot({path:path.join(report,name+'.png')});
+try {
+ await page.goto(`http://127.0.0.1:6180/content/models/${fixture.modelId}/edit`);
+ await expect(page.locator('iframe')).toBeVisible({timeout:60000});
+ const frame=await page.locator('iframe').contentFrame();
+ await expect(frame.locator('#scene-count')).toBeVisible({timeout:60000});
+ const editorFrame=page.frames().find(f=>f.url().includes('legacy-model-editor'));
+ await editorFrame.waitForFunction(()=>window.editorApp?.apiSession?.project && window.editorApp?.modelTargets?.size>0,{timeout:60000});
+ const detail=await editorFrame.evaluate(async()=>{
+ const app=window.editorApp;app.timeline.pause();
+ const body=[...app.modelTargets.entries()].find(([,object])=>object.name==='Cesium_Milk_Truck_1');
+ const wheel=[...app.modelTargets.entries()].find(([,object])=>object.name==='Wheels');
+ if(!body||!wheel)throw new Error('Fixture body/wheel not found');
+ const keys=(a,b)=>[{id:crypto.randomUUID(),time:0,value:a},{id:crypto.randomUUID(),time:4,value:b}];
+ const track=(id,name,targetId,property,a,b,extra={})=>({id,name,targetId,property,keyframes:keys(a,b),...extra});
+ const timeline={version:1,duration:4,currentTime:0,loop:false,zoom:1,clips:[{id:'work',name:'旋出与清洁演示',start:0,end:4,finish:'hold'},{id:'reset',name:'阀门复位演示',start:0,end:4,finish:'reset'}],tracks:[
+ track('rotate','部件旋出',wheel[0],'rotation.x',0,Math.PI,{pivot:[0,.1,0]}),
+ track('move','旋出位移',wheel[0],'position',[0,0,0],[0,.3,0]),
+ track('state','污渍到清洁',body[0],'material.state',0,1,{states:[{name:'污渍',color:'#795548',roughness:.95},{name:'清洁',color:'#dce8ed',roughness:.2}]}),
+ track('flow','回路流向',body[0],'flow',1,-1,{flow:{points:[[0,0,.5],[1,0,.5],[1,1,.5]],color:'#16b9f3',radius:.045,count:10,speed:.6,gateTrackId:'state',openValue:1}}),
+ ]};
+ app.timeline.loadJSON(timeline,{pushHistory:true});app.timeline.seek(4);
+ const saved=await app.saveProject();if(!saved)throw new Error('Model save failed');
+ return {bodyId:body[0],wheelId:wheel[0],timeline:saved.content?.timeline || timeline,pose:wheel[1].position.toArray(),version:saved.version};
+ });
+ fixture.detail=detail;save();
+ checks.push({name:'真实工程保存组合旋转、位移、材质状态和流向轨道',pass:true});
+ await editorFrame.evaluate(()=>{window.editorApp.setBottomMode?.('timeline');window.editorApp.timeline.selectedTrackId='flow';window.editorApp.timeline.render();});
+ await frame.locator('[data-action="animation-settings"]').click();
+ await expect(frame.locator('.animation-settings')).toBeVisible();await shot('01-animation-settings');
+ await frame.locator('.animation-settings [data-close]').first().click();
+ await shot('02-editor-end-pose');
+ const latest=await call(`tran/v1/content/projects/${fixture.modelId}`,session.accessToken);
+ assert.equal(latest.data.currentVersion.content.timeline.clips.length,2);
+ fixture.savedDocument=latest.data.currentVersion.content;save();
+ await page.reload();await expect(page.locator('iframe')).toBeVisible();
+ const reopened=page.frames().find(f=>f.url().includes('legacy-model-editor'));
+ await reopened.waitForFunction(()=>window.editorApp?.apiSession?.project && window.editorApp?.modelTargets?.size>0,{timeout:60000});
+ const replay=await reopened.evaluate(()=>{const app=window.editorApp;app.timeline.seek(0);const a=app.modelTargets.get(app.timeline.getTrack('move').targetId).position.toArray();app.timeline.seek(4);const b=app.modelTargets.get(app.timeline.getTrack('move').targetId).position.toArray();return {a,b,clips:app.timeline.clips,flows:app.animationRuntime.flows.length};});
+ assert.notDeepEqual(replay.a,replay.b);assert.equal(replay.flows,1);assert.equal(replay.clips.length,2);
+ checks.push({name:'刷新重开后动作片段、复位基准和流向配置恢复',pass:true,detail:replay});
+ await shot('03-model-reopened');
+ const published=await reopened.evaluate(async()=>{const result=await window.editorApp.publishToSceneLibrary();return Boolean(result);});
+ const result=await call(`tran/v1/content/projects/${fixture.modelId}`,session.accessToken);
+ assert.equal(result.data.project.status,'PUBLISHED',JSON.stringify({published,status:result.data.project.status}));
+ fixture.modelVersionId=result.data.project.publishedVersionId;save();
+ checks.push({name:'正式发布固定动画模型版本',pass:true,versionId:fixture.modelVersionId});
+ await shot('04-model-published');
+} catch(error){checks.push({name:'模型动作端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await shot('failure').catch(()=>{});}
+finally {fs.writeFileSync(path.join(report,'model-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}
diff --git a/tools/animation-capabilities-report.mjs b/tools/animation-capabilities-report.mjs
new file mode 100644
index 0000000..23c627f
--- /dev/null
+++ b/tools/animation-capabilities-report.mjs
@@ -0,0 +1,36 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+const root=path.resolve('reports/animation-capabilities-20260912');
+const sections=[['model','模型编辑与发布'],['guide','指导书在线使用与交付'],['offline','离线播放'],['teaching','正式教学视口'],['training-editor','训练编辑器绑定']];
+const escape=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
+const results=sections.map(([key,name])=>({key,name,...JSON.parse(fs.readFileSync(path.join(root,key+'-checks.json'),'utf8'))}));
+for(const result of results){assert.ok(result.checks.every(c=>c.pass),result.key);assert.equal(result.errors.length,0,result.key);}
+const f=JSON.parse(fs.readFileSync(path.join(root,'fixture.json'),'utf8'));
+const shots=[['01-animation-settings','动作与轨道设置:范围、结束处理、路径和阀门联动'],['02-editor-end-pose','模型编辑器中的组合动作'],['03-model-reopened','刷新重开后恢复动画配置'],['05-guide-model-animation','正式指导书模型视口'],['07-offline-before','离线包加载模型'],['08-offline-animation','离线片段播放及暂停'],['09-teaching-animation','教学步骤:保持末帧、清洁状态和流向开通'],['10-teaching-reset','教学步骤:播放结束复位并关闭流向'],['11-training-action-library','训练编辑器读取正式动作目录'],['12-training-action-bound','通过页面绑定并保存动作']];
+const total=results.reduce((n,r)=>n+r.checks.length,0);
+const html=`动画能力实施与测试报告 · 2026-09-12
+
+数字车间 · 功能实施 / 真实接口 / 浏览器端到端
模型动画能力实施与测试报告
2026-09-12 · ${total} 项端到端检查通过
按已确认范围补齐命名动作、组合运动、材质状态、流向讲解和跨模块复用。使用独立的牛奶卡车测试工程验证,不修改原始模型和既有业务任务。
+已实施能力
| 能力 | 实现与使用 |
+| 动作片段与机械组合运动 | 时间轴设置命名起止范围;旋转中心使用部件局部坐标;位移与 XYZ 旋转叠加,关键帧错开形成顺序动作;支持保持末帧和回到起点。蓝图按名称播放片段。 |
+| 状态与材质 | 新增缩放、显隐、不透明度、颜色、材质状态轨道;材质副本隔离,不污染其他实例。录制与保存保留中性基准,旋转中心偏移不会被重复写入位移关键帧。 |
+| 回路流向 | 配置路径点、颜色、速度、流点数量、正向/反向/停止;可关联标量轨道控制开通。用于工作原理讲解。 |
+| 跨模块复用 | 训练编辑器从正式目录选动作,保存步骤绑定;教学任务固定模型/场景/训练版本;指导书在线模型与离线 ZIP 使用同一动画求值规则。原生 GLB 动画仍可选择。 |
+实测结果
${results.map(r=>`${escape(r.name)}
| 检查 | 结果 |
${r.checks.map(c=>`| ${escape(c.name)} | 通过 |
`).join('')}
查看原始检查记录 · 浏览器脚本异常 ${r.errors.length} 条
`).join('')}
+补充验证:前端类型检查及生产构建通过;生成的模型编辑器 public/dist 文件哈希一致。后端 18 条校验、快照与考试提示隔离用例通过;原型 5 项合同测试、Web 4 项场景/缓存/落地测试通过。构建仅有现有的大分块提示。
+测试采用真实登录、GLB、保存/发布/下载接口。部分配置和视口内部状态由 Playwright 调用页面模块核对;教学原生 GLB 切换为浏览器运行时兼容检查。离线用例阻断业务接口及公网请求,外部请求数为 0。
+可继续使用的测试数据
| 数据 | 标识 | 入口 |
+| 组合动作模型 | ${f.modelId} · 固定模型版本 ${f.modelVersionId} | 打开模型编辑器 |
+| 测试场景 | ${f.animationSceneId} · 版本 ${f.animationSceneVersionId} | 打开场景 |
+| 两步骤测试训练 | ${f.animationTrainingId} | 打开训练编排 |
+| 固定快照教学任务 | ${f.animationAssignmentId} · 训练版本 482 | 打开教学预览 |
+| 动画指导书 | ${f.guideId} | 打开指导书 · 下载实测离线包 |
+训练当前保留实测保存的草稿;已有任务仍使用旧发布快照,这正是版本隔离测试验证的行为。试验性教学任务未产生学员成绩。
+使用顺序与边界
- 模型编辑器时间轴添加轨道,选择部件、属性及关键帧;进入「动作与轨道设置」配置片段、枢轴、材质状态或流向。
- 保存并发布模型,在场景选择固定版本;训练的「步骤动画」选择正式动作并保存。
- 发布训练、下发新任务后在教学端使用;指导书关联模型片段后可在线播放或导出离线 ZIP。
+本次没有数据库表改动。未加入新的密钥文件、独立服务或认证方式。代码尚未提交 Git。
+污渍/清洁通过颜色和粗糙度等 PBR 参数表达;贴图擦除、软管/密封圈弹性变形、真实流体压力计算、整片视频导出不在本次实施范围。表格中的具体装备动作仍需配置部件、路径和时序,本报告不代表已经制作完成全部动画素材。
+浏览器截图
点击图片查看原图。
${shots.map(([file,title])=>`
${escape(title)}`).join('')}
`;
+fs.writeFileSync(path.join(root,'index.html'),html);
+fs.writeFileSync(path.join(root,'summary.json'),JSON.stringify({date:'2026-09-12',passed:total,failed:0,backendTests:18,prototypeContractTests:5,webTests:4,build:'passed',ddlChanges:false},null,2));
+console.log(`Report generated: ${total} checks passed`);
diff --git a/tools/animation-guide-live.mjs b/tools/animation-guide-live.mjs
new file mode 100644
index 0000000..fe0e709
--- /dev/null
+++ b/tools/animation-guide-live.mjs
@@ -0,0 +1,80 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+import {chromium,expect} from '@playwright/test';
+import {login,call} from './role-permissions-live.mjs';
+const report=path.resolve('reports/animation-capabilities-20260912'),fixturePath=path.join(report,'fixture.json');
+const fixture=JSON.parse(fs.readFileSync(fixturePath,'utf8')),session=await login('root');
+const save=()=>fs.writeFileSync(fixturePath,JSON.stringify(fixture,null,2));
+const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
+const context=await browser.newContext({viewport:{width:1920,height:1080}});
+await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session);
+const page=await context.newPage(),checks=[],errors=[];
+page.on('pageerror',e=>errors.push(e.message));
+const shot=async name=>page.screenshot({path:path.join(report,name+'.png')});
+try {
+ await page.goto('http://127.0.0.1:6180/content/ofd');await expect(page.locator('.legacy-guide-page')).toBeVisible({timeout:60000});
+ if(!fixture.guideId){
+ fixture.guide=await page.evaluate(async({modelId,versionId,candidateId})=>{
+ const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
+ const {blankGuideState}=await import('/src/features/guide-legacy/formalDocument.ts');
+ const {createMediaBlock}=await import('/src/features/guide-legacy/demo-src/ofd-media.js');
+ const server=new FormalGuideStore(),state=blankGuideState();
+ state.project.name='动画能力验收指导书-20260912';state.project.owner='功能验收';state.project.equipment='牛奶卡车动画测试';
+ const record=candidateId?await server.load(candidateId):await server.createOFDProject({title:state.project.name,state});
+ await server.loadCatalog();
+ const key=`${modelId}@${versionId}`;
+ const step=record.state.chapters[0].steps[0];step.title='旋出、清洁和流向演示';step.summary='选择动作片段,观察车轮组合运动、污渍到清洁状态变化以及回路开通后的流向。';
+ step.modelRef={assetId:key,modelId:key,nodeName:'Wheels'};
+ step.animationRef={clipName:'旋出与清洁演示',start:0,end:4,loop:false};step.animation='旋出与清洁演示';
+ step.mediaBlocks=[createMediaBlock({kind:'model',assetId:key,assetName:'组合动作演示',clipName:'旋出与清洁演示',start:0,end:4})];
+ const saved=await server.saveOFDProjectState(record.id,record.state);
+ const resource=saved.state.resources.find(r=>r.id===key);
+ server.dispose();return {id:record.id,resource:{id:resource.id,tracks:resource.timeline?.tracks.length,clips:resource.timeline?.clips.length,overrides:Object.keys(resource.modelOverrides||{}).length}};
+ },{modelId:fixture.modelId,versionId:fixture.modelVersionId,candidateId:fixture.guideCandidateId});
+ fixture.guideId=fixture.guide.id;save();
+ }
+ assert.equal(fixture.guide.resource.tracks,4);assert.equal(fixture.guide.resource.clips,2);
+ checks.push({name:'指导书通过正式接口固定引用模型版本并持久化动画元数据',pass:true,resource:fixture.guide.resource});
+ await page.goto(`http://127.0.0.1:6180/content/ofd/${fixture.guideId}/studio?view=editor`);
+ await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000});
+ await page.locator('[data-action="select-step"]').first().click();
+ await page.locator('[data-action="canvas-block"][data-block-id*="media:"]').first().click();
+ await page.locator('[data-action="media-open"]').first().click();
+ await expect(page.locator('.ofd-media-panel canvas')).toBeVisible({timeout:60000});
+ await shot('05-guide-model-animation');
+ console.log('MEDIA_BUTTONS',await page.locator('.ofd-media-panel button').allTextContents());
+ const outcome=await page.evaluate(async({guideId,modelId,versionId})=>{
+ const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
+ const {createGuideViewport}=await import('/src/features/guide-legacy/demo-src/ofd-viewport.js');
+ const server=new FormalGuideStore();await server.load(guideId);
+ const asset=server.record.state.resources.find(r=>r.id===`${modelId}@${versionId}`);
+ const viewport=createGuideViewport();
+ const blob=(await server.assetStore.get(asset.assetKey)).blob;
+ await viewport.load(asset.id,blob,asset);
+ const clips=viewport.clips.map(c=>c.name),wheel=viewport.findModelNode('Wheels');
+ viewport.playClip('旋出与清洁演示');viewport.pause();
+ const initial=wheel.position.toArray();
+ // Seek through the same mixer transport used by reader controls.
+ const {createAnimationRuntimeTools}=await import('/src/features/guide-legacy/demo-src/animation-runtime.js');
+ const THREE=await import('/node_modules/.vite/deps/three.js');
+ const tools=createAnimationRuntimeTools(THREE);tools.apply(viewport.editorTimeline,4);
+ const final=wheel.position.toArray(),state=viewport.editorTimeline.materials.values().next().value.clones[0].roughness;
+ viewport.dispose();server.dispose();return {clips,initial,final,state};
+ },{guideId:fixture.guideId,modelId:fixture.modelId,versionId:fixture.modelVersionId});
+ assert.ok(outcome.clips.includes('旋出与清洁演示'));assert.ok(outcome.clips.includes('阀门复位演示'));assert.notDeepEqual(outcome.initial,outcome.final);assert.equal(outcome.state,.2);
+ checks.push({name:'正式指导书视口加载命名动作并还原组合运动与材质状态',pass:true,detail:outcome});
+ // Publish using the page's normal delivery pipeline.
+ await page.keyboard.press('Escape');
+ const close=page.locator('[data-action="media-close"]');if(await close.count())await close.first().click();
+ await page.locator('[data-action="save"]').click();await page.waitForTimeout(700);
+ await page.locator('[data-action="open-publish"]').click();
+ const wait=page.waitForResponse(r=>r.url().endsWith('/publish')&&r.request().method()==='POST',{timeout:180000});
+ await page.locator('[data-action="lifecycle-action"][data-lifecycle-action="publish"]').click();
+ const published=await wait;assert.ok(published.ok(),(await published.text()).slice(0,300));
+ await shot('06-guide-published');
+ const response=await context.request.get(`http://127.0.0.1:6180/api/tran/v1/teaching/guides/${fixture.guideId}/delivery?format=OFFLINE_HTML`,{headers:{Authorization:`Bearer ${session.accessToken}`},timeout:120000});
+ assert.ok(response.ok());fs.writeFileSync(path.join(report,'animation-guide.zip'),await response.body());
+ checks.push({name:'正式发布并下载带动画的离线指导书',pass:true});
+}catch(error){checks.push({name:'指导书动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await shot('guide-failure').catch(()=>{});}
+finally{fs.writeFileSync(path.join(report,'guide-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}
diff --git a/tools/animation-offline-live.mjs b/tools/animation-offline-live.mjs
new file mode 100644
index 0000000..551febc
--- /dev/null
+++ b/tools/animation-offline-live.mjs
@@ -0,0 +1,42 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import http from 'node:http';
+import {execFileSync} from 'node:child_process';
+import assert from 'node:assert/strict';
+import {chromium,expect} from '@playwright/test';
+const report=path.resolve('reports/animation-capabilities-20260912'),root=path.join(report,'offline');
+execFileSync('python',['-c',`import zipfile,pathlib
+root=pathlib.Path(${JSON.stringify(root)}).resolve()
+with zipfile.ZipFile(${JSON.stringify(path.join(report,'animation-guide.zip'))}) as archive:
+ for entry in archive.infolist():
+ target=(root/entry.filename).resolve()
+ if not target.is_relative_to(root): raise ValueError('Invalid archive path')
+ archive.extract(entry,root)
+`]);
+const mime={'.html':'text/html','.js':'text/javascript','.wasm':'application/wasm','.glb':'model/gltf-binary','.css':'text/css','.png':'image/png','.jpg':'image/jpeg'};
+const server=http.createServer((req,res)=>{const file=path.resolve(root,'.'+decodeURIComponent(new URL(req.url,'http://localhost').pathname).replace(/\/$/,'/index.html'));if(!file.startsWith(root+path.sep)||!fs.existsSync(file)){res.writeHead(404).end();return;}res.setHeader('Content-Type',mime[path.extname(file)]||'application/octet-stream');fs.createReadStream(file).pipe(res);});
+await new Promise(resolve=>server.listen(18779,'127.0.0.1',resolve));
+const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
+const page=await browser.newPage({viewport:{width:1440,height:1000}}),checks=[],errors=[],external=[];
+page.on('pageerror',e=>errors.push(e.message));
+await page.route('**/*',route=>{const url=new URL(route.request().url());if(url.origin!=='http://127.0.0.1:18779'&&!['data:','blob:'].includes(url.protocol)){external.push(url.origin);return route.abort();}return route.continue();});
+try{
+ await page.goto('http://127.0.0.1:18779/');
+ await page.locator('[data-jump="1"]').first().click();
+ await page.locator('[data-action="open-model"]').first().click();
+ await expect(page.locator('[data-model-status]')).not.toContainText(/正在加载|失败|未加载/,{timeout:60000});
+ await expect(page.locator('[data-model-stage] canvas')).toBeVisible({timeout:60000});
+ await page.locator('[data-model-stage] canvas').scrollIntoViewIfNeeded();
+ const canvas=page.locator('[data-model-stage] canvas');
+ const before=await canvas.screenshot();await page.screenshot({path:path.join(report,'07-offline-before.png')});
+ await page.locator('[data-model-action="play-animation"]').click();
+ await expect(page.locator('[data-model-status]')).toContainText('旋出与清洁演示');
+ await page.waitForTimeout(1800);const after=await canvas.screenshot();
+ assert.notDeepEqual(before,after,'Offline canvas must change while the authored action plays');
+ await page.locator('[data-model-action="pause-animation"]').click();
+ await page.screenshot({path:path.join(report,'08-offline-animation.png')});
+ checks.push({name:'下载的离线包可播放命名组合动作,画布发生变化并可暂停',pass:true});
+ assert.equal(external.length,0);assert.equal(errors.length,0,errors.join('\n'));
+ checks.push({name:'断开业务服务与公网后仍可加载模型、引擎和动画',pass:true});
+}catch(error){checks.push({name:'离线动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await page.screenshot({path:path.join(report,'offline-failure.png')}).catch(()=>{});}
+finally{fs.writeFileSync(path.join(report,'offline-checks.json'),JSON.stringify({checks,errors,external},null,2));await browser.close();await new Promise(resolve=>server.close(resolve));}
diff --git a/tools/animation-teaching-live.mjs b/tools/animation-teaching-live.mjs
new file mode 100644
index 0000000..abf0535
--- /dev/null
+++ b/tools/animation-teaching-live.mjs
@@ -0,0 +1,114 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+import {chromium,expect} from '@playwright/test';
+import {login,call} from './role-permissions-live.mjs';
+const report=path.resolve('reports/animation-capabilities-20260912'),file=path.join(report,'fixture.json');
+const fixture=JSON.parse(fs.readFileSync(file,'utf8')),session=await login('root');
+const save=()=>fs.writeFileSync(file,JSON.stringify(fixture,null,2));
+const api=async(url,body,method)=>{const r=await call('tran/v1/'+url,session.accessToken,body,method);assert.equal(r.status,200,`${url}: ${r.message}`);return r.data;};
+const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
+const context=await browser.newContext({viewport:{width:1920,height:1080}});
+await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session);
+const page=await context.newPage(),checks=[],errors=[];page.on('pageerror',e=>errors.push(e.message));
+try {
+ await page.goto('http://127.0.0.1:6180/content/scenes');
+ if(!fixture.animationSceneId){
+ const content=await page.evaluate(async f=>{
+ const {createEmptySceneDocument,createObjectDocument}=await import('/src/features/editors/scene/presets.ts');
+ const scene=createEmptySceneDocument(),object=createObjectDocument('box');
+ scene.name='动画能力验收场景-20260912';
+ Object.assign(object,{id:'animation-truck',type:'model',name:'组合动作牛奶卡车',targetProjectId:f.modelId,targetVersionId:f.modelVersionId,assetCode:f.savedDocument.modelResource.assetCode,assetId:f.savedDocument.modelId});
+ object.semantic.modelId=f.savedDocument.modelId;object.semantic.deviceId='ANIM-TRUCK';object.semantic.interactionId='animation-truck';object.semantic.operable=true;
+ scene.objects=[object];scene.rendering.cameraPosition=[6,4,7];scene.rendering.cameraTarget=[0,1,0];
+ return scene;
+ },fixture);
+ const created=await api('content/projects',{type:'SCENE',name:content.name,categoryCode:'WORKSHOP',content,dependencies:[{targetProjectId:fixture.modelId,targetVersionId:fixture.modelVersionId,relationType:'SCENE_MODEL',required:true}],assets:[]});
+ fixture.animationSceneId=created.project.id;save();
+ }
+ let scene=await api(`content/projects/${fixture.animationSceneId}`);
+ if(scene.project.status!=='PUBLISHED'&&!scene.currentVersion.content.objects[0].semantic.deviceId){
+ scene.currentVersion.content.objects[0].semantic.deviceId='ANIM-TRUCK';
+ scene=await api(`content/projects/${fixture.animationSceneId}`,{type:'SCENE',name:scene.project.name,categoryCode:'WORKSHOP',content:scene.currentVersion.content,version:scene.project.version,dependencies:scene.dependencies,assets:[]},'PUT');
+ }
+ if(scene.project.status!=='PUBLISHED')await api(`content/projects/${fixture.animationSceneId}/publish`,{version:scene.project.version});
+ const publishedScene=await api(`content/projects/${fixture.animationSceneId}`);fixture.animationSceneVersionId=publishedScene.project.publishedVersionId;save();
+ if(!fixture.animationTrainingId){
+ const content=await page.evaluate(async({f,scene})=>{
+ const {createTrainingProject}=await import('/src/features/guide-legacy/demo-src/training-project-io.js');
+ const doc=createTrainingProject();doc.name='动画能力验收训练-20260912';doc.task.title=doc.name;
+ const step=doc.steps[0];step.id='animation-step';step.code='animation-step';step.title='旋出、清洁与回路流向';step.targetId='animation-truck';step.targetName='组合动作牛奶卡车';step.duration=4;
+ step.animationBinding={assetId:`animation-${f.modelId}@${f.modelVersionId}-work`,modelAssetId:`${f.modelId}@${f.modelVersionId}`,clipName:'旋出与清洁演示',sourceStart:0,finish:'hold',duration:4};
+ doc.steps=[step,{...structuredClone(step),id:'reset-step',code:'reset-step',title:'播放后回到起点',animationBinding:{...step.animationBinding,clipName:'阀门复位演示',finish:'reset'}}];
+ doc.steps.forEach(step=>{step.stepCode=step.code;step.actionCode='CHECK-'+step.code;});
+ doc.scenario.sceneResource={id:`${f.animationSceneId}@${f.animationSceneVersionId}`,projectId:f.animationSceneId,versionId:f.animationSceneVersionId,name:scene.name,snapshot:scene};
+ doc.scenario.visibleParts=['animation-truck'];doc.scenario.operableParts=['animation-truck'];
+ doc.resources.models=[{id:`${f.modelId}@${f.modelVersionId}`,modelId:f.savedDocument.modelId,targetProjectId:f.modelId,targetVersionId:f.modelVersionId,timeline:f.savedDocument.timeline}];
+ return doc;
+ },{f:fixture,scene:publishedScene.currentVersion.content});
+ const created=await api('content/projects',{type:'TRAINING',name:content.name,categoryCode:'MAINTENANCE',trainingMode:'VIRTUAL',content,dependencies:[{targetProjectId:fixture.animationSceneId,targetVersionId:fixture.animationSceneVersionId,relationType:'TRAINING_SCENE',required:true}],assets:[]});
+ fixture.animationTrainingId=created.project.id;save();
+ }
+ let training=await api(`content/projects/${fixture.animationTrainingId}`);
+ if(training.project.status!=='PUBLISHED'&&training.currentVersion.content.steps[0].actionCode==='CHECK'){
+ training.currentVersion.content.steps.forEach(step=>{step.stepCode=step.code;step.actionCode='CHECK-'+step.code;});
+ training=await api(`content/projects/${fixture.animationTrainingId}`,{type:'TRAINING',name:training.project.name,categoryCode:'MAINTENANCE',trainingMode:'VIRTUAL',content:training.currentVersion.content,version:training.project.version,dependencies:training.dependencies,assets:[]},'PUT');
+ }
+ if(training.project.status!=='PUBLISHED')await api(`content/projects/${fixture.animationTrainingId}/publish`,{version:training.project.version});
+ const published=await api(`content/projects/${fixture.animationTrainingId}`);fixture.animationTrainingVersionId=published.project.publishedVersionId;save();
+ if(!fixture.animationAssignmentId){
+ const a=await api('teaching/assignments',{code:'ANIM-CAP-'+Date.now(),name:'动画能力验收任务-20260912',description:'开发环境动画播放验证',channel:'VIRTUAL',assignmentKind:'TRAINING',executionMode:'DEMO',audienceType:'COMMON',collaborationMode:'INDIVIDUAL',digitalHumanAllowed:false,contentProjectId:fixture.animationTrainingId,contentVersionId:fixture.animationTrainingVersionId,members:[]});
+ fixture.animationAssignmentId=a.id;save();
+ }
+ const assignment=await api(`teaching/assignments/${fixture.animationAssignmentId}`);
+ fixture.animationAssignmentContentVersionId=assignment.contentVersionId;save();
+ const object=assignment.definitionSnapshot.teachingScene.objects.find(o=>o.targetProjectId===fixture.modelId);
+ assert.equal(object.modelTimeline.clips.length,2);assert.equal(object.modelTimeline.tracks.length,4);assert.ok(Object.keys(object.modelOverrides).length>0);
+ assert.equal(object.targetVersionId,fixture.modelVersionId);
+ checks.push({name:'真实教学任务固定模型版本、动作、材质、枢轴和流向配置',pass:true,modelVersionId:object.targetVersionId,assignmentId:assignment.id});
+ await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/preview/${assignment.id}`);
+ await expect(page.locator('.teaching-three-scene canvas')).toBeVisible({timeout:60000});
+ await page.getByRole('button',{name:'适应',exact:true}).click();
+ await page.waitForTimeout(6500);
+ // Inspect the real component's runtime, not a second test renderer.
+ const inspect=()=>page.evaluate(()=>{
+ const host=document.querySelector('.teaching-three-scene');let instance=host?.__vueParentComponent;
+ while(instance&&!instance.type.__name?.includes('TeachingRunScene'))instance=instance.parent;
+ const state=instance?.devtoolsRawSetupState;
+ if(!state)throw new Error('Teaching scene setup unavailable');
+ const entry=state.timelineRuntimes[0],runtime=entry?.runtime;
+ const wheel=runtime?.bindings.find(b=>b.track.id==='move')?.object;
+ const material=runtime?.materials.values().next().value;
+ const mesh=runtime?.materials.keys().next().value;
+ return {status:state.sceneStatus?.value || state.sceneStatus,time:runtime?.time,bindings:runtime?.bindings.length,unresolved:runtime?.unresolved,
+ wheel:wheel?.position.toArray(),base:wheel&&runtime.bases.get(wheel).position,roughness:material?.clones[0].roughness,
+ actualRoughness:mesh?.material.roughness,flowVisible:runtime?.flows[0]?.dots.some(dot=>dot.visible),step:state.props.currentStepCode};
+ });
+ const state=await inspect();assert.doesNotMatch(String(state.status),/失败/);
+ assert.equal(state.bindings,4);assert.equal(state.unresolved,0);assert.equal(state.time,4);assert.equal(state.roughness,.2);assert.equal(state.actualRoughness,.2);assert.equal(state.flowVisible,true);assert.notDeepEqual(state.wheel,state.base);
+ await page.screenshot({path:path.join(report,'09-teaching-animation.png')});
+ checks.push({name:'正式教学预览实际播放指定片段并保持末帧,四条轨道全部绑定',pass:true,detail:state});
+ await page.getByRole('button',{name:'引导完成',exact:true}).click();
+ await page.getByRole('button',{name:'下一步',exact:true}).click();
+ await page.waitForTimeout(6500);
+ const reset=await inspect();assert.equal(reset.step,'reset-step');assert.equal(reset.time,0);assert.equal(reset.roughness,.95);assert.equal(reset.flowVisible,false);assert.deepEqual(reset.wheel,reset.base);
+ await page.screenshot({path:path.join(report,'10-teaching-reset.png')});
+ checks.push({name:'切换步骤后播放复位片段,结束恢复起点并关闭流向',pass:true,detail:reset});
+ const native=await page.evaluate(async()=>{
+ let instance=document.querySelector('.teaching-three-scene').__vueParentComponent;
+ while(!instance.type.__name?.includes('TeachingRunScene'))instance=instance.parent;
+ const s=instance.devtoolsRawSetupState,entry=s.nativePlayers[0];
+ if(!entry?.clips.length)throw new Error('Fixture native GLB clip missing');
+ const clip=entry.clips[0],step=s.definitionSteps().find(step=>s.stepCodeOf(step)===s.props.currentStepCode);
+ const binding=step.animationBinding,previous=binding.clipName;
+ binding.clipName=clip.name;s.syncStepTimelines();
+ await new Promise(resolve=>setTimeout(resolve,900));
+ const time=entry.mixer.clipAction(clip).time;
+ binding.clipName=previous;s.syncStepTimelines();
+ return {name:clip.name,time};
+ });
+ assert.ok(native.time>0);checks.push({name:'同一教学视口原生 GLB 动画与自编片段切换兼容(浏览器运行时检查)',pass:true,detail:native});
+ fs.writeFileSync(path.join(report,'teaching-inspection.json'),JSON.stringify(state,null,2));
+ assert.equal(errors.length,0,errors.join('\n'));
+}catch(error){checks.push({name:'教学动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await page.screenshot({path:path.join(report,'teaching-failure.png')}).catch(()=>{});}
+finally{fs.writeFileSync(path.join(report,'teaching-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}
diff --git a/tools/animation-training-editor-live.mjs b/tools/animation-training-editor-live.mjs
new file mode 100644
index 0000000..16ff665
--- /dev/null
+++ b/tools/animation-training-editor-live.mjs
@@ -0,0 +1,37 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+import {chromium,expect} from '@playwright/test';
+import {login,call} from './role-permissions-live.mjs';
+const report=path.resolve('reports/animation-capabilities-20260912'),f=JSON.parse(fs.readFileSync(path.join(report,'fixture.json'),'utf8'));
+const session=await login('root'),browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
+const context=await browser.newContext({viewport:{width:1920,height:1080}});
+await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session);
+const page=await context.newPage(),checks=[],errors=[];page.on('pageerror',e=>errors.push(e.message));
+try{
+ await page.goto(`http://127.0.0.1:6180/content/training-projects/${f.animationTrainingId}/legacy`);
+ const open=page.locator('[data-action="open-content-resources"][data-resource-scope="animation"]');
+ await expect(open).toBeVisible({timeout:60000});await open.click();
+ const key=`animation-content-model-${f.modelId}-${f.modelVersionId}-work`;
+ const button=page.locator(`[data-action="bind-content-resource"][data-resource-id="${key}"]`);
+ await expect(button).toBeVisible({timeout:60000});
+ await expect(page.locator(`[data-resource-id="animation-content-model-${f.modelId}-${f.modelVersionId}-reset"]`)).toBeVisible();
+ await button.scrollIntoViewIfNeeded();await page.screenshot({path:path.join(report,'11-training-action-library.png')});
+ if(await button.innerText()==='取消绑定')await button.click();
+ await button.click();
+ await page.locator('[data-action="close-dialog"]').last().click();
+ await expect(page.locator('.te-step-animation-binding')).toContainText('旋出与清洁演示');
+ const save=page.locator('[data-action="save"]');console.log('SAVE_COUNT',await save.count());
+ const wait=page.waitForResponse(r=>r.request().method()==='PUT'&&r.url().includes(`/content/projects/${f.animationTrainingId}`),{timeout:60000});
+ await save.click();const response=await wait;assert.ok(response.ok(),(await response.text()).slice(0,200));
+ const result=await call(`tran/v1/content/projects/${f.animationTrainingId}`,session.accessToken);
+ const binding=result.data.currentVersion.content.steps[0].animationBinding;
+ assert.equal(binding.clipName,'旋出与清洁演示');assert.equal(binding.modelAssetId,`content-model-${f.modelId}-${f.modelVersionId}`);
+ checks.push({name:'训练编辑器从正式模型库选择命名动作并通过真实保存接口持久化',pass:true,binding});
+ await page.screenshot({path:path.join(report,'12-training-action-bound.png')});
+ const assignment=await call(`tran/v1/teaching/assignments/${f.animationAssignmentId}`,session.accessToken);
+ assert.equal(assignment.data.contentVersionId,f.animationAssignmentContentVersionId || '482');
+ checks.push({name:'训练编辑草稿不会覆盖已创建任务的固定发布快照',pass:true});
+ assert.equal(errors.length,0,errors.join('\n'));
+}catch(error){checks.push({name:'训练动画绑定端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await page.screenshot({path:path.join(report,'training-editor-failure.png')}).catch(()=>{});}
+finally{fs.writeFileSync(path.join(report,'training-editor-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}