feat: 开发移动AGV节点
This commit is contained in:
parent
751cb3cd8e
commit
6dfa2be64e
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "CMVR-IOT-UI",
|
"name": "changan-inspection",
|
||||||
"version": "3.8.8",
|
"version": "3.8.8",
|
||||||
"description": "CMVR-IOT平台管理系统前端UI框架",
|
"description": "长安巡检项目",
|
||||||
"author": "CMVR",
|
"author": "CMVR",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -1186,10 +1186,6 @@
|
|||||||
</el-collapse>
|
</el-collapse>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<!-- <template #footer>
|
|
||||||
<el-button @click="cancel">取消</el-button>
|
|
||||||
<el-button type="primary" @click="confirm">保存</el-button>
|
|
||||||
</template> -->
|
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -363,12 +363,28 @@ function handler(params) {
|
|||||||
{
|
{
|
||||||
collapseTitle: "机器人",
|
collapseTitle: "机器人",
|
||||||
nodeList: [
|
nodeList: [
|
||||||
|
{
|
||||||
|
icon: videoSvg,
|
||||||
|
name: "AGV控制",
|
||||||
|
type: "serviceNode",
|
||||||
|
desc: "控制机器人的底盘",
|
||||||
|
action: 'AGV_MOVE_TO_POINT',
|
||||||
|
nodeType: 'EDGE',
|
||||||
|
nodeParams: [
|
||||||
|
{ name: "deviceId", type: "input", input: "", disabled: true },
|
||||||
|
{ name: "x", type: "input", input: "", disabled: true },
|
||||||
|
{ name: "y", type: "input", input: "", disabled: true },
|
||||||
|
{ name: "theta", type: "input", input: "", disabled: true },
|
||||||
|
],
|
||||||
|
outputType: 'json'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: videoSvg,
|
icon: videoSvg,
|
||||||
name: "机械臂",
|
name: "机械臂",
|
||||||
type: "serviceNode",
|
type: "serviceNode",
|
||||||
desc: "控制机器人的手臂",
|
desc: "控制机器人的手臂",
|
||||||
action: 'ALM',
|
action: 'ALM',
|
||||||
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", disabled: true }
|
{ name: "deviceId", type: "input", input: "", disabled: true }
|
||||||
],
|
],
|
||||||
|
|||||||
@ -34,89 +34,13 @@ let scene, camera, renderer, controls;
|
|||||||
const animationId = ref(null);
|
const animationId = ref(null);
|
||||||
const jointControls = reactive([]);
|
const jointControls = reactive([]);
|
||||||
|
|
||||||
|
|
||||||
// ============ 工具函数 ============
|
|
||||||
const cleanMeshPath = (path) => {
|
|
||||||
if (!path) return '';
|
|
||||||
path = path.replace(/^file:\/\//, '');
|
|
||||||
path = path.replace(/^package:\/\/[^/]+\//, '');
|
|
||||||
const findMatch = path.match(/\$\(find\s+([^)]+)\)/);
|
|
||||||
if (findMatch) path = path.replace(/\$\(find\s+[^)]+\)/, '');
|
|
||||||
path = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseXYZ = (str) => {
|
|
||||||
if (!str) return { x: 0, y: 0, z: 0 };
|
|
||||||
const [x, y, z] = str.trim().split(/\s+/).map(Number);
|
|
||||||
return { x: x || 0, y: y || 0, z: z || 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseRPY = (str) => {
|
|
||||||
if (!str) return { roll: 0, pitch: 0, yaw: 0 };
|
|
||||||
const [r, p, y] = str.trim().split(/\s+/).map(Number);
|
|
||||||
return { roll: r || 0, pitch: p || 0, yaw: y || 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
// ★ 正确转换 RPY → 四元数(URDF 固定轴 XYZ = Three.js 局部轴 ZYX)
|
|
||||||
const rpyToQuaternion = (rpy) => {
|
|
||||||
const euler = new THREE.Euler(rpy.roll, rpy.pitch, rpy.yaw, 'ZYX');
|
|
||||||
return new THREE.Quaternion().setFromEuler(euler);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============ STL 加载 ============
|
|
||||||
const loadSTLWithRetry = (url, group, visualPos, visualQuat) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const loader = new STLLoader();
|
|
||||||
const loadMesh = (attemptUrl) => {
|
|
||||||
loader.load(
|
|
||||||
attemptUrl,
|
|
||||||
(geometry) => {
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
color: props.modelColor,
|
|
||||||
roughness: 0.4,
|
|
||||||
metalness: 0.3,
|
|
||||||
});
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
mesh.position.set(visualPos.x, visualPos.y, visualPos.z);
|
|
||||||
mesh.quaternion.copy(visualQuat); // 使用四元数
|
|
||||||
mesh.castShadow = true;
|
|
||||||
mesh.receiveShadow = true;
|
|
||||||
while (group.children.length > 0) {
|
|
||||||
const child = group.children[0];
|
|
||||||
if (child.isMesh) {
|
|
||||||
child.geometry?.dispose();
|
|
||||||
child.material?.dispose();
|
|
||||||
}
|
|
||||||
group.remove(child);
|
|
||||||
}
|
|
||||||
group.add(mesh);
|
|
||||||
resolve(mesh);
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
(error) => {
|
|
||||||
console.warn(`加载STL失败: ${attemptUrl}`, error);
|
|
||||||
if (!attemptUrl.startsWith('/') && !attemptUrl.startsWith('http')) {
|
|
||||||
loadMesh('/' + attemptUrl);
|
|
||||||
} else {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
loadMesh(url);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ============ 场景初始化 ============
|
// ============ 场景初始化 ============
|
||||||
const initScene = () => {
|
const initScene = () => {
|
||||||
const container = viewerContainer.value;
|
const container = viewerContainer.value;
|
||||||
const width = container.clientWidth || 800;
|
const width = container.clientWidth || 800;
|
||||||
const height = container.clientHeight || 600;
|
const height = container.clientHeight || 600;
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x1a1a2e);
|
scene.background = new THREE.Color('#000d2e');
|
||||||
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
|
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
|
||||||
camera.position.set(2, 2, 3);
|
camera.position.set(2, 2, 3);
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||||
@ -145,9 +69,9 @@ const initScene = () => {
|
|||||||
const rim = new THREE.DirectionalLight(0xffffff, 0.3);
|
const rim = new THREE.DirectionalLight(0xffffff, 0.3);
|
||||||
rim.position.set(0, -5, 5);
|
rim.position.set(0, -5, 5);
|
||||||
scene.add(rim);
|
scene.add(rim);
|
||||||
const grid = new THREE.GridHelper(3, 20, 0x444466, 0x333355);
|
// const grid = new THREE.GridHelper(3, 20, 0x444466, 0x333355);
|
||||||
grid.position.y = -0.5;
|
// grid.position.y = -0.5;
|
||||||
scene.add(grid);
|
// scene.add(grid);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -174,7 +98,6 @@ const updateJoint = (jointName, angle) => {
|
|||||||
if (control) control.value = angle;
|
if (control) control.value = angle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const resetJoints = () => {
|
const resetJoints = () => {
|
||||||
for (const joint of jointControls) {
|
for (const joint of jointControls) {
|
||||||
joint.value = 0;
|
joint.value = 0;
|
||||||
@ -225,7 +148,6 @@ const loadRobot = async () => {
|
|||||||
|
|
||||||
const robot = model.threeObject; // 这就是 URDFRobot
|
const robot = model.threeObject; // 这就是 URDFRobot
|
||||||
|
|
||||||
|
|
||||||
// 将机器人添加到场景
|
// 将机器人添加到场景
|
||||||
scene.add(robot); // ⚠️ 注意:是 robot.scene,不是 robot
|
scene.add(robot); // ⚠️ 注意:是 robot.scene,不是 robot
|
||||||
robot.rotation.x = -Math.PI / 2;
|
robot.rotation.x = -Math.PI / 2;
|
||||||
@ -246,8 +168,6 @@ const loadRobot = async () => {
|
|||||||
console.error('未知的 joints 结构:', robot.joints);
|
console.error('未知的 joints 结构:', robot.joints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 填充关节控制列表
|
// 填充关节控制列表
|
||||||
for (const [name, joint] of jointEntries) {
|
for (const [name, joint] of jointEntries) {
|
||||||
if (joint._jointType === 'fixed') continue;
|
if (joint._jointType === 'fixed') continue;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user