目录
实现功能
1、通关节角度调整机械臂姿态(关节运动)
2、示教点
3、坐标系显示
4、支持机械臂3d显示
5、支持桌子、xy平台3d显示
6、系统资源监控
实现效果
STL简介
STL文件是一种用许多空间小三角形面片逼近三维实体表面的3D模型。STL模型给出了组成三角形法向量的3个分量(用于确定三角面片的正反方向)及三角形的3个顶点坐标。一个完整的STL文件记录了组成实体模型的所有三角形面片的法向量数据和顶点坐标数据信息。
大致思路
-
第一步:从3D软件中导出STL格式的文件
STL文件格式包括二进制文件(BINARY)和文本文件(ASCII)两种
-
第二步:解析STL文件内容,包括三角形的三个顶点坐标和法向量
解析二进制文件(BINARY)
解析文本文件(ASCII)
两种实现一个均可,本文只实现了第二种方案,解析放在了stlfileloader中
-
第三步:使用opengl接口循环画出每个三角形就可以画出一个完成的小模型
-
第四步:组装多个STL模型
通过旋转glRotatef
、平移glTranslatef
的方式改变坐标系将每个小模型显示在不同的位置,这个需要设置大量参数去调整
如何让模型运动起来?
结合实际的机械臂运行原理可知,通过马达转动可将机械臂摆成不同的姿态。在3d模型中每个关节都是一个小模型,每个小模型都有一个坐标系(关节坐标系),将模型的旋转方式设置成一致的方向,比如所有关节绕Z轴旋转,只要控制多个Z轴的旋转角度就可以整体运动起来。
类图设计
核心代码
STL文件解析
stlfileloader.h
#ifndef STLFILELOADER_H
#define STLFILELOADER_H
#include <QList>
#include <QVector3D>
class STLTriangle;
//STL文件解析类
class STLFileLoader {
public:
/**
* @brief STLFileLoader
* @param filename stl文件路径
* @param ratio 放大系数(有的3d模型单位很小,通过放大系数将模型放大)
*/
STLFileLoader(QString filename, int ratio);
~STLFileLoader();
/**
* @brief draw 画出STL模型
*/
void draw();
private:
/**
* @brief loadStl 加载stl文件
* @param filename
*/
void loadStl(QString filename);
/**
* @brief loadTextStl 加载文本格式stl文件
* @param filename
*/
void loadTextStl(QString filename);
/**
* @brief loadBinaryStl 加载二进制格式stl文件
* @param filename
*/
void loadBinaryStl(QString filename);
private:
QList<STLTriangle> model;
int mRatio = 1; //比例系数
};
//stl格式: 三个顶点 + 法向量
class STLTriangle {
public:
STLTriangle();
/**
* @brief setVertex 设置顶点坐标
* @param index 哪个点
* @param point3D
*/
void setVertex(int index, QVector3D point3D);
/**
* @brief getVertex 获取顶点坐标
* @param index 哪个点
* @return
*/
QVector3D getVertex(int index);
/**
* @brief setNormal 设置法向量
* @param nx x分量
* @param ny y分量
* @param nz z分量
*/
void setNormal(float nx, float ny, float nz);
/**
* @brief getNormal 获取法向量
* @return
*/
QVector3D getNormal();
/**
* @brief reset 重置参数
*/
void reset();
private:
/**
* @brief checkVertexIndex
* @param index
* @return
*/
bool checkVertexIndex(int index);
private:
QVector3D v[3]; //三角顶点坐标
QVector3D n; //三角形法向量的三个分量
};
#endif // STLFILELOADER_H
stlfileloader.cpp
#include "stlfileloader.h"
#include <GL/glu.h>
#include <QDebug>
#include <QFile>
#include <fstream>
#include <iostream>
STLFileLoader::STLFileLoader(QString filename, int ratio) {
mRatio = ratio;
model.clear();
loadStl(filename);
}
STLFileLoader::~STLFileLoader() {}
void STLFileLoader::loadStl(QString filename) {
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QByteArray arr;
arr = file.read(5);
file.close();
qDebug() << arr;
if (arr == "solid") {
loadTextStl(filename);
} else {
loadBinaryStl(filename);
}
}else{
qDebug() << filename << "不存在";
}
}
void STLFileLoader::loadTextStl(QString filename) {
qDebug() << "load text file:" << filename;
model.clear(); //清除模型
QList<QVector3D> triangle;
STLTriangle tSTLTriangle;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
while (!file.atEnd()) {
QString line =
file.readLine().trimmed(); // trimmed去除了开头和结尾的空白字符串
QStringList words = line.split(' ', QString::SkipEmptyParts);
if (words[0] == "facet") {
triangle.clear();
tSTLTriangle.reset();
tSTLTriangle.setNormal(words[2].toFloat(), words[3].toFloat(),
words[4].toFloat());
}
if (words[0] == "vertex") {
triangle.append(QVector3D(words[1].toFloat(), words[2].toFloat(),
words[3].toFloat()));
}
if (words[0] == "endloop") {
if (triangle.length() == 3) {
for (size_t i = 0; i < 3; ++i) {
tSTLTriangle.setVertex(i, triangle[i]);
}
model.append(tSTLTriangle);
}
}
}
file.close();
}
}
void STLFileLoader::loadBinaryStl(QString filename) {
//TODO: 待完成二进制文件解析,二进制文件大小更小,这种解析效率会更高
qDebug() << "load binary file:" << filename;
// std::ifstream file(filename, std::ios::in | std::ios::binary);
// if (file) {
// char header[80];
// char num_triangles[4];
// file.read(header, 80);
// file.read(num_triangles, 4);
// std::string(header);
// }
// file.close();
}
void STLFileLoader::draw() {
QList<STLTriangle> triangles = model;
QVector3D normal;
QVector3D vertex;
glBegin(GL_TRIANGLES); // 绘制一个或多个三角形
foreach (STLTriangle tri, triangles) {
normal = tri.getNormal();
glNormal3f(mRatio * normal.x(), mRatio * normal.y(), mRatio * normal.z());
for (size_t j = 0; j < 3; ++j) {
vertex = tri.getVertex(j);
glVertex3f(mRatio * vertex.x(), mRatio * vertex.y(), mRatio * vertex.z());
}
}
glEnd();
}
STLTriangle::STLTriangle() { reset(); }
void STLTriangle::setVertex(int index, QVector3D point3D) {
if (!checkVertexIndex(index)) {
return;
}
v[index] = point3D;
}
QVector3D STLTriangle::getVertex(int index) {
if (!checkVertexIndex(index)) {
return QVector3D();
}
return v[index];
}
void STLTriangle::setNormal(float nx, float ny, float nz) {
n = QVector3D(nx, ny, nz);
}
QVector3D STLTriangle::getNormal() { return n; }
void STLTriangle::reset() {
n = QVector3D(0.f, 0.f, 0.f);
for (int i = 0; i < 3; ++i) {
v[i] = QVector3D(0.f, 0.f, 0.f);
}
}
bool STLTriangle::checkVertexIndex(int index) {
if (index < 0 || index > 2) {
qDebug() << "CRITICAL: invalid index provided to STLTriangle::SetVertex()!";
return false;
}
return true;
}
参考资料
- PyQt /Python + opengl实现 https://github.com/khuongnguyen96/RobotSimulator
IT项目交流群-1群:245022761
IT项目交流群-2群:9462866053