feat: 巡检机器人
This commit is contained in:
parent
8d8bcb4e26
commit
d2130ffaf5
5
components.d.ts
vendored
5
components.d.ts
vendored
@ -12,7 +12,6 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
@ -21,10 +20,10 @@ declare module 'vue' {
|
|||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||||
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
IPlayer: typeof import('./src/components/IPlayer/index.vue')['default']
|
IPlayer: typeof import('./src/components/IPlayer/index.vue')['default']
|
||||||
LandscapeGuard: typeof import('./src/components/LandscapeGuard.vue')['default']
|
LandscapeGuard: typeof import('./src/components/LandscapeGuard.vue')['default']
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<!-- 主体内容区 -->
|
<!-- 主体内容区 -->
|
||||||
<div class="main-wrap">
|
<div class="main-wrap">
|
||||||
<!-- 左侧数据面板 -->
|
<!-- 左侧数据面板 -->
|
||||||
<div class="left-panel">
|
<div class="left-panel" :class="{ 'left-panel--dual-arm': isExplanationRobot }">
|
||||||
<!-- TCP 笛卡尔坐标 -->
|
<!-- TCP 笛卡尔坐标 -->
|
||||||
<div class="panel-card">
|
<div class="panel-card">
|
||||||
<div class="row dual-row">
|
<div class="row dual-row">
|
||||||
@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="joint-list">
|
<div v-if="!isExplanationRobot" class="joint-list">
|
||||||
<div class="title">关节控制</div>
|
<div class="title">关节控制</div>
|
||||||
<div v-for="joint in jointControls" :key="joint.name" class="joint-item">
|
<div v-for="joint in jointControls" :key="joint.name" class="joint-item">
|
||||||
<label :for="joint.name">{{ joint.name }}</label>
|
<label :for="joint.name">{{ joint.name }}</label>
|
||||||
@ -65,6 +65,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="joint-list joint-list--dual-arm">
|
||||||
|
<div class="title">关节控制</div>
|
||||||
|
<div class="arm-columns">
|
||||||
|
<div v-for="arm in armJointGroups" :key="arm.key" class="arm-column">
|
||||||
|
<div class="arm-title">{{ arm.title }}</div>
|
||||||
|
<div v-for="joint in arm.joints" :key="joint.name" class="joint-item">
|
||||||
|
<label :for="joint.name">{{ joint.name }}</label>
|
||||||
|
<el-input v-model="joint.value" readonly>
|
||||||
|
<template #prepend>
|
||||||
|
<el-button class="press-control"
|
||||||
|
@pointerdown="startPointerControl($event, () => updateJoint(joint.name, '-'))"
|
||||||
|
@pointerup="endPointerControl" @pointercancel="endPointerControl">
|
||||||
|
-
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<el-button class="press-control"
|
||||||
|
@pointerdown="startPointerControl($event, () => updateJoint(joint.name, '+'))"
|
||||||
|
@pointerup="endPointerControl" @pointercancel="endPointerControl">
|
||||||
|
+
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="speed-container">
|
<div class="speed-container">
|
||||||
<div class="label">速度:</div>
|
<div class="label">速度:</div>
|
||||||
<div class="speed-slider" @mouseenter="hoveredSlider = 'joint'" @mouseleave="hoveredSlider = null">
|
<div class="speed-slider" @mouseenter="hoveredSlider = 'joint'" @mouseleave="hoveredSlider = null">
|
||||||
@ -78,7 +106,7 @@
|
|||||||
|
|
||||||
<!-- 中间3D机械臂空白区域 -->
|
<!-- 中间3D机械臂空白区域 -->
|
||||||
<div class="center-view">
|
<div class="center-view">
|
||||||
<UrdfView ref="urdfViewerRef" />
|
<UrdfView ref="urdfViewerRef" :modelPath="modelPath"/>
|
||||||
<!-- 底部功能按钮 -->
|
<!-- 底部功能按钮 -->
|
||||||
<!-- <div class="center-bottom-btns">
|
<!-- <div class="center-bottom-btns">
|
||||||
<el-button type="success" size="large">零力示教</el-button>
|
<el-button type="success" size="large">零力示教</el-button>
|
||||||
@ -91,6 +119,13 @@
|
|||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="controller">
|
<div class="controller">
|
||||||
<div class="title">末端控制</div>
|
<div class="title">末端控制</div>
|
||||||
|
<div v-if="isExplanationRobot" class="arm-selector">
|
||||||
|
<div class="arm-selector__label">控制机械臂</div>
|
||||||
|
<el-radio-group v-model="selectedEndArm" class="arm-selector__options">
|
||||||
|
<el-radio-button value="left">左臂</el-radio-button>
|
||||||
|
<el-radio-button value="right">右臂</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
<div class="axis-group">
|
<div class="axis-group">
|
||||||
<!-- X Axis -->
|
<!-- X Axis -->
|
||||||
<div class="axis-row x">
|
<div class="axis-row x">
|
||||||
@ -171,6 +206,13 @@
|
|||||||
|
|
||||||
<div class="controller">
|
<div class="controller">
|
||||||
<div class="title">姿态控制</div>
|
<div class="title">姿态控制</div>
|
||||||
|
<div v-if="isExplanationRobot" class="arm-selector">
|
||||||
|
<div class="arm-selector__label">控制机械臂</div>
|
||||||
|
<el-radio-group v-model="selectedPostureArm" class="arm-selector__options">
|
||||||
|
<el-radio-button value="left">左臂</el-radio-button>
|
||||||
|
<el-radio-button value="right">右臂</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
<div class="axis-group">
|
<div class="axis-group">
|
||||||
<!-- X Axis -->
|
<!-- X Axis -->
|
||||||
<div class="axis-row x">
|
<div class="axis-row x">
|
||||||
@ -254,7 +296,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||||
import UrdfView from './UrdfView.vue'
|
import UrdfView from './UrdfView.vue'
|
||||||
import { getPoseApi, getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, speedLApi } from '@/api/arm'
|
import { getPoseApi, getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, speedLApi } from '@/api/arm'
|
||||||
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
||||||
@ -264,7 +306,31 @@ const robotStore = useRobotStore()
|
|||||||
const deviceId = robotStore.almDeviceId
|
const deviceId = robotStore.almDeviceId
|
||||||
|
|
||||||
const urdfViewerRef = ref()
|
const urdfViewerRef = ref()
|
||||||
const jointControls = ref()
|
|
||||||
|
const modelPath = computed(() => {
|
||||||
|
if (isExplanationRobot.value) {
|
||||||
|
return 'upper_body/robot.urdf'
|
||||||
|
} else {
|
||||||
|
return '/inspection/elfin10/elfin10.urdf.xacro'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const jointControls = ref([])
|
||||||
|
const isExplanationRobot = computed(() => robotStore.robotType === 'explanation')
|
||||||
|
const selectedEndArm = ref('left')
|
||||||
|
const selectedPostureArm = ref('left')
|
||||||
|
const armJointGroups = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'left',
|
||||||
|
title: '左臂',
|
||||||
|
joints: jointControls.value.filter(joint => /^left_arm_/i.test(joint.name))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'right',
|
||||||
|
title: '右臂',
|
||||||
|
joints: jointControls.value.filter(joint => /^right_arm_/i.test(joint.name))
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// TCP坐标数据
|
// TCP坐标数据
|
||||||
const tcp = ref({
|
const tcp = ref({
|
||||||
@ -512,11 +578,16 @@ onUnmounted(() => {
|
|||||||
// 左侧面板
|
// 左侧面板
|
||||||
.left-panel {
|
.left-panel {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: $color-background-panel;
|
background: $color-background-panel;
|
||||||
border-right: 1px solid $color-accent-border;
|
border-right: 1px solid $color-accent-border;
|
||||||
|
|
||||||
|
&--dual-arm {
|
||||||
|
width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-card {
|
.panel-card {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
@ -525,11 +596,27 @@ onUnmounted(() => {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.coord-item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.key {
|
||||||
|
width: 26px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
width: 170px;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: $color-background-card;
|
background: $color-background-card;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,6 +633,19 @@ onUnmounted(() => {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arm-columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arm-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.joint-item {
|
.joint-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -607,6 +707,17 @@ onUnmounted(() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--dual-arm {
|
||||||
|
.joint-item {
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: 82px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -625,6 +736,36 @@ onUnmounted(() => {
|
|||||||
background: $color-background-panel;
|
background: $color-background-panel;
|
||||||
border-left: 1px solid $color-accent-border;
|
border-left: 1px solid $color-accent-border;
|
||||||
|
|
||||||
|
.arm-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid $color-accent-border;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $color-background-base;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__options {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
:deep(.el-radio-button) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-radio-button__inner) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.controller {
|
.controller {
|
||||||
margin-bottom: 48px;
|
margin-bottom: 48px;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user