教员系统,分队数据库,保存分队

This commit is contained in:
liaoboping 2025-09-10 16:23:40 +08:00
parent 8f2f953a14
commit dcfdf8aca0
9 changed files with 556 additions and 199 deletions

View File

@ -43,16 +43,10 @@ export default class MyCesium {
}
viewer = null
// 选中模型
selectedModel = null
// 右鍵菜單
contextMenu = {
visible: false,
left: 0,
top: 0,
}
// 未完成的在地图上移动类的操作
operations = []
// 已添加的军标
plots = []
constructor(dom, options = {}) {
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
@ -127,144 +121,6 @@ export default class MyCesium {
// 用于启用或禁用指南针外环。true是启用false是禁用。默认值为true。如果将选项设置为false则该环将可见但无效。
enableCompassOuterRing: true,
})
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction(({ position }) => {
if (this.selectedModel) {
this.deselectModel(this.selectedModel)
this.selectedModel = null
}
const pickedObject = viewer.scene.pick(position)
if (!Cesium.defined(pickedObject) || !pickedObject.id) {
window.Emit('pickedObject', null)
return
}
const entity = pickedObject.id
if (
!entity.properties ||
(entity.properties.type != MyCesium.ENTITY_TYPES.MODEL && entity.properties.type != MyCesium.ENTITY_TYPES.IMAGE)
) {
window.Emit('pickedObject', null)
return
}
this.selectedModel = entity
this.selectModel(entity)
window.Emit('selectedModel', {
id: entity.id,
})
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
// 鼠标右键事件
handler.setInputAction(({ position }) => {
const pickedObject = viewer.scene.pick(position)
if (!Cesium.defined(pickedObject) || !pickedObject.id) return
const entity = pickedObject.id
const properties = entity.properties
if (
!properties ||
!properties.type ||
(properties.type != MyCesium.ENTITY_TYPES.MODEL && properties.type != MyCesium.ENTITY_TYPES.IMAGE)
) {
return
}
const nomove = properties.nomove
if (selectedModel && entity.id == selectedModel.id && !(nomove && nomove.valueOf())) {
this.showContextMenu(entity)
this.cancelPreviousOperation()
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
// shift + 左键事件
handler.setInputAction(
({ position }) => {
const pickedObject = viewer.scene.pick(position)
if (!Cesium.defined(pickedObject) || !pickedObject.id) return
const entity = pickedObject.id
if (!entity.properties || entity.properties.type != MyCesium.ENTITY_TYPES.MODEL) return
if (this.selectedModel) {
// 找到已有的properties中的from和to是选中的两个模型的id的线
const find = viewer.entities.values.find((item) => {
const props = item.properties
if (!props || !props.type || !props.from || !props.to) return false
return (
(props.from == this.selectedModel.id && props.to == entity.id) ||
(props.from == entity.id && props.to == this.selectedModel.id)
)
})
// 如果已经有了,移除
if (find) {
viewer.entities.remove(find)
const { from, to } = find.properties
// 发送删除线的事件
window.Emit('removeLineBetwenModel', {
id: find.id,
from: from.valueOf(),
to: to.valueOf(),
})
} else {
const id = Cesium.createGuid()
const currentTime = viewer.clock.currentTime
const positions = [this.selectedModel.position.getValue(currentTime), entity.position.getValue(currentTime)]
if (positions.some((item) => !item)) {
console.log('缺少位置信息')
return
}
this.addLineBetwenModel(id, positions, selectedModel.id, entity.id)
// 发送添加线的事件
window.Emit('addLineBetwenModel', {
positions,
id,
from: selectedModel.id,
to: entity.id,
})
}
}
},
Cesium.ScreenSpaceEventType.LEFT_CLICK,
Cesium.KeyboardEventModifier.SHIFT
)
}
/**
* 选中模型
* @param {Cesium.Entity} entity
*/
selectModel(entity) {
if (!entity.model) return
const {
properties: { color: _color },
} = entity
const color = _color.valueOf()
// 设置模型轮廓效果
entity.model.silhouetteSize = 2 // 轮廓线宽度
entity.model.silhouetteColor = Cesium.Color.fromCssColorString(color)
entity.label.show = true
}
/**
* 取消选中模型
* @param {Cesium.Entity} entity
*/
deselectModel(entity) {
if (!entity.model) return
entity.model.silhouetteSize = 0 // 设置为0取消轮廓
entity.label.show = false
}
/**
* 显示右键菜单
* @param {Cesium.Entity} entity
*/
showContextMenu(entity) {
const currentTime = viewer.clock.currentTime
// 获取entity中心点的屏幕坐标
const position = entity.position.getValue(currentTime)
const screenPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene, position)
this.contextMenu.left = screenPosition.x + 20
this.contextMenu.top = screenPosition.y
this.contextMenu.visible = true
}
// 取消未完成的在地图上移动类的操作(划线之类的)
@ -274,33 +130,13 @@ export default class MyCesium {
}
/**
* 在模型之间添加线
* @param id
* @param { Cesium.Cartesian3[] } positions
* @param fromId
* @param toId
* 根据四个角落坐标确定视口位置
* @param leftUp
* @param rightUp
* @param rightDown
* @param leftDown
*/
addLineBetwenModel(id, positions, fromId, toId) {
this.viewer.entities.add({
id,
polyline: {
positions,
width: 2,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.YELLOW,
dashLength: 20, //短划线长度
}),
clampToGround: true,
},
properties: {
type: MyCesium.ENTITY_TYPES.LINE,
from: fromId,
to: toId,
},
})
}
setViewerByAllCorner(leftUp, rightUp, rightDown, leftDown) {
setClientByAllCorner(leftUp, rightUp, rightDown, leftDown) {
const [leftUpLon, leftUpLat] = leftUp
const [rightUpLon, rightUpLat] = rightUp
const [rightDownLon, rightDownLat] = rightDown
@ -328,18 +164,17 @@ export default class MyCesium {
* @param x
* @param y
*/
addPlot(base64, { x, y }) {
addPlot(base64, screenPosition) {
this.cancelPreviousOperation()
const id = Cesium.createGuid()
const position = getCatesian3FromPX(this.viewer, { x, y })
if (!position) return
const position = getCatesian3FromPX(this.viewer, screenPosition)
if (!position) return false
const id = Cesium.createGuid()
const isEnemy = false
const color = 'red'
const radius = 150000
this.viewer.entities.add({
const plot = {
id,
position,
billboard: {
@ -364,6 +199,19 @@ export default class MyCesium {
radius,
collisions: new Set(),
},
})
}
this.viewer.entities.add(plot)
this.plots.push(plot)
// 1. 将屏幕坐标转换为世界坐标(在椭球体表面)
const cartesian = this.viewer.camera.pickEllipsoid(screenPosition, this.viewer.scene.globe.ellipsoid)
if (Cesium.defined(cartesian)) {
// 2. 将世界坐标 (Cartesian3) 转换为地理坐标 Cartographic (弧度)
const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
// 3. 将弧度转换为度数
const longitude = Cesium.Math.toDegrees(cartographic.longitude)
const latitude = Cesium.Math.toDegrees(cartographic.latitude)
return { plotId: id, longitude, latitude }
}
}
}

View File

@ -42,5 +42,6 @@ export default {
window.MyWebsocket = MyWebsocket
Vue.prototype.$bus = Bus
Vue.prototype.$console = window.console
},
}

View File

@ -261,6 +261,14 @@ export const constantRouterMap = [
},
],
},
{
path: '/simulationScene/instructor',
name: 'SimulationSceneInstructor',
component: () => import(/* webpackChunkName: "fail" */ '@/views/simulationScene/instructor/index.vue'),
meta: { title: '教员系统' },
},
{
path: '/simulationScene/systemSelect',
name: 'SimulationSceneSystemSelect',

View File

@ -0,0 +1,120 @@
<template>
<page-header-wrapper>
<Grid>
<a-card class="my-card">
<AntQueryTable
height="100%"
ref="fd-table"
:queryConfig="fdTable.queryConfig"
:tableConfig="fdTable.tableConfig"
:pageConfig="fdTable.pageConfig"
:showTool="fdTable.showTool"
>
<template #toolbar-left>
<a-button type="primary" icon="plus" @click="handleOpenAddModal()">新增</a-button>
</template>
<template #tablecell-action="{ record }">
<a-button type="text-primary" icon="edit" @click="handleOpenEditModal(record)"></a-button>
<a-button type="text-danger" icon="delete" @click="handleDelete(record)"></a-button>
</template>
</AntQueryTable>
</a-card>
</Grid>
<AntFormModal
:visible.sync="AEModal.visible"
:title="AEModal.title"
width="900px"
:formConfig="{ labelCol: { span: 3 }, wrapperCol: { span: 19 } }"
:formItems="AEModal.formItems"
:formRules="AEModal.formRules"
:formData="AEModal.formData"
:onSubmit="handleSubmitAE"
@success="handleSubmitAESuccess"
></AntFormModal>
</page-header-wrapper>
</template>
<script>
import { getAction, postAction } from '@/api/manage'
export default {
data() {
return {
fdTable: {
queryConfig: false,
tableConfig: {
query: () => getAction('/team/list'),
columns: [
{ dataIndex: 'serial' },
{ dataIndex: 'name', title: '分队名称', width: 'auto', minWidth: 160 },
{
dataIndex: 'type',
title: '分队类型',
width: 'auto',
minWidth: 160,
align: 'center',
customRender: (text) => ['作战分队', '保障分队'][text],
},
{ dataIndex: 'action' },
],
},
pageConfig: true,
showTool: true,
},
AEModal: {
visible: false,
title: '',
formItems: [
{ label: '分队名称', prop: 'name' },
{
label: '分队类型',
prop: 'type',
component: 'AntOriginSelect',
options: {
dataSource: () => ({
data: [
{ title: '作战分队', id: 0 },
{ title: '保障分队', id: 1 },
],
}),
},
},
{ label: '分队军标', prop: 'iconId', component: 'IconSelector' },
],
formRules: {},
formData: {},
},
}
},
methods: {
handleOpenAddModal() {
this.AEModal.title = '新增分队'
this.AEModal.formData = {}
this.AEModal.visible = true
},
handleOpenEditModal(record) {
this.AEModal.title = '编辑分队'
this.AEModal.formData = { ...record }
this.AEModal.visible = true
},
handleSubmitAE(formData) {
return postAction('/team/save', formData)
},
handleSubmitAESuccess() {
this.$refs['fd-table'].commitAction('query')
},
async handleDelete(record) {
try {
await this.$confirm({ title: '温馨提示', content: '确定要删除该分队吗?' })
const res = await getAction('/team/remove/' + record.id)
this.$message.success(res.message)
this.$refs['fd-table'].commitAction('query')
} catch (error) {
console.log(error)
}
},
},
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,24 @@
<template>
<Grid
:columns="[5, 3, 2]"
:rows="[2, 3, 5]"
gap="0px"
:style="{ width: size, height: size, cursor: 'pointer' }"
title="新建房间"
v-on="$listeners"
>
<a-icon type="home" :style="{ fontSize: `calc(${size} * 8 / 10)`, gridColumn: '1 / 3', gridRow: '2 / 4' }" />
<a-icon type="plus" :style="{ fontSize: `calc(${size} * 5 / 10)`, gridColumn: '2 / 4', gridRow: '1 / 3' }" />
</Grid>
</template>
<script>
export default {
props: {
size: { type: String, default: '30px' },
},
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,305 @@
<template>
<Grid class="instructor-page" :columns="[1, 2, 2]">
<ModuleWrapper title="数据库子系统"> </ModuleWrapper>
<ModuleWrapper title="想定列表">
<template #extra>
<a-icon title="新建想定" type="plus" style="font-size: 30px" @click="handleOpenAddScenarioModal()" />
</template>
<Flex fd="co" class="normal">
<div class="scenario-list flex-1">
<Flex v-for="item in scenario.listData" :key="item.id" ai="c" class="scenario-item">
<div class="flex-1">
<div class="scenario-name">{{ item.id }}{{ item.name }}</div>
<Flex ai="c">
<div class="scenario-author">{{ item.author }}</div>
<div class="scenario-create-time">{{ item.createTime.replace('T', ' ') }}</div>
</Flex>
</div>
<a-icon
title="删除"
type="delete"
style="font-size: 30px; margin-right: 30px; color: red"
@click="handleDeleteScenario(item)"
/>
<a-icon
title="预设"
type="setting"
style="font-size: 30px; margin-right: 30px"
@click="handleOpenSetting(item)"
/>
<AddRoom @click="handleOpenAddRoomModal(item)" />
</Flex>
</div>
<Flex ai="c" jc="c">
<a-pagination
v-model="scenario.pagination.pageNum"
:total="scenario.pagination.total"
:pageSize="scenario.pagination.pageSize"
show-less-items
:showTotal="
(total) => `
共${total}`
"
@change="getScenarioList"
/>
</Flex>
</Flex>
</ModuleWrapper>
<ModuleWrapper title="房间列表">
<Flex fd="co" class="normal">
<Flex fw="w" ac="fs" class="room-list flex-1">
<div class="room-item" v-for="item in room.listData" :key="item.id">
<Flex class="room-card">
<Flex fd="co" jc="c" class="room-info flex-1">
<div class="room-name"><a-icon type="home" style="margin-right: 10px" />{{ item.roomName }}</div>
<div class="room-scenario-relation">想定编号{{ item.scenarioId }}</div>
<div class="room-create-time">{{ item.createTime.slice(0, 19).replace('T', ' ') }}</div>
</Flex>
<Flex class="room-enter" ai="c" title="开始训练" @click="handleEnterRoom(item)">
<a-icon type="right" style="font-size: 20px" />
</Flex>
</Flex>
</div>
</Flex>
<Flex ai="c" jc="c">
<a-pagination
v-model="room.pagination.pageNum"
:total="room.pagination.total"
:pageSize="room.pagination.pageSize"
show-less-items
:showTotal="(total) => `共${total}条`"
@change="getRoomList"
/>
</Flex>
</Flex>
</ModuleWrapper>
<AntFormModal
:visible.sync="scenarioModal.visible"
:title="scenarioModal.title"
:formItems="scenarioModal.formItems"
:formRules="scenarioModal.formRules"
:formData="scenarioModal.formData"
:onSubmit="handleSubmitScenario"
@success="handleSubmitScenarioSuccess"
></AntFormModal>
<AntFormModal
:visible.sync="roomModal.visible"
:title="roomModal.title"
:formItems="roomModal.formItems"
:formRules="roomModal.formRules"
:formData="roomModal.formData"
:onSubmit="handleSubmitRoom"
@success="handleSubmitRoomSuccess"
></AntFormModal>
</Grid>
</template>
<script>
import AddRoom from './AddRoom.vue'
import { getAction, postAction } from '@/api/manage'
export default {
name: 'SimulationSceneInstructor',
components: {
AddRoom,
},
data() {
return {
scenario: {
listData: [],
pagination: {
total: 0,
pageNum: 1,
pageSize: 10,
},
},
scenarioModal: {
visible: false,
title: '',
mode: '',
formItems: [
{ label: '想定名称', prop: 'name', required: true },
{ label: '想定区域(东)', prop: 'right', required: true, options: { type: 'number' } },
{ label: '想定区域(西)', prop: 'left', required: true, options: { type: 'number' } },
{ label: '想定区域(南)', prop: 'down', required: true, options: { type: 'number' } },
{ label: '想定区域(北)', prop: 'up', required: true, options: { type: 'number' } },
],
formRules: {},
formData: {},
},
room: {
listData: [],
pagination: {
total: 0,
pageNum: 1,
pageSize: 10,
},
},
roomModal: {
visible: false,
title: '',
mode: '',
formItems: [{ label: '房间名称', prop: 'roomName', required: true }],
formRules: {},
formData: {},
},
}
},
created() {
this.getScenarioList()
this.getRoomList()
},
methods: {
async getScenarioList() {
try {
const res = await this.$http({
url: '/baseData/scenario/list',
method: 'get',
params: {
pageNum: this.scenario.pagination.pageNum,
pageSize: this.scenario.pagination.pageSize,
},
})
this.scenario.listData = res.data.data
this.scenario.pagination.total = res.data.totalCount
} catch (error) {
console.log(error)
}
},
async getRoomList() {
try {
const res = await this.$http({
url: '/scenario/room/list',
method: 'get',
params: {
pageNum: this.room.pagination.pageNum,
pageSize: this.room.pagination.pageSize,
},
})
this.room.listData = res.data.data
this.room.pagination.total = res.data.totalCount
} catch (error) {
console.log(error)
}
},
handleOpenAddScenarioModal() {
this.scenarioModal.title = '新建想定'
this.scenarioModal.formData = {}
this.scenarioModal.visible = true
},
handleSubmitScenario(formData) {
const params = {
name: formData.name,
leftUpLng: formData.left,
leftUpLat: formData.up,
rightUpLng: formData.right,
rightUpLat: formData.up,
leftBottomLng: formData.left,
leftBottomLat: formData.down,
rightBottomLng: formData.right,
rightBottomLat: formData.down,
}
return postAction('/baseData/scenario/save', params)
},
handleSubmitScenarioSuccess() {
this.getScenarioList()
},
async handleDeleteScenario(scenarioItem) {
try {
await this.$confirm({ title: '温馨提示', content: '确定要删除该想定吗?' })
const res = await getAction(`/baseData/scenario/remove/${scenarioItem.id}`)
this.$message.success(res.message)
this.getScenarioList()
} catch (error) {
console.log(error)
}
},
handleOpenSetting(scenarioItem) {
window.localStorage.setItem('scenarioId', scenarioItem.id)
window.localStorage.setItem('scenarioName', scenarioItem.name)
window.localStorage.setItem('scenarioRedirectUrl', 'SimulationSceneInstructor')
this.$router.push({ name: 'SimulationSceneSceneEditing' })
},
handleOpenAddRoomModal(scenarioItem) {
this.roomModal.title = '新建房间'
this.roomModal.formData = {}
this.roomModal.formData.scenarioId = scenarioItem.id
this.roomModal.visible = true
},
handleSubmitRoom(formData) {
return postAction('/scenario/room/save', formData)
},
handleSubmitRoomSuccess() {
this.getRoomList()
},
handleEnterRoom(roomItem) {
window.localStorage.setItem('instructorRoomId', roomItem.id)
window.localStorage.setItem('instructorRoomName', roomItem.roomName)
this.$router.push({ name: 'SimulationSceneInstructorMenu' })
},
},
}
</script>
<style lang="less" scoped>
.instructor-page {
padding: 20px;
}
.scenario-item {
margin: 0 10px;
height: 10%;
border-bottom: 1px solid #ffffff22;
.scenario-name {
font-size: 20px;
font-weight: bolder;
margin-left: -10px;
}
.scenario-author {
font-size: 16px;
margin-right: 16px;
}
.scenario-create-time {
font-size: 14px;
font-style: italic;
color: #888888;
}
}
.room-item {
width: 50%;
height: 20%;
padding: 10px;
.room-card {
width: 100%;
height: 100%;
border-radius: 10px;
border: 1px solid #00deff;
.room-info {
padding: 10px;
border-right: 1px solid #00deff;
}
.room-name {
font-size: 26px;
font-weight: bolder;
}
.room-create-time {
font-size: 14px;
font-style: italic;
color: #888888;
}
.room-enter {
border-radius: 0 10px 10px 0;
cursor: pointer;
padding: 0 10px;
}
.room-enter:hover {
background-color: #10475f;
}
}
}
::v-deep {
.ant-pagination-total-text {
font-size: 16px;
color: #ffffff;
}
}
</style>

View File

@ -44,13 +44,7 @@
</Flex>
<Flex class="flex-1 scroller-y" fw="w" ac="fs">
<Flex v-for="item in model.listData" :key="item.id" class="model-item" fd="co" ai="c">
<img
class="model-image"
:src="item.imgBase64"
:draggable="true"
@dragstart="dragstart(item, $event)"
@dragend="dragend(item, $event)"
/>
<img class="model-image" :src="item.imgBase64" :draggable="true" @dragend="dragend(item, $event)" />
<span class="model-name">{{ item.name }}</span>
</Flex>
</Flex>
@ -78,7 +72,7 @@
</template>
<script>
import { getAction } from '@/api/manage'
import { getAction, postAction } from '@/api/manage'
export default {
props: {
@ -86,6 +80,7 @@ export default {
},
data() {
return {
scenarioDetail: {},
zzbzll: {
treeData: [],
selectedKeys: [],
@ -117,14 +112,40 @@ export default {
},
}
},
created() {
this.getZzbzllTreeData()
this.getZbysListData()
},
mounted() {
this.cesium = new window.MyCesium('cesium-container')
this.getScenarioDetail()
// this.getZzbzllTreeData()
this.getZbysListData()
},
methods: {
async getScenarioDetail() {
try {
const res = await getAction(`/baseData/scenario/${this.scenarioId}`)
this.scenarioDetail = res.data
// this.setCesiumClient()
} catch (error) {
console.log(error)
}
},
setCesiumClient() {
const {
leftUpLng,
rightUpLng,
leftUpLat,
rightUpLat,
leftBottomLng,
rightBottomLng,
leftBottomLat,
rightBottomLat,
} = this.scenarioDetail
this.cesium.setClientByAllCorner(
[leftUpLng, leftUpLat],
[rightUpLng, rightUpLat],
[rightBottomLng, rightBottomLat],
[leftBottomLng, leftBottomLat]
)
},
async getZzbzllTreeData() {
try {
const res = await getAction(`/scenario/power/${this.scenarioId}`)
@ -168,13 +189,26 @@ export default {
console.log(error)
}
},
dragstart(item, e) {},
dragend(item, e) {
console.log(e.x, this.$refs['cesium-container'].offsetLeft, e.x - this.$refs['cesium-container'].offsetLeft)
console.log(e.y, this.$refs['cesium-container'].offsetTop, e.y - this.$refs['cesium-container'].offsetTop)
const x = e.x - this.$refs['cesium-container'].offsetLeft
const y = e.y - this.$refs['cesium-container'].offsetTop
this.cesium.addPlot(item.imgBase64, { x, y })
const { plotId, longitude, latitude } = this.cesium.addPlot(item.imgBase64, { x, y })
this.savePlot(this.model.queryParams.force, item, { plotId, longitude, latitude })
},
async savePlot(force, item, { plotId, longitude, latitude }) {
try {
await postAction('/scenario/resource/save', {
scenarioId: this.scenarioId,
type: force,
resourceType: item.type,
resourceId: item.id,
lng: longitude,
lat: latitude,
})
// this.getZzbzllTreeData()
} catch (error) {
console.log(error)
}
},
},
}

View File

@ -107,7 +107,13 @@ export default {
visible: false,
title: '',
mode: '',
formItems: [{ label: '想定名称', prop: 'name', required: true }],
formItems: [
{ label: '想定名称', prop: 'name', required: true },
{ label: '想定区域(东)', prop: 'right', required: true, options: { type: 'number' } },
{ label: '想定区域(西)', prop: 'left', required: true, options: { type: 'number' } },
{ label: '想定区域(南)', prop: 'down', required: true, options: { type: 'number' } },
{ label: '想定区域(北)', prop: 'up', required: true, options: { type: 'number' } },
],
formRules: {},
formData: {},
},
@ -172,7 +178,18 @@ export default {
this.scenarioModal.visible = true
},
handleSubmitScenario(formData) {
return postAction('/baseData/scenario/save', formData)
const params = {
name: formData.name,
leftUpLng: formData.left,
leftUpLat: formData.up,
rightUpLng: formData.right,
rightUpLat: formData.up,
leftBottomLng: formData.left,
leftBottomLat: formData.down,
rightBottomLng: formData.right,
rightBottomLat: formData.down,
}
return postAction('/baseData/scenario/save', params)
},
handleSubmitScenarioSuccess() {
this.getScenarioList()
@ -265,4 +282,4 @@ export default {
color: #ffffff;
}
}
</style>
</style>

View File

@ -109,7 +109,7 @@ export default {
this.$router.push({ name: 'SimulationSceneCentralControl' })
},
openInstructorSystem() {
this.$router.push({ name: 'SimulationSceneCentralControl' })
this.$router.push({ name: 'SimulationSceneInstructor' })
},
openTrainerSystem() {
this.$router.push({ name: 'SimulationSceneTrainer' })