ubuntu 如何安装blender
官网blender.org下载tar.xz压缩文件
tar -xvf xxx.tar.xz
如何启动blender,命令行输入:
blender
如何在blender中安装pygame模块
需要找到blender中的python解释器路径
import sys
print(sys.executable)
然后在终端terminal中使用以下命令
$ "xxxx/python" -m pip install pygame
在ubuntu系统下,使用blender执行脚本时,看不到日志信息。可以使用logging 模块调试程序。
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("详细调试信息")
logger.info("常规信息")
logger.warning("警告信息")
logger.error("错误信息")
这时候打印的信息,在控制台显示
ubuntu 使用blender如果出现卡死,可以使用如下命令,强制关闭blender 界面
pkill -9 blender
# 安装测试工具
sudo apt install jstest-gtk joystick
# 检测手柄
jstest /dev/input/js0
终端会出现如图所示的结果,最下面一行是 摇杆和开关的状态。如果不清楚映射关系,可以操控手柄,观察数值变化。

根据测试,遥控手柄的键位映射关系如下
摇杆Axes :
左横向:Axes 4
左纵向:Axes 1
右横向:Axes 3
右纵向:Axes 0
Axes 2 未知。手柄上没有控件对应Axes 2.
按钮 buttons:
SWA: 0
SWB:2
SWC:3
手柄上左上角的旋钮未知。
也可以通过 jstest-gtk图形工具,测试手柄的键位映射。

选择 properties.

晃动摇杆,或者切换SWA/B/C 开关,即可观测数值变化。
通过Axes 和 按钮 buttons, 即可完成后续的开发工作。
以下是关于游戏手柄的操作说明。
1.确保游戏手柄通过usb接口连接到ubuntu 系统;
2.确保blender 安装了pygame 模块,并可以正常使用;
pygame可以自动识别游戏手柄。
手柄摇杆映射参考:
left_stick_x = joystick.get_axis(4) # 左摇杆 X 轴,横轴
left_stick_y = joystick.get_axis(1) # 左摇杆 Y 轴, 纵轴
right_stick_x = joystick.get_axis(3) # 右摇杆 X 轴,横轴
right_stick_y = joystick.get_axis(0) # 右摇杆 Y 轴,纵轴
开发日志
执行脚本的时候,容易 发生闪退。
频繁发生以下现象

