添加两种不同种类的3D线条显示效果+解决界面bug

This commit is contained in:
2026-01-08 15:36:40 +08:00
parent bbfcd63503
commit 2de3857d1c
3 changed files with 137 additions and 127 deletions

View File

@ -19,7 +19,6 @@ from ui_3d import Target3DWidgetA, Target3DWidgetB
class LengthMotorController(QMainWindow): class LengthMotorController(QMainWindow):
"""线条长度标记与电机位置控制系统主界面""" """线条长度标记与电机位置控制系统主界面"""
update_time_signal = Signal(str)
record_data_signal = Signal(float, float, float, float) record_data_signal = Signal(float, float, float, float)
line_select_signal = Signal(str, float, float, float, float) line_select_signal = Signal(str, float, float, float, float)
@ -241,6 +240,7 @@ class LengthMotorController(QMainWindow):
self.time_label = QLabel() self.time_label = QLabel()
self.time_label.setAlignment(Qt.AlignCenter) self.time_label.setAlignment(Qt.AlignCenter)
self.time_label.setFont(QFont("Microsoft YaHei", 14, QFont.Bold)) self.time_label.setFont(QFont("Microsoft YaHei", 14, QFont.Bold))
self._update_current_time() # 手动触发一次,避免延迟
time_layout.addWidget(self.time_label) time_layout.addWidget(self.time_label)
right_v_layout.addWidget(time_group) right_v_layout.addWidget(time_group)
@ -390,39 +390,56 @@ class LengthMotorController(QMainWindow):
display_layout = QVBoxLayout(display_group) display_layout = QVBoxLayout(display_group)
display_layout.setContentsMargins(20, 20, 20, 20) display_layout.setContentsMargins(20, 20, 20, 20)
# 实例化ui_3d.py中的Target3DWidget # 创建3D控件的容器
self.ui_3d_widget = Target3DWidgetA() # 创建3D绘图控件实例 self.ui_3d_container = QWidget() # 空容器专门放3D控件
self.ui_3d_widget.setMaximumSize(500, 330) self.ui_3d_container_layout = QVBoxLayout(self.ui_3d_container)
self.ui_3d_container_layout.setContentsMargins(0, 0, 0, 0)
self.ui_3d_container.setFixedSize(500, 330)
test_btn = QPushButton("测试参数传递") # 初始化默认显示A控件
test_btn.setFont(self.font2) self.current_3d_widget = Target3DWidgetA()
test_btn.setFixedWidth(150) self.ui_3d_container_layout.addWidget(self.current_3d_widget)
test_btn.clicked.connect(self._test_3d_param_pass)
display_layout.addWidget(self.ui_3d_widget) display_layout.addWidget(self.ui_3d_container)
display_layout.addWidget(test_btn, alignment=Qt.AlignCenter)
parent_layout.addWidget(display_group) parent_layout.addWidget(display_group)
def _switch_3d_widget(self, widget_type):
"""
动态切换3D控件
:param widget_type: "A""B"
"""
# 移除旧的3D控件
if self.current_3d_widget:
self.current_3d_widget.setParent(None) # 从布局中移除
self.current_3d_widget.deleteLater() # 释放资源
# 创建新的3D控件
if widget_type == "A":
self.current_3d_widget = Target3DWidgetA()
else:
self.current_3d_widget = Target3DWidgetB()
# 添加新控件到容器
self.ui_3d_container_layout.addWidget(self.current_3d_widget)
def _init_timer(self): def _init_timer(self):
"""初始化定时器""" """初始化定时器"""
self.timer = QTimer(self) self.timer = QTimer(self)
self.timer.timeout.connect(self._update_current_time) self.timer.timeout.connect(self._update_current_time)
self.timer.start(1000) self.timer.start(1000)
self._update_current_time()
def _connect_signals(self): def _connect_signals(self):
"""连接信号与槽函数""" """连接信号与槽函数"""
self.save_btn.clicked.connect(self._save_settings) self.save_btn.clicked.connect(self._save_settings)
self.clear_btn.clicked.connect(self._clear_inputs) self.clear_btn.clicked.connect(self._clear_inputs)
self.export_btn.clicked.connect(self._export_records) self.export_btn.clicked.connect(self._export_records)
self.update_time_signal.connect(self.time_label.setText)
self.record_data_signal.connect(self._log_param_change) self.record_data_signal.connect(self._log_param_change)
self.line_select_signal.connect(self._set_line_params) self.line_select_signal.connect(self._set_line_params)
def _update_current_time(self): def _update_current_time(self):
"""更新当前时间""" """更新当前时间"""
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.update_time_signal.emit(current_time) self.time_label.setText(current_time)
@Slot(str) @Slot(str)
def _import_line_param(self, line_type): def _import_line_param(self, line_type):
@ -431,13 +448,17 @@ class LengthMotorController(QMainWindow):
selected_type = self.selected_line_a selected_type = self.selected_line_a
params = self.line_a_params[selected_type] params = self.line_a_params[selected_type]
line_name = f"线条A-{selected_type}" line_name = f"线条A-{selected_type}"
# 切换为A控件
self._switch_3d_widget("A")
else: else:
selected_type = self.selected_line_b selected_type = self.selected_line_b
params = self.line_b_params[selected_type] params = self.line_b_params[selected_type]
line_name = f"线条B-{selected_type}" line_name = f"线条B-{selected_type}"
# 切换为B控件
self._switch_3d_widget("B")
self.line_select_signal.emit(line_name, *params) self.line_select_signal.emit(line_name, *params)
QMessageBox.information(self, "导入成功", f"已导入{line_name}的参数") QMessageBox.information(self, "导入成功", f"已导入{line_name}的参数\n3D视图已切换为{line_type}类型")
@Slot(str, float, float, float, float) @Slot(str, float, float, float, float)
def _set_line_params(self, line_name, a, b, c, d): def _set_line_params(self, line_name, a, b, c, d):
@ -449,10 +470,10 @@ class LengthMotorController(QMainWindow):
d_str = f"{d:.0f}" # 参数d是整数25/26/28等 d_str = f"{d:.0f}" # 参数d是整数25/26/28等
"""设置参数到下拉框""" """设置参数到下拉框"""
self.combo_a.setCurrentText(str(a)) self.combo_a.setCurrentText(a_str)
self.combo_b.setCurrentText(str(b)) self.combo_b.setCurrentText(b_str)
self.combo_c.setCurrentText(str(c)) self.combo_c.setCurrentText(c_str)
self.combo_d.setCurrentText(str(d)) self.combo_d.setCurrentText(d_str)
@Slot() @Slot()
def _save_settings(self): def _save_settings(self):
@ -482,27 +503,6 @@ class LengthMotorController(QMainWindow):
"time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) "time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}) })
@Slot()
def _test_3d_param_pass(self):
"""测试参数传递"""
try:
param_a = float(self.combo_a.currentText())
param_b = float(self.combo_b.currentText())
param_c = float(self.combo_c.currentText())
param_d = float(self.combo_d.currentText())
except ValueError:
QMessageBox.critical(self, "测试失败", "当前参数格式无效!")
return
QMessageBox.information(
self, "参数传递测试成功",
f"当前参数已准备好传递给3D模块\n"
f"参数a半径{param_a}\n"
f"参数b长度{param_b}\n"
f"参数c颜色通道1{param_c}\n"
f"参数d颜色通道2{param_d}"
)
@Slot() @Slot()
def _clear_inputs(self): def _clear_inputs(self):
"""清空所有选择""" """清空所有选择"""

