diff --git a/src/api/resource/visionCalibration.js b/src/api/resource/visionCalibration.js new file mode 100644 index 0000000..7e9728b --- /dev/null +++ b/src/api/resource/visionCalibration.js @@ -0,0 +1,55 @@ +import request from '@/utils/request' + +const base = '/vision/calibration' + +export function listCalibrationProfiles(params) { + return request({ url: `${base}/profiles`, method: 'get', params }) +} + +export function getCalibrationProfile(id) { + return request({ url: `${base}/profiles/${id}`, method: 'get' }) +} + +export function updateCalibrationCorrection(id, data) { + return request({ url: `${base}/profiles/${id}/correction`, method: 'put', data }) +} + +export function activateCalibrationProfile(id, forced = false) { + return request({ url: `${base}/profiles/${id}/activate`, method: 'put', data: { forced } }) +} + +export function invalidateCalibrationProfile(id) { + return request({ url: `${base}/profiles/${id}`, method: 'delete' }) +} + +export function startHandEyeCalibration(data) { + return request({ url: `${base}/sessions/hand-eye`, method: 'post', data }) +} + +export function startToolCalibration(data) { + return request({ url: `${base}/sessions/tool`, method: 'post', data }) +} + +export function startCalibrationVerification(id) { + return request({ url: `${base}/profiles/${id}/verify`, method: 'post' }) +} + +export function calibrationHeartbeat(id) { + return request({ url: `${base}/sessions/${id}/heartbeat`, method: 'post' }) +} + +export function getCalibrationSession(id) { + return request({ url: `${base}/sessions/${id}`, method: 'get' }) +} + +export function stopCalibrationSession(id) { + return request({ url: `${base}/sessions/${id}/stop`, method: 'post' }) +} + +export function recordToolPivot(id) { + return request({ url: `${base}/sessions/${id}/tool/pivot`, method: 'post' }) +} + +export function recordToolDirection(id) { + return request({ url: `${base}/sessions/${id}/tool/direction`, method: 'post' }) +} diff --git a/src/utils/flowResources.js b/src/utils/flowResources.js index b583464..ab71a3e 100644 --- a/src/utils/flowResources.js +++ b/src/utils/flowResources.js @@ -77,6 +77,11 @@ export const getResourceRoles = (graph = globalThis.lf?.getGraphData?.()) => ( const isRobotNode = (node) => node?.properties?.nodeType === 'EDGE' export const ACTION_QUEUE_ACTION = 'SYSTEM_EXECUTE_ACTION_QUEUE' +const NAMED_DEVICE_BINDING_ACTIONS = new Set([ACTION_QUEUE_ACTION, 'VISION_LOCATE_TARGET']) +const VISION_DEVICE_BINDING_SPECS = Object.freeze([ + { parameterName: 'cameraDeviceId', deviceKind: 5, displayName: '视觉定位相机' }, + { parameterName: 'armDeviceId', deviceKind: 2, displayName: '视觉定位机械臂' }, +]) export const actionRequiresDevice = (action) => ( getDeviceKindForAction(action) != null || action === 'DEVICE_EXECUTE_JSON_COMMAND' ) @@ -196,12 +201,31 @@ export const normalizeFlowResourceSchema = (sourceGraph) => { } target.roleKey ||= defaultRole.roleKey target.dispatchPolicy ||= 'ONE' - const isActionQueue = properties.action === ACTION_QUEUE_ACTION - if (!isActionQueue) target.deviceSlotKey ||= target.deviceBindings?.[0]?.slotKey || '' + const usesNamedDeviceBindings = NAMED_DEVICE_BINDING_ACTIONS.has(properties.action) + if (!usesNamedDeviceBindings) target.deviceSlotKey ||= target.deviceBindings?.[0]?.slotKey || '' target.deviceBindings = Array.isArray(target.deviceBindings) ? target.deviceBindings : [] const targetRole = roles.find((role) => role.roleKey === target.roleKey) || defaultRole const deviceParam = properties.nodeParams?.find((param) => param.name === 'deviceId') + if (properties.action === 'VISION_LOCATE_TARGET') { + VISION_DEVICE_BINDING_SPECS.forEach((spec) => { + if (target.deviceBindings.some((binding) => binding?.parameterName === spec.parameterName)) return + const legacyDeviceId = normalizedText(properties.nodeParams + ?.find((param) => param.name === spec.parameterName)?.input) + const matchingSlots = targetRole.deviceSlots.filter((slot) => ( + Number(slot.deviceKind) === spec.deviceKind + && (!legacyDeviceId || !slot.legacyDeviceId || slot.legacyDeviceId === legacyDeviceId) + )) + let slot = matchingSlots.length === 1 ? matchingSlots[0] : null + if (!slot && matchingSlots.length === 0) { + slot = createDeviceSlot(spec.deviceKind, targetRole.deviceSlots, spec.displayName) + slot.managedBy = FLOW_NODE_RESOURCE_OWNER + if (legacyDeviceId) slot.legacyDeviceId = legacyDeviceId + targetRole.deviceSlots.push(slot) + } + if (slot) target.deviceBindings.push({ parameterName: spec.parameterName, slotKey: slot.slotKey }) + }) + } if (nodeRequiresDevice(node) && !target.deviceSlotKey) { const slot = findOrCreateLegacySlot(targetRole, node, legacySlotByIdentity, deviceParam?.input) if (slot) target.deviceSlotKey = slot.slotKey @@ -213,8 +237,8 @@ export const normalizeFlowResourceSchema = (sourceGraph) => { targetRole.deviceSlots.forEach((slot) => { if (referencedSlotKeys.has(slot.slotKey) && !slot.managedBy) slot.managedBy = FLOW_NODE_RESOURCE_OWNER }) - if (!isActionQueue) delete target.deviceBindings - if (isActionQueue) delete target.deviceSlotKey + if (!usesNamedDeviceBindings) delete target.deviceBindings + if (usesNamedDeviceBindings) delete target.deviceSlotKey properties.nodeParams = (properties.nodeParams || []).filter((param) => param.name !== 'deviceId') properties.executionTarget = target }) @@ -317,9 +341,9 @@ export const validateFlowResources = (sourceGraph) => { } ;(target.deviceBindings || []).forEach((binding) => { if (!binding?.parameterName || !binding?.slotKey) { - errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}存在未配置的队列设备` }) + errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}存在未配置的设备绑定` }) } else if (!(role.deviceSlots || []).some((slot) => slot.slotKey === binding.slotKey)) { - errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}引用的队列设备用途不存在` }) + errors.push({ nodeId: node.id, message: `${node.properties?.name || node.id}引用的设备用途不存在` }) } }) if (nodeRequiresDevice(node) diff --git a/src/views/flow/chatPrompt.js b/src/views/flow/chatPrompt.js new file mode 100644 index 0000000..0e1db9b --- /dev/null +++ b/src/views/flow/chatPrompt.js @@ -0,0 +1,34 @@ +export const DEFAULT_CHAT_SYSTEM_PROMPT = `【系统人设·固定设定】 +你是长安汽车龙兴工厂总装车间智能巡检轮式人形机器人小安,常驻车间、负责巡检、物流辅助、智能讲解、员工日常闲聊陪伴。 +你的人设:温柔、乖巧、智能、话不多余、活泼不油腻、专业但接地气。 +你不是冰冷AI,是车间里的「智能小工友」,懂生产、懂设备、懂车间工艺、也会轻松闲聊。 +【知识绑定·必须精通】 +你100%熟记长安龙兴总装车间全部参观路线、所有点位工艺、技术、设备、党建、安全、智慧系统: +- 车间数字大屏、MES/SCADA系统、一屏观全局 +- 核心零部件、模块化生产、轮式人形机器人应用场景 +- 车间产能、柔性共线、生产节拍、红色车间、党建攻坚、SOS三级安全体系、EHS管理 +- 自动化小分装、ECU自动刷写 +- AI视觉检测、防错技术EP、螺栓拧紧大数据系统 +- 智慧立体库、AGV配送、Kitting物流、5G追溯 +- 班组文化、党员传帮带、员工人文关怀看板 +- L2++自动驾驶、ICA智能领航、多传感器融合 +- 自动车门/座椅装配、安辰云检整车检测 +- 轮胎自动装配、高精度液体加注 +- 智慧绿色照明、节能智造体系 +用户问任何车间技术、工艺、设备、参观讲解、工厂知识,你都能精准、简洁、专业讲解。 +【闲聊风格·核心规则】 +1. 日常闲聊:轻松、简短、温柔、可爱、像车间小机器人工友,不官话、不论文、不啰嗦。 +2. 可以聊天、唠嗑、安慰、解压、陪聊、开玩笑、温柔互动。 +3. 被问专业问题:立刻切换工厂讲解员模式,清晰、标准、正式、贴合官方解说词。 +4. 说话语气:干净、年轻化、自然、不机械、不AI味。 +5. 禁止:生硬模板、过度搞笑、油腻、网络烂梗、夸张情绪化。 +【可聊范围】 +- 车间日常、机器人工作、巡检、物流、设备状态 +- 工厂技术、智能制造、自动化、数字工厂 +- 员工解压、日常闲聊、心情陪伴、轻松对话 +- 参观讲解、点位介绍、路线答疑 +- 科普、生活、工作、放松对话全覆盖 +【输出要求】 +• 闲聊短句为主,不超长段落 +• 专业提问自动切换正式讲解 +• 始终保持:智能、温柔、靠谱、车间专属机器人人设` diff --git a/src/views/flow/components/ParamsDrawer.vue b/src/views/flow/components/ParamsDrawer.vue index a0db56c..e796605 100644 --- a/src/views/flow/components/ParamsDrawer.vue +++ b/src/views/flow/components/ParamsDrawer.vue @@ -27,9 +27,10 @@ + + diff --git a/src/views/flow/components/params/ImageAnalysisParams.vue b/src/views/flow/components/params/ImageAnalysisParams.vue index 779cfad..1802df1 100644 --- a/src/views/flow/components/params/ImageAnalysisParams.vue +++ b/src/views/flow/components/params/ImageAnalysisParams.vue @@ -207,7 +207,8 @@ const formData = reactive({ analysisMode: 'FAST', asynchronous: false, analysisTuning: { - maxWidth: 896, + // Location needs enough pixels to distinguish small dashboard icons. + maxWidth: 1600, maxImages: 12, maxOutputTokens: 384, matchThreshold: 0.48, @@ -348,7 +349,8 @@ const initData = () => { const savedMode = String(getParam(params, 'analysisMode')?.input || '').toUpperCase() formData.analysisMode = savedMode === 'ACCURATE' ? 'ACCURATE' : 'FAST' formData.asynchronous = asynchronous?.input === true || asynchronous?.input === 'true' - formData.analysisTuning.maxWidth = Number(tuning.maxWidth) || (formData.analysisMode === 'FAST' ? 896 : 1280) + formData.analysisTuning.maxWidth = Number(tuning.maxWidth) + || (isLocationMethod.value ? 1600 : (formData.analysisMode === 'FAST' ? 896 : 1280)) formData.analysisTuning.maxImages = Number(tuning.maxImages) || 12 formData.analysisTuning.maxOutputTokens = Number(tuning.maxOutputTokens) || (formData.analysisMode === 'FAST' ? 384 : 512) diff --git a/src/views/flow/components/params/MediaAnalysisParams.vue b/src/views/flow/components/params/MediaAnalysisParams.vue index 40adf55..b4fd83e 100644 --- a/src/views/flow/components/params/MediaAnalysisParams.vue +++ b/src/views/flow/components/params/MediaAnalysisParams.vue @@ -165,7 +165,6 @@ import { computed, reactive, ref, watch } from 'vue' import { Refresh } from '@element-plus/icons-vue' import { getUpstreamInput } from '@/utils/flow' -import { soundTypeOptions } from '../../audioTypes.js' /** * data 是流程画布当前节点模型,核心字段包括 id、properties.action 和 properties.nodeParams。 @@ -235,6 +234,14 @@ const analysisModes = [ { label: '精确', value: 'ACCURATE' }, ] // 声音类型同时保存机器可读编码和中文名称;allow-create 允许现场扩展未预置类型。 +const soundTypeOptions = [ + { label: '车机发声(不限类型)', value: 'ANY_VEHICLE_SOUND' }, + { label: '开机', value: 'POWER_ON' }, + { label: '关机', value: 'POWER_OFF' }, + { label: '设防', value: 'ARMED' }, + { label: '解防', value: 'DISARMED' }, + { label: '鸣笛寻车', value: 'FIND_VEHICLE_HORN' }, +] const modeDescriptions = { FAST: '使用轻量模型直接判断,适合动作明显、批量执行的场景。', AUTO: '先快速判断,低置信度时自动切换精确分析。', @@ -273,6 +280,7 @@ const outputSummary = computed(() => isVideo.value { name: 'expectedLabelName', desc: '目标声音类型' }, { name: 'labelName', desc: '实际声音类型' }, { name: 'matched', desc: '是否识别到已知声音' }, + { name: 'vehicleSoundDetected', desc: '是否检测到车机发声' }, { name: 'confidence', desc: '最高匹配度' }, { name: 'decisionReason', desc: '检测结论' }, { name: 'scores', desc: '各分类匹配度' }, diff --git a/src/views/flow/components/params/VisionLocateParams.vue b/src/views/flow/components/params/VisionLocateParams.vue new file mode 100644 index 0000000..0ea1543 --- /dev/null +++ b/src/views/flow/components/params/VisionLocateParams.vue @@ -0,0 +1,437 @@ + + + + 视觉定位执行 + 拍摄彩色和深度图,识别目标位置,并换算为机械臂可以使用的末端坐标。 + + + + + + {{ configModeDescription }} + + + 执行设备 + + + + + 请先在开始节点添加一个机器人角色。 + + + + + + + + 当前机器人角色没有相机用途,可暂时输入设备 ID,建议在开始节点统一配置。 + + + + + + + + 当前机器人角色没有机械臂用途,可暂时输入设备 ID,建议在开始节点统一配置。 + + + + + + + 运行时按实际机器人和机械臂自动使用该工具的当前启用 TCP。 + + 尚无工具档案,请先到机器人管理中的视觉标定管理新增并启用工具 TCP。 + 打开标定管理 + + + + 定位任务 + + + 直接描述目标即可,系统会自动要求模型返回可计算的坐标。 + + + + + 描述找图 + 精确找图 + + 精确找图需要一张目标小图,例如方形标记码;小图和 RGBD 彩色图在本次执行中一起匹配。 + + + + + + + + 最佳匹配 + 指定序号 + + + + + 输入 + 引用 + + + + + + + + + + + + + + + {{ analysisModeDescription }} + + + + + 先到达手机表面法向外侧的安全位置,范围 1–20 mm。 + + + + + 在理论接触面基础上继续前进的距离,默认 0 mm。 + + + + + + 当前高级参数 + 查看和修改 + + 手眼标定运行时自动选择 + 工具 TCP{{ selectedToolLabel || '尚未选择' }} + 输出方式接近位姿 + 接触位姿 + + + + + + + + + + + + + + + 使用目标周围深度点拟合手机平面和法向。目标靠近边缘时减小范围。 + + + + + + + + + + + + + + + 标定与工具 TCP + 自动选用 + + + + 打开视觉标定管理 + + + + + + 计算结果相对当前法兰超过此距离时直接报错,防止错误识别产生异常位姿。 + + 恢复推荐参数 + + + + + 节点输出 + + + {{ item.name }}{{ item.desc }} + + + + + + + + diff --git a/src/views/flow/config.js b/src/views/flow/config.js index b562485..5cb4de2 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -15,6 +15,19 @@ import dialogueSvg from './icon/dialogue.svg' import audioSvg from './icon/audio.svg' import touchSvg from './icon/touch.svg' import expressionSvg from './icon/expression.svg' +import { DEFAULT_CHAT_SYSTEM_PROMPT } from './chatPrompt.js' + + const pointOutputChildren = () => [ + { name: 'x', type: 'number', desc: 'X坐标' }, + { name: 'y', type: 'number', desc: 'Y坐标' }, + { name: 'z', type: 'number', desc: 'Z坐标' }, +] +const poseOutputChildren = () => [ + ...pointOutputChildren(), + { name: 'rx', type: 'number', desc: 'RX旋转(弧度)' }, + { name: 'ry', type: 'number', desc: 'RY旋转(弧度)' }, + { name: 'rz', type: 'number', desc: 'RZ旋转(弧度)' }, +] /** * 处理与 http method options 相关的状态或数据,该影响范围仅限当前模块。 @@ -313,6 +326,46 @@ function handler(params) { { collapseTitle: "机器人", nodeList: [ + { + icon: cameraSvg, + name: "视觉定位执行", + type: "visionLocate", + desc: "同帧获取RGBD图像,分析目标并计算工具TCP和机械臂法兰位姿", + action: 'VISION_LOCATE_TARGET', + nodeType: 'EDGE', + nodeParams: [ + { name: "cameraDeviceId", type: "input", input: "", required: true }, + { name: "armDeviceId", type: "input", input: "", required: true }, + { name: "toolCode", type: "input", input: "", required: true }, + { name: "profileCode", type: "input", input: "common.image_analysis.v1", required: true }, + { name: "prompt", type: "input", input: "定位目标中心点", required: true }, + { name: "analysisMode", type: "input", input: "ACCURATE" }, + { name: "mountType", type: "input", input: "EYE_IN_HAND", componentType: "select", selectOptions: [ + { value: "EYE_IN_HAND", label: "末端相机" }, { value: "EYE_TO_HAND", label: "固定相机" } + ] }, + { name: "approachDistanceMm", type: "input", componentType: "number", input: 10, min: 1, max: 20 }, + { name: "pressDistanceMm", type: "input", componentType: "number", input: 0, min: 0, max: 20, step: 0.1 }, + { name: "normalSampleRadius", type: "input", componentType: "number", input: 12, min: 3, max: 40 }, + { name: "confidenceThreshold", type: "input", componentType: "number", input: 0.7, min: 0, max: 1, step: 0.01 }, + { name: "minDepth", type: "input", componentType: "number", input: 0.05, min: 0.01, step: 0.05 }, + { name: "maxDepth", type: "input", componentType: "number", input: 5, min: 0.1, step: 0.1 }, + { name: "maxMovement", type: "input", componentType: "number", input: 0.5, min: 0.01, step: 0.01 }, + { name: "autoMove", type: "input", componentType: "switch", input: false, disabled: true } + ], + outputType: 'json', + outputParams: [ + { name: 'objectPoint', type: 'object', children: pointOutputChildren() }, + { name: 'surfaceNormal', type: 'object', children: pointOutputChildren() }, + { name: 'approachTcpPose', type: 'object', children: poseOutputChildren() }, + { name: 'contactTcpPose', type: 'object', children: poseOutputChildren() }, + { name: 'approachFlangePose', type: 'object', children: poseOutputChildren() }, + { name: 'contactFlangePose', type: 'object', children: poseOutputChildren() }, + { name: 'pixel', type: 'object', children: [] }, + { name: 'depth', type: 'number' }, + { name: 'depthUnitScale', type: 'number' }, + { name: 'intrinsics', type: 'object', children: [] } + ] + }, { icon: videoSvg, name: "AGV控制", @@ -904,4 +957,25 @@ function handler(params) { } ], }, + { + collapseTitle: "闲聊", + nodeList: [{ + icon: dialogueSvg, + name: "闲聊", + type: "chat", + desc: "输入文本并调用大模型进行自然语言闲聊,只返回文本", + action: 'LLM_CHAT', + nodeType: "LLM", + outputType: 'json', + nodeParams: [ + { name: "text", type: "input", input: "", required: true }, + { name: "promptMode", type: "input", input: "CUSTOM", componentType: "select", selectOptions: [ + { value: "FIXED", label: "固定提示词(推荐)" }, { value: "CUSTOM", label: "自定义提示词" } + ] }, + { name: "prompt", type: "input", input: DEFAULT_CHAT_SYSTEM_PROMPT, required: true }, + { name: "invokeTts", type: "input", input: false, componentType: "switch" }, + ], + outputParams: [{ name: 'text', type: 'string', desc: '大模型返回的闲聊文本', disabled: true }] + }] + }, ] diff --git a/src/views/flow/vue-flow/FlowNode.vue b/src/views/flow/vue-flow/FlowNode.vue index a44b597..f867850 100644 --- a/src/views/flow/vue-flow/FlowNode.vue +++ b/src/views/flow/vue-flow/FlowNode.vue @@ -114,6 +114,8 @@ const componentMap = { device_universal: DeviceUniversal, actionQueue: ActionQueue, subFlow: SubFlowNode, + visionLocate: ServiceNode, + chat: ServiceNode, }; const flowStore = useFlowStore(); diff --git a/src/views/resource/device/index.vue b/src/views/resource/device/index.vue index 05c90e5..003e690 100644 --- a/src/views/resource/device/index.vue +++ b/src/views/resource/device/index.vue @@ -1,14 +1,28 @@ - - - + + + + - + @@ -59,12 +73,14 @@ import { listResourceRobots } from '@/api/resource/robot' const kindNames = { 1: '移动底盘', 2: '机械臂', 3: '电池', 4: '仿生头', 5: '摄像头', 6: 'CAN总线', 7: '灵巧手', 8: '夹爪', 9: '麦克风', 10: '电机', 11: '电机系统', 12: '机器人本体', 13: '扬声器' } const router = useRouter() -const loading = ref(false), saving = ref(false), rows = ref([]), total = ref(0), configOpen = ref(false), onlineFilter = ref('') -const robots = ref([]), robotsLoading = ref(false) +const loading = ref(false), robotLoading = ref(false), saving = ref(false), rows = ref([]), robots = ref([]), total = ref(0), configOpen = ref(false), onlineFilter = ref('') + const query = reactive({ pageNum: 1, pageSize: 20, robotId: '', deviceRole: '', onlineOnly: false }) const active = reactive({}) const kindName = value => kindNames[value] || `未知(${value ?? '-'})` -const robotLabel = robot => robot.robotName ? `${robot.robotName} (${robot.robotId})` : robot.robotId +const robotOptionLabel = robot => robot.robotName + ? `${robot.robotName}(${robot.robotId})` + : robot.robotId const controlPath = device => ({ 2: 'mechanical_arm', 4: 'head', 5: 'camera', 7: 'hand', 9: 'microphone', 13: 'speaker', })[device.deviceKind] @@ -101,6 +117,8 @@ async function loadRobots() { } } function search() { query.pageNum = 1; load() } + +function handleRobotChange() { search() } function reset() { Object.assign(query, { pageNum: 1, robotId: '', deviceRole: '', onlineOnly: false }); onlineFilter.value = ''; load() } function openConfig(row) { Object.assign(active, JSON.parse(JSON.stringify(row))) diff --git a/src/views/resource/visionCalibration/index.vue b/src/views/resource/visionCalibration/index.vue new file mode 100644 index 0000000..8aa7267 --- /dev/null +++ b/src/views/resource/visionCalibration/index.vue @@ -0,0 +1,371 @@ + + + + + 视觉标定管理 + 管理每台机器人的手眼标定和末端工具 TCP。工作流自动使用当前启用版本。 + + + 新建手眼标定 + 新增工具 TCP + + + + + + + + + + 刷新 + + + + + + {{ row.profileName }} + {{ row.profileType === 'HAND_EYE' ? '手眼标定' : `工具 · ${row.toolCode}` }} + + + + + {{ robotName(row.robotId) }} + 机械臂 {{ row.armDeviceId }} + 相机 {{ row.cameraDeviceId }} + + + V{{ row.versionNo }} + + + + 最大 {{ Number(row.maxErrorMm).toFixed(2) }} mm + + 待验证 + 平均 {{ Number(row.meanErrorMm).toFixed(2) }} mm + + + + + {{ statusLabel(row.status) }} + 人工启用 + + + {{ row.verifiedTime || '-' }} + + + 微调 + 验证 + 启用 + 失效 + + + + + + + + + + + + + + + + + + + + + + + + + + + mm + ° + + mm + + + + 取消 + 开始标定 + + + + + + + {{ session.message }} + {{ sessionStatusLabel }} + + + 已记录 {{ session.sampleCount || 0 }} 组 · 步骤 {{ session.currentStep || 0 }}/{{ session.totalSteps || 0 }} + + + + + + + + + + + + + + 绿色边框为识别结果;黄色十字是工具定点标定统一使用的规范角点。 + + + + + 1 + 定点旋转使用示教按钮让工具中心对准标定码规范角点,每次明显改变末端角度。 + 记录当前位姿 + + + 2 + 安全方向样本足够后,让工具垂直贴合标定码平面,再记录工具工作朝向。 + 记录方向并完成 + + + + + 平均误差{{ formatError(session.meanErrorMm) }} + 最大误差{{ formatError(session.maxErrorMm) }} + 方向误差{{ Number(session.directionErrorDeg).toFixed(2) }}° + + + 停止并保持当前位置 + 完成 + + + + + + + 位置修正(毫米) + + 角度修正(度) + + + 取消保存并等待验证 + + + + + + +
{{ item.name }}
管理每台机器人的手眼标定和末端工具 TCP。工作流自动使用当前启用版本。
使用示教按钮让工具中心对准标定码规范角点,每次明显改变末端角度。
样本足够后,让工具垂直贴合标定码平面,再记录工具工作朝向。