以下代码可以测试摇杆输出值,但是会引发blender is not responding 问题。
import bpy
import math
import random
import time
import mathutils
import pygame
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("详细调试信息")
logger.info("常规信息")
logger.warning("警告信息")
logger.error("错误信息")
import pygame
# 初始化 Pygame
pygame.init()
pygame.joystick.init()
# 检测手柄数量
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
print("未检测到手柄!")
exit()
# 初始化第一个手柄
joystick = pygame.joystick.Joystick(0)
joystick.init()
print(f"手柄名称: {joystick.get_name()}")
print(f"摇杆数量: {joystick.get_numaxes() // 2}") # 每个摇杆占 2 个轴(X/Y)
# 主循环
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 获取摇杆状态(不依赖事件,直接读取)
left_stick_y = joystick.get_axis(1) # 左摇杆 X 轴(通常 -1 左,1 右)
right_stick_x = joystick.get_axis(3)
left_stick_x = joystick.get_axis(4) # have some problem
right_stick_y = joystick.get_axis(0)
# 打印摇杆值(保留 2 位小数)
print(f"左摇杆: X={left_stick_x:.2f}, Y={left_stick_y:.2f} | 右摇杆: X={right_stick_x:.2f}, Y={right_stick_y:.2f}", end="\r")
pygame.time.delay(50) # 降低输出频率
pygame.quit()
以下是测试程序和功能说明
import bpy
import math
import random
import time
import mathutils
import pygame
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("详细调试信息")
logger.info("常规信息")
logger.warning("警告信息")
logger.error("错误信息")
class GamepadModalOperator(bpy.types.Operator):
bl_idname = "object.gamepad_modal_operator"
bl_label = "Gamepad Modal Control"
_timer = None
_joystick = None
def modal(self, context, event):
# 首先处理Blender事件(如ESC键退出)
if event.type in {'ESC'}:
return self.cancel(context)
# 只在TIMER事件中处理手柄输入,避免过于频繁的更新
if event.type == 'TIMER':
# 处理Pygame事件队列
pygame.event.pump()
# 获取所有Pygame事件但不处理(避免阻塞)
for py_event in pygame.event.get():
# 处理按钮事件
if py_event.type == pygame.JOYBUTTONDOWN:
print(f"按钮 {py_event.button} 按下")
if py_event.button == 0: # A按钮示例
print("back")
# 指定物体的名称
object_name = "drone" # 替换为你的物体名称
obj = bpy.data.objects.get(object_name)
if obj:
# 定义移动向量(局部坐标系)
move_vector = mathutils.Vector((0, 0, -0.2)) # 沿着局部坐标系的 X 轴移动 1 个单位
# (x,z,y)
# 将局部坐标系的向量转换为世界坐标系
world_move_vector = obj.matrix_world @ move_vector - obj.matrix_world.translation
# 更新物体的位置
obj.location += world_move_vector
if py_event.button == 3: # A按钮示例
print("forward")
object_name = "drone"
obj = bpy.data.objects.get(object_name)
if obj:
# 定义移动向量(局部坐标系)
move_vector = mathutils.Vector((0, 0, 0.2)) # 沿着局部坐标系的 X 轴移动 1 个单位
# (x,z,y)
# 将局部坐标系的向量转换为世界坐标系
world_move_vector = obj.matrix_world @ move_vector - obj.matrix_world.translation
# 更新物体的位置
obj.location += world_move_vector
else:
print(f"物体 '{object_name}' 未找到!")
if py_event.type == pygame.JOYBUTTONUP:
print(f"按钮 {py_event.button} 释放")
# 方向键(hat)事件
if event.type == pygame.JOYHATMOTION:
print(f"方向键 {event.hat} 值: {event.value}")
if event.value== (-1,0):
print("left turn!!!")
if event.value== (1,0):
print("right turn!!!")
if event.value== (0,1):
print("up turn!!!")
if event.value== (0,-1):
print("down turn!!!")
# 直接读取轴状态(更高效的方式)
# if self._joystick:
# # 获取左摇杆状态(轴0和1)
# axis_x = self._joystick.get_axis(0)
# axis_y = self._joystick.get_axis(1)
#
# # 应用死区过滤
# deadzone = 0.2
# move_speed = 0.1
#
# if context.active_object:
# if abs(axis_x) > deadzone:
# context.active_object.location.x += axis_x * move_speed
# if abs(axis_y) > deadzone:
# context.active_object.location.y -= axis_y * move_speed # Y轴反转
#
# # 获取右摇杆状态(轴2和3)
# axis_rx = self._joystick.get_axis(2)
# axis_ry = self._joystick.get_axis(3)
#
# if abs(axis_rx) > deadzone:
# context.active_object.rotation_euler.z -= axis_rx * 0.05
# if abs(axis_ry) > deadzone:
# context.active_object.rotation_euler.x += axis_ry * 0.05
return {'PASS_THROUGH'}
def execute(self, context):
# 初始化Pygame
pygame.init()
pygame.joystick.init()
# 检查手柄连接
if pygame.joystick.get_count() == 0:
self.report({'ERROR'}, "未检测到手柄设备")
return {'CANCELLED'}
# 初始化第一个手柄
self._joystick = pygame.joystick.Joystick(0)
self._joystick.init()
print(f"已连接手柄: {self._joystick.get_name()}")
# 设置定时器,控制更新频率
wm = context.window_manager
self._timer = wm.event_timer_add(0.04, window=context.window) # 约50FPS
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def cancel(self, context):
# 清理资源
if self._timer:
wm = context.window_manager
wm.event_timer_remove(self._timer)
if hasattr(self, '_joystick') and self._joystick:
self._joystick.quit()
pygame.quit()
print("手柄控制已退出")
return {'CANCELLED'}
def register():
bpy.utils.register_class(GamepadModalOperator)
def unregister():
bpy.utils.unregister_class(GamepadModalOperator)
# 测试运行
if __name__ == "__main__":
register()
bpy.ops.object.gamepad_modal_operator('INVOKE_DEFAULT')
以下脚本可以正常输出。
import bpy
import math
import random
import time
import mathutils
import pygame
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("详细调试信息")
logger.info("常规信息")
logger.warning("警告信息")
logger.error("错误信息")
class GamepadModalOperator(bpy.types.Operator):
bl_idname = "object.gamepad_modal_operator"
bl_label = "Gamepad Modal Control"
_timer = None
_joystick = None
def modal(self, context, event):
# 首先处理Blender事件(如ESC键退出)
if event.type in {'ESC'}:
return self.cancel(context)
# 只在TIMER事件中处理手柄输入,避免过于频繁的更新
if event.type == 'TIMER':
# 处理Pygame事件队列
pygame.event.pump()
# 获取所有Pygame事件但不处理(避免阻塞)
for py_event in pygame.event.get():
# 处理按钮事件
if py_event.type == pygame.JOYBUTTONDOWN:
print(f"按钮 {py_event.button} 按下")
if py_event.button == 0: # A按钮示例
print("back")
# 指定物体的名称
object_name = "drone" # 替换为你的物体名称
obj = bpy.data.objects.get(object_name)
if obj:
# 定义移动向量(局部坐标系)
move_vector = mathutils.Vector((0, 0, -0.2)) # 沿着局部坐标系的 X 轴移动 1 个单位
# (x,z,y)
# 将局部坐标系的向量转换为世界坐标系
world_move_vector = obj.matrix_world @ move_vector - obj.matrix_world.translation
# 更新物体的位置
obj.location += world_move_vector
if py_event.button == 3: # Y按钮示例
print("forward")
object_name = "drone"
obj = bpy.data.objects.get(object_name)
if obj:
# 定义移动向量(局部坐标系)
move_vector = mathutils.Vector((0, 0, 0.2)) # 沿着局部坐标系的 X 轴移动 1 个单位
# (x,z,y)
# 将局部坐标系的向量转换为世界坐标系
world_move_vector = obj.matrix_world @ move_vector - obj.matrix_world.translation
# 更新物体的位置
obj.location += world_move_vector
else:
print(f"物体 '{object_name}' 未找到!")
if py_event.type == pygame.JOYBUTTONUP:
print(f"按钮 {py_event.button} 释放")
# 方向键(hat)事件
if event.type == pygame.JOYHATMOTION:
print(f"方向键 {event.hat} 值: {event.value}")
if event.value== (-1,0):
print("left turn!!!")
if event.value== (1,0):
print("right turn!!!")
if event.value== (0,1):
print("up turn!!!")
if event.value== (0,-1):
print("down turn!!!")
# 直接读取轴状态(更高效的方式)
# if self._joystick:
# # 获取左摇杆状态(轴0和1)
# axis_x = self._joystick.get_axis(0)
# axis_y = self._joystick.get_axis(1)
#
# # 应用死区过滤
# deadzone = 0.2
# move_speed = 0.1
#
# if context.active_object:
# if abs(axis_x) > deadzone:
# context.active_object.location.x += axis_x * move_speed
# if abs(axis_y) > deadzone:
# context.active_object.location.y -= axis_y * move_speed # Y轴反转
#
# # 获取右摇杆状态(轴2和3)
# axis_rx = self._joystick.get_axis(2)
# axis_ry = self._joystick.get_axis(3)
#
# if abs(axis_rx) > deadzone:
# context.active_object.rotation_euler.z -= axis_rx * 0.05
# if abs(axis_ry) > deadzone:
# context.active_object.rotation_euler.x += axis_ry * 0.05
return {'PASS_THROUGH'}
def execute(self, context):
# 初始化Pygame
pygame.init()
pygame.joystick.init()
# 检查手柄连接
if pygame.joystick.get_count() == 0:
self.report({'ERROR'}, "未检测到手柄设备")
return {'CANCELLED'}
# 初始化第一个手柄
self._joystick = pygame.joystick.Joystick(0)
self._joystick.init()
print(f"已连接手柄: {self._joystick.get_name()}")
# 设置定时器,控制更新频率
wm = context.window_manager
self._timer = wm.event_timer_add(0.04, window=context.window) # 约50FPS
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def cancel(self, context):
# 清理资源
if self._timer:
wm = context.window_manager
wm.event_timer_remove(self._timer)
if hasattr(self, '_joystick') and self._joystick:
self._joystick.quit()
pygame.quit()
print("手柄控制已退出")
return {'CANCELLED'}
def register():
bpy.utils.register_class(GamepadModalOperator)
def unregister():
bpy.utils.unregister_class(GamepadModalOperator)
# 测试运行
if __name__ == "__main__":
register()
bpy.ops.object.gamepad_modal_operator('INVOKE_DEFAULT')