View File

@ -19,7 +19,6 @@ from ui_3d import Target3DWidgetA, Target3DWidgetB
class LengthMotorController(QMainWindow): class LengthMotorController(QMainWindow):
"""线条长度标记与电机位置控制系统主界面""" """线条长度标记与电机位置控制系统主界面"""
update_time_signal = Signal(str)
record_data_signal = Signal(float, float, float, float) record_data_signal = Signal(float, float, float, float)
line_select_signal = Signal(str, float, float, float, float) line_select_signal = Signal(str, float, float, float, float)
@ -241,6 +240,7 @@ class LengthMotorController(QMainWindow):
self.time_label = QLabel() self.time_label = QLabel()
self.time_label.setAlignment(Qt.AlignCenter) self.time_label.setAlignment(Qt.AlignCenter)
self.time_label.setFont(QFont("Microsoft YaHei", 14, QFont.Bold)) self.time_label.setFont(QFont("Microsoft YaHei", 14, QFont.Bold))
self._update_current_time() # 手动触发一次,避免延迟
time_layout.addWidget(self.time_label) time_layout.addWidget(self.time_label)
right_v_layout.addWidget(time_group) right_v_layout.addWidget(time_group)
@ -390,39 +390,56 @@ class LengthMotorController(QMainWindow):
display_layout = QVBoxLayout(display_group) display_layout = QVBoxLayout(display_group)
display_layout.setContentsMargins(20, 20, 20, 20) display_layout.setContentsMargins(20, 20, 20, 20)
# 实例化ui_3d.py中的Target3DWidget # 创建3D控件的容器
self.ui_3d_widget = Target3DWidgetA() # 创建3D绘图控件实例 self.ui_3d_container = QWidget() # 空容器专门放3D控件
self.ui_3d_widget.setMaximumSize(500, 330) self.ui_3d_container_layout = QVBoxLayout(self.ui_3d_container)
self.ui_3d_container_layout.setContentsMargins(0, 0, 0, 0)
self.ui_3d_container.setFixedSize(500, 330)
test_btn = QPushButton("测试参数传递") # 初始化默认显示A控件
test_btn.setFont(self.font2) self.current_3d_widget = Target3DWidgetA()
test_btn.setFixedWidth(150) self.ui_3d_container_layout.addWidget(self.current_3d_widget)
test_btn.clicked.connect(self._test_3d_param_pass)
display_layout.addWidget(self.ui_3d_widget) display_layout.addWidget(self.ui_3d_container)
display_layout.addWidget(test_btn, alignment=Qt.AlignCenter)
parent_layout.addWidget(display_group) parent_layout.addWidget(display_group)
def _switch_3d_widget(self, widget_type):
"""
动态切换3D控件
:param widget_type: "A""B"
"""
# 移除旧的3D控件
if self.current_3d_widget:
self.current_3d_widget.setParent(None) # 从布局中移除
self.current_3d_widget.deleteLater() # 释放资源
# 创建新的3D控件
if widget_type == "A":
self.current_3d_widget = Target3DWidgetA()
else:
self.current_3d_widget = Target3DWidgetB()
# 添加新控件到容器
self.ui_3d_container_layout.addWidget(self.current_3d_widget)
def _init_timer(self): def _init_timer(self):
"""初始化定时器""" """初始化定时器"""
self.timer = QTimer(self) self.timer = QTimer(self)
self.timer.timeout.connect(self._update_current_time) self.timer.timeout.connect(self._update_current_time)
self.timer.start(1000) self.timer.start(1000)
self._update_current_time()
def _connect_signals(self): def _connect_signals(self):
"""连接信号与槽函数""" """连接信号与槽函数"""
self.save_btn.clicked.connect(self._save_settings) self.save_btn.clicked.connect(self._save_settings)
self.clear_btn.clicked.connect(self._clear_inputs) self.clear_btn.clicked.connect(self._clear_inputs)
self.export_btn.clicked.connect(self._export_records) self.export_btn.clicked.connect(self._export_records)
self.update_time_signal.connect(self.time_label.setText)
self.record_data_signal.connect(self._log_param_change) self.record_data_signal.connect(self._log_param_change)
self.line_select_signal.connect(self._set_line_params) self.line_select_signal.connect(self._set_line_params)
def _update_current_time(self): def _update_current_time(self):
"""更新当前时间""" """更新当前时间"""
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.update_time_signal.emit(current_time) self.time_label.setText(current_time)
@Slot(str) @Slot(str)
def _import_line_param(self, line_type): def _import_line_param(self, line_type):
@ -431,13 +448,17 @@ class LengthMotorController(QMainWindow):
selected_type = self.selected_line_a selected_type = self.selected_line_a
params = self.line_a_params[selected_type] params = self.line_a_params[selected_type]
line_name = f"线条A-{selected_type}" line_name = f"线条A-{selected_type}"
# 切换为A控件
self._switch_3d_widget("A")
else: else:
selected_type = self.selected_line_b selected_type = self.selected_line_b
params = self.line_b_params[selected_type] params = self.line_b_params[selected_type]
line_name = f"线条B-{selected_type}" line_name = f"线条B-{selected_type}"
# 切换为B控件
self._switch_3d_widget("B")
self.line_select_signal.emit(line_name, *params) self.line_select_signal.emit(line_name, *params)
QMessageBox.information(self, "导入成功", f"已导入{line_name}的参数") QMessageBox.information(self, "导入成功", f"已导入{line_name}的参数\n3D视图已切换为{line_type}类型")
@Slot(str, float, float, float, float) @Slot(str, float, float, float, float)
def _set_line_params(self, line_name, a, b, c, d): def _set_line_params(self, line_name, a, b, c, d):
@ -449,10 +470,10 @@ class LengthMotorController(QMainWindow):
d_str = f"{d:.0f}" # 参数d是整数25/26/28等 d_str = f"{d:.0f}" # 参数d是整数25/26/28等
"""设置参数到下拉框""" """设置参数到下拉框"""
self.combo_a.setCurrentText(str(a)) self.combo_a.setCurrentText(a_str)
self.combo_b.setCurrentText(str(b)) self.combo_b.setCurrentText(b_str)
self.combo_c.setCurrentText(str(c)) self.combo_c.setCurrentText(c_str)
self.combo_d.setCurrentText(str(d)) self.combo_d.setCurrentText(d_str)
@Slot() @Slot()
def _save_settings(self): def _save_settings(self):
@ -482,27 +503,6 @@ class LengthMotorController(QMainWindow):
"time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) "time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}) })
@Slot()
def _test_3d_param_pass(self):
"""测试参数传递"""
try:
param_a = float(self.combo_a.currentText())
param_b = float(self.combo_b.currentText())
param_c = float(self.combo_c.currentText())
param_d = float(self.combo_d.currentText())
except ValueError:
QMessageBox.critical(self, "测试失败", "当前参数格式无效!")
return
QMessageBox.information(
self, "参数传递测试成功",
f"当前参数已准备好传递给3D模块\n"
f"参数a半径{param_a}\n"
f"参数b长度{param_b}\n"
f"参数c颜色通道1{param_c}\n"
f"参数d颜色通道2{param_d}"
)
@Slot() @Slot()
def _clear_inputs(self): def _clear_inputs(self):
"""清空所有选择""" """清空所有选择"""

