简介
文章目录
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,如下
css
View3D {
id: view
anchors.fill: parent
importScene: rootNode
camera: came
}
可以将View3D和Node封装在一个Item下方,这样可供外部调用显示
css
// 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,本地电脑应该也有这个例程
css
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
}
}
}