Qt Quick 3D-机械臂模型显示与交互
简介
文章目录
- 简介
- Quick3D
- 机械臂模型搭建
- 代码与演示
Qt Quick 3D 为在QML中创建基于 Qt Quick 的3D内容和3D用户界面提供了一个高级 API。当使用这个空间场景图时,可以将 Qt Quick 的2D内容(Item)与3D内容(Node)混合在一起。

Quick3D
类似与普通组件,Item
是2D对象的基类,在3D组件中,Node
是3D对象的基类,例如 Camera, Joint, Light,Model
等3D相关的组件都继承自Node
。Node
本身也可以作为一个"空"的空间节点(用于组织结构)。通常可以以Node
为根节点,在其中加入模型,灯光等元素,最后将该节点导入到三维视图窗口组件View3D
中进行显示。官方例程可以参考,Qt Quick 3D Examples and Tutorials | Qt Quick 3D | Qt 6.9.3
机械臂模型搭建
机械臂模型文件来自Robot Arm | Qt 6.9,取其以下文件和文件夹,分别提供了模型描述和模型的网格(mesh)文件。如果要自己搭建模型可以使用相关工具导出,并将模型文件转为mesh,Qt配套的Balsam命令行工具可以将一些常见格式转为View3D
支持的mesh格式
查看描述文件层次结构,如下,一个Model套一个Model很像机械臂安装顺序,这样配置内部坐标变换是相对与上一个个父节点(Model)的变换,即相对位置
注意Node只是一个描述组件,并不具有显示功能,还需要创建一个名为View3D
的组件,使用其importScene
属性,传入Node的id,如下
View3D { id: view anchors.fill: parent importScene: rootNode camera: came
}
可以将View3D和Node封装在一个Item下方,这样可供外部调用显示
// Scene3D.qml
Item {Node {id: rootNode }View3D {anchors.fill: parent importScene: rootNode }
}// main.qml
Scene3D {
...
}
也可以添加一些组件或者鼠标事件用来控制机械臂之间的相对夹角,或者旋转相机视图或者旋转模型节点,实现交互功能
代码与演示
完整代码如下,注意需要先从官方例程content « robotarm « demos « examples - qt/qtdoc.git - Qt Documentation中找到meshes
文件夹放到和该文件同一个目录下,如果安装了Qt,本地电脑应该也有这个例程
import QtQuick
import QtQuick3D
import QtQuick.Layouts
import QtQuick.Controls Item { property int rotation1: rotations[0] property int rotation2: rotations[1] property int rotation3: rotations[2] property int rotation4: rotations[3] property int clawsAngle: rotations[4] readonly property alias hand_position: hand_grab_t.scenePosition readonly property alias hand_hinge_position: hand_hinge.scenePosition readonly property alias arm_position: arm.scenePosition readonly property alias forearm_position: forearm.scenePosition readonly property alias root_position: root.scenePosition property var rotations: [50, 50, 50, 0, 0] // 数组需要重新赋值才能触发绑定更新 property point lastMouse: "0,0" Behavior on rotation1 { PropertyAnimation { duration: 100 } } Behavior on rotation2 { PropertyAnimation { duration: 100 } } Behavior on rotation3 { PropertyAnimation { duration: 100 } } Behavior on rotation4 { PropertyAnimation { duration: 100 } } ColumnLayout { anchors.left: parent.left anchors.top: parent.top width: parent.width Repeater { id: sliderRepeater model: [ {label: "Rotation 1", min: -180, max: 180, step: 1, index: 0}, {label: "Rotation 2", min: -180, max: 180, step: 1, index: 1}, {label: "Rotation 3", min: -180, max: 180, step: 1, index: 2}, {label: "Rotation 4", min: -180, max: 180, step: 1, index: 3}, {label: "Claws Angle", min: 0, max: 90, step: 1, index: 4} ] delegate: Slider { Layout.fillWidth: true Layout.preferredHeight: 40 from: modelData.min to: modelData.max stepSize: modelData.step onValueChanged: { var newRotations = rotations; newRotations[modelData.index] = value rotations = newRotations // 触发绑定更新 } } } } Node { id: rootNode Node { PerspectiveCamera { id: came // z: 1000 // y: 300 position: Qt.vector3d(0, 100, 400) eulerRotation.x: 0 // 俯仰角 (Pitch) eulerRotation.y: 0 // 偏航角 (Yaw) } DirectionalLight { ambientColor: Qt.rgba(0.5, 0.5, 0.5, 1.0) brightness: 0.8 eulerRotation.x: -25 } } Model { id: base scale.x: 100 scale.y: 100 scale.z: 100 source: "meshes/base.mesh" eulerRotation.x: -90 DefaultMaterial { id: steel_material diffuseColor: "#ff595959" } DefaultMaterial { id: plastic_material } materials: [steel_material, plastic_material] Model { id: root y: -5.96047e-08 z: 1.0472 eulerRotation.z: rotation4 source: "meshes/root.mesh" DefaultMaterial { id: plastic_color_material diffuseColor: "#41cd52" } materials: [plastic_material, plastic_color_material, steel_material] Model { id: forearm x: 5.32907e-15 y: -0.165542 z: 1.53472 eulerRotation.x: rotation3 source: "meshes/forearm.mesh" /* 一个复杂的 3D 模型(如 .glb, .gltf 文件)每个部分称为一个 sub-mesh,可能由多个“部分”组成。 */ materials: [plastic_material, steel_material] Model { id: arm x: -7.43453e-07 y: 0.667101 z: 2.23365 eulerRotation.x: rotation2 source: "meshes/arm.mesh" DefaultMaterial { id: plastic_qt_material diffuseMap: Texture { source: "maps/qt.png" pivotU: 0.5 pivotV: 0.5 generateMipmaps: true mipFilter: Texture.Linear } } materials: [plastic_material, plastic_qt_material, steel_material] Model { id: hand_hinge x: 7.43453e-07 y: 0.0635689 z: 2.12289 eulerRotation.x: rotation1 source: "meshes/hand_hinge.mesh" materials: [plastic_material] Model { id: hand x: 3.35649e-06 y: 2.38419e-07 z: 0.366503 source: "meshes/hand.mesh" materials: [plastic_material, steel_material] Model { id: hand_grab_t_hinge_2 x: -9.5112e-07 y: 0.323057 z: 0.472305 eulerRotation: hand_grab_t_hinge_1.eulerRotation source: "meshes/hand_grab_t_hinge_2.mesh" materials: [steel_material] } Model { id: hand_grab_t_hinge_1 x: -9.3061e-07 y: 0.143685 z: 0.728553 eulerRotation.x: clawsAngle * -1 source: "meshes/hand_grab_t_hinge_1.mesh" materials: [steel_material] Model { id: hand_grab_t x: -2.42588e-06 y: -0.0327932 z: 0.414757 eulerRotation.x: hand_grab_t_hinge_1.eulerRotation.x * -1 source: "meshes/hand_grab_t.mesh" materials: [plastic_color_material, steel_material] } } Model { id: hand_grab_b_hinge_1 x: -9.38738e-07 y: -0.143685 z: 0.728553 eulerRotation.x: clawsAngle source: "meshes/hand_grab_b_hinge_1.mesh" materials: [steel_material] Model { id: hand_grab_b x: -2.41775e-06 y: 0.0327224 z: 0.413965 eulerRotation.x: hand_grab_b_hinge_1.eulerRotation.x * -1 source: "meshes/hand_grab_b.mesh" materials: [plastic_color_material, steel_material] } } Model { id: hand_grab_b_hinge_2 x: -9.5112e-07 y: -0.323058 z: 0.472305 eulerRotation: hand_grab_b_hinge_1.eulerRotation source: "meshes/hand_grab_b_hinge_2.mesh" materials: [steel_material] } } } } } } } } Item { anchors.fill: parent View3D { id: view anchors.fill: parent importScene: rootNode camera: came } MouseArea { id: cameraControlArea anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: 200 acceptedButtons: Qt.LeftButton | Qt.RightButton property real lastMouseX: 0 property real lastMouseY: 0 property var mouse_button: Qt.LeftButton // 当前按下的鼠标按钮 onPressed: function (mouse) { // 记录当前位置作为起点 lastMouseX = mouse.x lastMouseY = mouse.y mouse_button = mouse.button } onPositionChanged: function (mouse) { if (pressed) { var deltaX = mouse.x - lastMouseX var deltaY = mouse.y - lastMouseY if (mouse_button === Qt.LeftButton) { base.eulerRotation.y += deltaX } else if (mouse_button === Qt.RightButton) { base.position.x += deltaX * 5 base.position.y += -deltaY * 5 } // 更新上一次鼠标位置 lastMouseX = mouse.x lastMouseY = mouse.y } } onWheel: function (wheel) { var delta = wheel.angleDelta.y came.z += -delta } } }