View File

@ -35,16 +35,21 @@ class Target3DWidgetA(QWidget):
self.ax.set_ylabel('Y') self.ax.set_ylabel('Y')
self.ax.set_zlabel('Z') self.ax.set_zlabel('Z')
# 1. 轴范围X轴设为0-60cmY/Z设为0-5cm # 1. 清空刻度数值(隐藏刻度数字
self.ax.set_xlim(0, 60) self.ax.set_xticklabels([])
self.ax.set_yticklabels([])
self.ax.set_zticklabels([])
# 2. 隐藏刻度线针对3D轴的特殊设置
self.ax.tick_params(axis='x', which='both', length=0) # X轴刻度线长度设为0
self.ax.tick_params(axis='y', which='both', length=0) # Y轴刻度线长度设为0
self.ax.tick_params(axis='z', which='both', length=0) # Z轴刻度线长度设为0
# 轴范围保留保证3D图形显示范围正确
self.ax.set_xlim(0, 120)
self.ax.set_ylim(0, 5) self.ax.set_ylim(0, 5)
self.ax.set_zlim(0, 5) self.ax.set_zlim(0, 5)
# 2. 刻度间隔统一为1cm
self.ax.set_xticks(range(0, 61, 5)) # X轴每5cm标一次刻度避免太密集
self.ax.set_yticks(range(0, 6, 1)) # Y轴每1cm标一次刻度
self.ax.set_zticks(range(0, 6, 1)) # Z轴每1cm标一次刻度
# 调整视角适配X轴1cm刻度 # 调整视角适配X轴1cm刻度
self.ax.view_init(elev=20, azim=60) self.ax.view_init(elev=20, azim=60)
@ -52,27 +57,27 @@ class Target3DWidgetA(QWidget):
"""绘制3D结构""" """绘制3D结构"""
vertices = [ vertices = [
# 底部截面Z=0平面 # 底部截面Z=0平面
(0, 3, 0), (50, 3, 0), (0, 3, 0), (100, 3, 0),
(50, 3, 0), (50, 0, 0), (100, 3, 0), (100, 0, 0),
# 中间截面Z=0.5平面) # 中间截面Z=0.5平面)
(0, 0.5, 0.5), (0, 3, 0.5), (0, 0.5, 0.5), (0, 3, 0.5),
(0, 3, 0.5), (50, 3, 0.5), (0, 3, 0.5), (100, 3, 0.5),
(50, 3, 0.5), (50, 0.5, 0.5), (100, 3, 0.5), (100, 0.5, 0.5),
(50, 0.5, 0.5), (0, 0.5, 0.5), (100, 0.5, 0.5), (0, 0.5, 0.5),
# 顶部界面Z=2平面 # 顶部界面Z=2平面
(0, 0, 2), (0, 0.5, 2), (0, 0, 2), (0, 0.5, 2),
(0, 0.5, 2), (50, 0.5, 2), (0, 0.5, 2), (100, 0.5, 2),
(50, 0.5, 2), (50, 0, 2), (100, 0.5, 2), (100, 0, 2),
(50, 0, 2), (0, 0, 2), (100, 0, 2), (0, 0, 2),
# z方向的连线 # z方向的连线
(0, 3, 0.5), (0, 3, 0), (0, 3, 0.5), (0, 3, 0),
(50, 3, 0.5), (50, 3, 0), (100, 3, 0.5), (100, 3, 0),
(0, 0.5, 2), (0, 0.5, 0.5), (0, 0.5, 2), (0, 0.5, 0.5),
(50, 0.5, 2), (50, 0.5, 0.5), (100, 0.5, 2), (100, 0.5, 0.5),
(50, 0, 2), (50, 0, 0), (100, 0, 2), (100, 0, 0),
] ]
# 绘制所有棱边 # 绘制所有棱边
@ -85,10 +90,10 @@ class Target3DWidgetA(QWidget):
def add_annotations(self): def add_annotations(self):
"""标注位置适配X轴1cm单位""" """标注位置适配X轴1cm单位"""
annotations = [ annotations = [
("a", 50, 0, 1, 'red'), ("a", 100, 0, 1, 'red'),
("b", 50, 1, 0, 'green'), ("b", 100, 1, 0, 'green'),
("c", 50, 3, 0.25, 'blue'), ("c", 100, 3, 0.25, 'blue'),
("d", 25, 2, 0, 'magenta') ("d", 50, 2, 0, 'magenta')
] ]
for text, x, y, z, color in annotations: for text, x, y, z, color in annotations:
self.ax.text(x, y, z, text, fontsize=14, color=color, weight='bold') self.ax.text(x, y, z, text, fontsize=14, color=color, weight='bold')
@ -117,16 +122,21 @@ class Target3DWidgetB(QWidget):
self.ax.set_ylabel('Y') self.ax.set_ylabel('Y')
self.ax.set_zlabel('Z') self.ax.set_zlabel('Z')
# 1. 轴范围X轴设为0-60cmY/Z设为0-5cm # 1. 清空刻度数值(隐藏刻度数字
self.ax.set_xlim(0, 60) self.ax.set_xticklabels([])
self.ax.set_yticklabels([])
self.ax.set_zticklabels([])
# 2. 隐藏刻度线针对3D轴的特殊设置
self.ax.tick_params(axis='x', which='both', length=0) # X轴刻度线长度设为0
self.ax.tick_params(axis='y', which='both', length=0) # Y轴刻度线长度设为0
self.ax.tick_params(axis='z', which='both', length=0) # Z轴刻度线长度设为0
# 轴范围保留保证3D图形显示范围正确
self.ax.set_xlim(0, 120)
self.ax.set_ylim(0, 5) self.ax.set_ylim(0, 5)
self.ax.set_zlim(0, 5) self.ax.set_zlim(0, 5)
# 2. 刻度间隔统一为1cm
self.ax.set_xticks(range(0, 61, 5)) # X轴每5cm标一次刻度避免太密集
self.ax.set_yticks(range(0, 6, 1)) # Y轴每1cm标一次刻度
self.ax.set_zticks(range(0, 6, 1)) # Z轴每1cm标一次刻度
# 调整视角适配X轴1cm刻度 # 调整视角适配X轴1cm刻度
self.ax.view_init(elev=20, azim=60) self.ax.view_init(elev=20, azim=60)
@ -134,34 +144,34 @@ class Target3DWidgetB(QWidget):
"""绘制3D结构""" """绘制3D结构"""
vertices = [ vertices = [
# 底部截面Z=0平面 # 底部截面Z=0平面
(0, 3, 0), (50, 3, 0), (0, 3, 0), (100, 3, 0),
(50, 3, 0), (50, 0, 0), (100, 3, 0), (100, 0, 0),
# 中间截面Z=0.5平面) # 中间截面Z=0.5平面)
(0, 0.5, 0.5), (0, 2.5, 0.5), (0, 0.5, 0.5), (0, 2.5, 0.5),
(50, 2.5, 0.5), (50, 0.5, 0.5), (100, 2.5, 0.5), (100, 0.5, 0.5),
(50, 0.5, 0.5), (0, 0.5, 0.5), (100, 0.5, 0.5), (0, 0.5, 0.5),
# 中间截面Z=1平面 # 中间截面Z=1平面
(0, 2.5, 1),(0, 3, 1), (0, 2.5, 1),(0, 3, 1),
(0, 3, 1), (50, 3, 1), (0, 3, 1), (100, 3, 1),
(50, 3, 1), (50, 2.5, 1), (100, 3, 1), (100, 2.5, 1),
(50, 2.5, 1), (0, 2.5, 1), (100, 2.5, 1), (0, 2.5, 1),
# 顶部界面Z=2平面 # 顶部界面Z=2平面
(0, 0, 2), (0, 0.5, 2), (0, 0, 2), (0, 0.5, 2),
(0, 0.5, 2), (50, 0.5, 2), (0, 0.5, 2), (100, 0.5, 2),
(50, 0.5, 2), (50, 0, 2), (100, 0.5, 2), (100, 0, 2),
(50, 0, 2), (0, 0, 2), (100, 0, 2), (0, 0, 2),
# z方向的连线 # z方向的连线
(0, 3, 1), (0, 3, 0), (0, 3, 1), (0, 3, 0),
(50, 3, 1), (50, 3, 0), (100, 3, 1), (100, 3, 0),
(0, 0.5, 2), (0, 0.5, 0.5), (0, 0.5, 2), (0, 0.5, 0.5),
(50, 0.5, 2), (50, 0.5, 0.5), (100, 0.5, 2), (100, 0.5, 0.5),
(50, 0, 2), (50, 0, 0), (100, 0, 2), (100, 0, 0),
(0, 2.5, 1),(0, 2.5, 0.5), (0, 2.5, 1),(0, 2.5, 0.5),
(50, 2.5, 1),(50, 2.5, 0.5) (100, 2.5, 1),(100, 2.5, 0.5)
] ]
# 绘制所有棱边 # 绘制所有棱边
@ -174,10 +184,10 @@ class Target3DWidgetB(QWidget):
def add_annotations(self): def add_annotations(self):
"""标注位置适配X轴1cm单位""" """标注位置适配X轴1cm单位"""
annotations = [ annotations = [
("a", 50, 0, 1, 'red'), ("a", 100, 0, 1, 'red'),
("b", 50, 1, 0, 'green'), ("b", 100, 1, 0, 'green'),
("c", 50, 3, 0.5, 'blue'), ("c", 100, 3, 0.5, 'blue'),
("d", 25, 2, 0, 'magenta') ("d", 50, 2, 0, 'magenta')
] ]
for text, x, y, z, color in annotations: for text, x, y, z, color in annotations:
self.ax.text(x, y, z, text, fontsize=14, color=color, weight='bold') self.ax.text(x, y, z, text, fontsize=14, color=color, weight='bold')
@ -211,6 +221,6 @@ def ui_3d_b():
# ----------测试接口---------- # ----------测试接口----------
if __name__ == '__main__': if __name__ == '__main__':
ui_3d_a() # ui_3d_a()
# ui_3d_b() ui_3d_b()