diff --git a/agv_app/setting.js b/agv_app/setting.js index 69c5d83..d8e8303 100644 --- a/agv_app/setting.js +++ b/agv_app/setting.js @@ -9,7 +9,8 @@ createApp({ missionConfig: { rows: 3, cols: 3, grid: [], machines: [] }, selectedMachine: null, sequence: [], - poseForm: { name: '', photo_type: 'front', description: '' }, + poseForm: {}, + newPoseForm: {}, // 地图 mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' }, mapMsg: '', @@ -45,6 +46,7 @@ createApp({ agvMoveInterval: null, agvCameraUrl: API + '/api/camera/refresh', agvCameraTimer: null, + armCameraTimer: null, // 机型展开 expandedModelId: null, showAddModelModal: false, @@ -61,6 +63,11 @@ createApp({ this.refreshAngles() this.loadQrConfigs() this.nav2Timer = setInterval(this.refreshNavStatus, 3000) + // 机械臂摄像头自动刷新(每2秒) + this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now() + this.armCameraTimer = setInterval(() => { + this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now() + }, 2000) }, computed: { hasQr() { @@ -90,6 +97,7 @@ createApp({ beforeUnmount() { Object.values(this.jogIntervals).forEach(i => clearInterval(i)) if (this.agvCameraTimer) clearInterval(this.agvCameraTimer) + if (this.armCameraTimer) { clearInterval(this.armCameraTimer); this.armCameraTimer = null } if (this.nav2Timer) clearInterval(this.nav2Timer) }, methods: { @@ -280,30 +288,88 @@ createApp({ await this.loadAllModels() }, // === 姿态管理(属于机型)=== - async addPose(modelId) { - const form = this.poseForm[modelId] - if (!form) return + async addPose(modelId, type, name) { + if (!name) name = '姿态' + (((this.getModel(modelId)?.poses?.length) || 0) + 1) await fetch(API + '/api/models/poses/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_id: modelId, - name: form.name || '姿态' + ((this.getModel(modelId)?.poses?.length || 0) + 1), - photo_type: form.photo_type, + name: name, + photo_type: type || 'front', arm_angles: this.currentAngles, speed: 500, - description: form.description || '' + description: '' }) }) await this.loadAllModels() - form.name = '' - form.description = '' + const key = modelId + '_' + (type || 'front') + if (this.newPoseForm[key] !== undefined) this.newPoseForm[key] = '' }, async deletePose(modelId, poseId) { if (!confirm('确定删除该姿态?')) return await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' }) await this.loadAllModels() }, + async refreshPoseAngles(modelId, poseId) { + if (!this.armConnected) { alert('机械臂未连接'); return } + try { + const res = await fetch(API + '/api/arm/get_angles') + const data = await res.json() + if (data.ok && data.angles) { + const model = this.getModel(modelId) + if (model && model.poses) { + const pose = model.poses.find(p => p.id === poseId) + if (pose) { + // Update local immediately for reactive UI + pose.arm_angles = [...data.angles] + // Persist to backend + await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ arm_angles: data.angles }) + }) + await this.loadAllModels() + } + } + } + } catch (e) { alert('刷新角度失败: ' + e.message) } + }, + async applyPoseAngles(modelId, poseId) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose || !pose.arm_angles) { alert('无效的姿态数据'); return } + try { + const res = await fetch(API + '/api/arm/set_angles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ angles: pose.arm_angles, speed: 50 }) + }) + const data = await res.json() + if (data.ok) { alert('姿态已应用到机械臂') } + else { alert('应用失败: ' + (data.error || '未知错误')) } + } catch (e) { alert('应用姿态失败: ' + e.message) } + }, + adjustPoseAngle(modelId, poseId, jointIdx, delta) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose) return + if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0] + if (!pose.arm_angles[jointIdx]) pose.arm_angles[jointIdx] = 0 + pose.arm_angles[jointIdx] = Math.round((pose.arm_angles[jointIdx] + delta) * 10) / 10 + this.setAngle(jointIdx, pose.arm_angles[jointIdx]) + }, + async updatePoseAngleAndMove(modelId, poseId, jointIdx, value) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose) return + if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0] + pose.arm_angles[jointIdx] = parseFloat(value) || 0 + await this.setAngle(jointIdx, pose.arm_angles[jointIdx]) + }, getModel(id) { return this.models.find(m => m.id === id) }, diff --git a/agv_app/static/js/setting.js b/agv_app/static/js/setting.js index 69c5d83..d8e8303 100644 --- a/agv_app/static/js/setting.js +++ b/agv_app/static/js/setting.js @@ -9,7 +9,8 @@ createApp({ missionConfig: { rows: 3, cols: 3, grid: [], machines: [] }, selectedMachine: null, sequence: [], - poseForm: { name: '', photo_type: 'front', description: '' }, + poseForm: {}, + newPoseForm: {}, // 地图 mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' }, mapMsg: '', @@ -45,6 +46,7 @@ createApp({ agvMoveInterval: null, agvCameraUrl: API + '/api/camera/refresh', agvCameraTimer: null, + armCameraTimer: null, // 机型展开 expandedModelId: null, showAddModelModal: false, @@ -61,6 +63,11 @@ createApp({ this.refreshAngles() this.loadQrConfigs() this.nav2Timer = setInterval(this.refreshNavStatus, 3000) + // 机械臂摄像头自动刷新(每2秒) + this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now() + this.armCameraTimer = setInterval(() => { + this.armCameraUrl = API + '/api/camera/arm_refresh?t=' + Date.now() + }, 2000) }, computed: { hasQr() { @@ -90,6 +97,7 @@ createApp({ beforeUnmount() { Object.values(this.jogIntervals).forEach(i => clearInterval(i)) if (this.agvCameraTimer) clearInterval(this.agvCameraTimer) + if (this.armCameraTimer) { clearInterval(this.armCameraTimer); this.armCameraTimer = null } if (this.nav2Timer) clearInterval(this.nav2Timer) }, methods: { @@ -280,30 +288,88 @@ createApp({ await this.loadAllModels() }, // === 姿态管理(属于机型)=== - async addPose(modelId) { - const form = this.poseForm[modelId] - if (!form) return + async addPose(modelId, type, name) { + if (!name) name = '姿态' + (((this.getModel(modelId)?.poses?.length) || 0) + 1) await fetch(API + '/api/models/poses/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_id: modelId, - name: form.name || '姿态' + ((this.getModel(modelId)?.poses?.length || 0) + 1), - photo_type: form.photo_type, + name: name, + photo_type: type || 'front', arm_angles: this.currentAngles, speed: 500, - description: form.description || '' + description: '' }) }) await this.loadAllModels() - form.name = '' - form.description = '' + const key = modelId + '_' + (type || 'front') + if (this.newPoseForm[key] !== undefined) this.newPoseForm[key] = '' }, async deletePose(modelId, poseId) { if (!confirm('确定删除该姿态?')) return await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { method: 'DELETE' }) await this.loadAllModels() }, + async refreshPoseAngles(modelId, poseId) { + if (!this.armConnected) { alert('机械臂未连接'); return } + try { + const res = await fetch(API + '/api/arm/get_angles') + const data = await res.json() + if (data.ok && data.angles) { + const model = this.getModel(modelId) + if (model && model.poses) { + const pose = model.poses.find(p => p.id === poseId) + if (pose) { + // Update local immediately for reactive UI + pose.arm_angles = [...data.angles] + // Persist to backend + await fetch(API + '/api/models/' + modelId + '/poses/' + poseId, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ arm_angles: data.angles }) + }) + await this.loadAllModels() + } + } + } + } catch (e) { alert('刷新角度失败: ' + e.message) } + }, + async applyPoseAngles(modelId, poseId) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose || !pose.arm_angles) { alert('无效的姿态数据'); return } + try { + const res = await fetch(API + '/api/arm/set_angles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ angles: pose.arm_angles, speed: 50 }) + }) + const data = await res.json() + if (data.ok) { alert('姿态已应用到机械臂') } + else { alert('应用失败: ' + (data.error || '未知错误')) } + } catch (e) { alert('应用姿态失败: ' + e.message) } + }, + adjustPoseAngle(modelId, poseId, jointIdx, delta) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose) return + if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0] + if (!pose.arm_angles[jointIdx]) pose.arm_angles[jointIdx] = 0 + pose.arm_angles[jointIdx] = Math.round((pose.arm_angles[jointIdx] + delta) * 10) / 10 + this.setAngle(jointIdx, pose.arm_angles[jointIdx]) + }, + async updatePoseAngleAndMove(modelId, poseId, jointIdx, value) { + const model = this.getModel(modelId) + if (!model || !model.poses) return + const pose = model.poses.find(p => p.id === poseId) + if (!pose) return + if (!pose.arm_angles) pose.arm_angles = [0,0,0,0,0,0] + pose.arm_angles[jointIdx] = parseFloat(value) || 0 + await this.setAngle(jointIdx, pose.arm_angles[jointIdx]) + }, getModel(id) { return this.models.find(m => m.id === id) }, diff --git a/agv_app/templates/setting.html b/agv_app/templates/setting.html index 41db5f6..e828878 100644 --- a/agv_app/templates/setting.html +++ b/agv_app/templates/setting.html @@ -4,7 +4,7 @@ 设置 - AGV 拍摄系统 - +
@@ -135,7 +135,7 @@

🟢 正面姿态

-
+
{% raw %}{{ pose.name || '正面姿态' }}{% endraw %} @@ -182,7 +182,7 @@

🔴 背面姿态

-
+
{% raw %}{{ pose.name || '背面姿态' }}{% endraw %} @@ -676,7 +676,7 @@
- - + +