def _set_tab(self): tab_widget = QTabWidget() tab_widget.setMaximumHeight(self.main_ui.window.geometry().height() // 2) self.layout.addWidget(tab_widget) toolBox1 = QToolBox() toolBox2 = QToolBox() groupBox1 = QGroupBox() groupBox2 = QGroupBox() toolBox1.addItem(groupBox1, "") toolBox2.addItem(groupBox2, "") tab_widget.addTab(toolBox1, i18n[self.lang]['Angle']) tab_widget.addTab(toolBox2, i18n[self.lang]['Coordinate']) joint_layout = QVBoxLayout(groupBox1) cartesian_layout = QVBoxLayout(groupBox2) self.cartesian_ui = CoordinateUI(self, cartesian_layout) self.axis_ui = AngleUI(self, joint_layout)
def initUI(self): self.container = QToolBox() self.setWidget(self.container) self.b_save_edit = QPushButton("Save current edit") self.b_save_edit.clicked.connect(self._applySaveEdit) self.container.addItem(OptionBox([self.b_save_edit]), "Save") self.tf_importance_point_count = QLineEdit() self.tf_importance_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_importance_point_count.setValidator( QIntValidator(1, 1000000, self)) self.b_importance_apply = QPushButton("Apply Sampling") self.b_importance_apply.clicked.connect( self._applyFaceImportanceSampling) self.container.addItem( OptionBox( [self.tf_importance_point_count, self.b_importance_apply]), "Importance Sampling (Mesh)") self.tf_poisson_point_count = QLineEdit() self.tf_poisson_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_poisson_point_count.setValidator( QIntValidator(1, 1000000, self)) self.tf_poisson_radius = QLineEdit() self.tf_poisson_radius.setPlaceholderText( "Radius of the poisson disks") self.tf_poisson_radius.setValidator(QDoubleValidator( 0.0, 2.0, 5, self)) self.b_poisson_apply = QPushButton("Apply Sampling") self.b_poisson_apply.clicked.connect(self._applyPoissonDiskSampling) self.container.addItem( OptionBox([ self.tf_poisson_point_count, self.tf_poisson_radius, self.b_poisson_apply ]), "Poisson Sampling (Mesh)") self.tf_montecarlo_point_count = QLineEdit() self.tf_montecarlo_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_montecarlo_point_count.setValidator( QIntValidator(1, 1000000, self)) self.b_montecarlo_apply = QPushButton("Apply Sampling") self.b_montecarlo_apply.clicked.connect(self._applyMontecarloSampling) self.container.addItem( OptionBox( [self.tf_montecarlo_point_count, self.b_montecarlo_apply]), "Montecarlo Sampling (Mesh)") self.tf_centroid_count = QLineEdit() self.tf_centroid_count.setPlaceholderText("Centroid Count") self.tf_centroid_count.setValidator(QIntValidator(1, 1000000, self)) self.b_show_centroids = QPushButton("Apply FPS") self.b_show_centroids.clicked.connect(self._applyFPS) self.container.addItem( OptionBox([self.tf_centroid_count, self.b_show_centroids]), "FPS Sampling (Point)") self.tf_show_pp2_centroids = QLineEdit() self.tf_show_pp2_centroids.setPlaceholderText("Number of Centroids") self.tf_show_pp2_centroids.setValidator(QIntValidator( 1, 1000000, self)) self.tf_show_pp2_centroids_with_ball = QLineEdit() self.tf_show_pp2_centroids_with_ball.setPlaceholderText( "Number of Centroids with plotted radius") self.tf_show_pp2_centroids_with_ball.setValidator( QIntValidator(1, 1000000, self)) self.tf_show_pp2_radius = QLineEdit() self.tf_show_pp2_radius.setPlaceholderText( "Radius of the neighbour area") self.tf_show_pp2_radius.setValidator( QDoubleValidator(0.0, 2.0, 5, self)) self.b_show_pp2_step = QPushButton("Apply Sampling") self.b_show_pp2_step.clicked.connect(self._show_pp2_step) self.container.addItem( OptionBox([ self.tf_show_pp2_centroids, self.tf_show_pp2_centroids_with_ball, self.tf_show_pp2_radius, self.b_show_pp2_step ]), "Show PointNet2 sampling")
class OperationsPanel(QScrollArea): def __init__(self): super().__init__() self.app_state = AppState() self.setWidgetResizable(True) self.initUI() def initUI(self): self.container = QToolBox() self.setWidget(self.container) self.b_save_edit = QPushButton("Save current edit") self.b_save_edit.clicked.connect(self._applySaveEdit) self.container.addItem(OptionBox([self.b_save_edit]), "Save") self.tf_importance_point_count = QLineEdit() self.tf_importance_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_importance_point_count.setValidator( QIntValidator(1, 1000000, self)) self.b_importance_apply = QPushButton("Apply Sampling") self.b_importance_apply.clicked.connect( self._applyFaceImportanceSampling) self.container.addItem( OptionBox( [self.tf_importance_point_count, self.b_importance_apply]), "Importance Sampling (Mesh)") self.tf_poisson_point_count = QLineEdit() self.tf_poisson_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_poisson_point_count.setValidator( QIntValidator(1, 1000000, self)) self.tf_poisson_radius = QLineEdit() self.tf_poisson_radius.setPlaceholderText( "Radius of the poisson disks") self.tf_poisson_radius.setValidator(QDoubleValidator( 0.0, 2.0, 5, self)) self.b_poisson_apply = QPushButton("Apply Sampling") self.b_poisson_apply.clicked.connect(self._applyPoissonDiskSampling) self.container.addItem( OptionBox([ self.tf_poisson_point_count, self.tf_poisson_radius, self.b_poisson_apply ]), "Poisson Sampling (Mesh)") self.tf_montecarlo_point_count = QLineEdit() self.tf_montecarlo_point_count.setPlaceholderText( "Number of Points to Sample") self.tf_montecarlo_point_count.setValidator( QIntValidator(1, 1000000, self)) self.b_montecarlo_apply = QPushButton("Apply Sampling") self.b_montecarlo_apply.clicked.connect(self._applyMontecarloSampling) self.container.addItem( OptionBox( [self.tf_montecarlo_point_count, self.b_montecarlo_apply]), "Montecarlo Sampling (Mesh)") self.tf_centroid_count = QLineEdit() self.tf_centroid_count.setPlaceholderText("Centroid Count") self.tf_centroid_count.setValidator(QIntValidator(1, 1000000, self)) self.b_show_centroids = QPushButton("Apply FPS") self.b_show_centroids.clicked.connect(self._applyFPS) self.container.addItem( OptionBox([self.tf_centroid_count, self.b_show_centroids]), "FPS Sampling (Point)") self.tf_show_pp2_centroids = QLineEdit() self.tf_show_pp2_centroids.setPlaceholderText("Number of Centroids") self.tf_show_pp2_centroids.setValidator(QIntValidator( 1, 1000000, self)) self.tf_show_pp2_centroids_with_ball = QLineEdit() self.tf_show_pp2_centroids_with_ball.setPlaceholderText( "Number of Centroids with plotted radius") self.tf_show_pp2_centroids_with_ball.setValidator( QIntValidator(1, 1000000, self)) self.tf_show_pp2_radius = QLineEdit() self.tf_show_pp2_radius.setPlaceholderText( "Radius of the neighbour area") self.tf_show_pp2_radius.setValidator( QDoubleValidator(0.0, 2.0, 5, self)) self.b_show_pp2_step = QPushButton("Apply Sampling") self.b_show_pp2_step.clicked.connect(self._show_pp2_step) self.container.addItem( OptionBox([ self.tf_show_pp2_centroids, self.tf_show_pp2_centroids_with_ball, self.tf_show_pp2_radius, self.b_show_pp2_step ]), "Show PointNet2 sampling") #Event Handlers def _applyFPS(self, event): item = self.app_state.current_item if (item is None or item.data is None): return if (item.type != item.TypePointCloud): self.app_state.showError( 'This operation can only be applyed to a Point Clouds.') return if (len(self.tf_centroid_count.text()) <= 0): return count = int(self.tf_centroid_count.text()) rateo = count / item.data.pos.size(0) if (rateo >= 1): self.app_state.showError( 'The number of point to select with FPS({}) have to be less than the total number of points({}).' .format(count, item.data.pos.size(0))) return item.data.pos = item.data.pos.double() idx = FPS(item.data.pos, torch.zeros(item.data.pos.size(0)).long(), rateo) new_item = Item(name='{}-FPS'.format(item.name), data=Data(pos=item.data.pos[idx]), type=Item.TypePointCloud) self.app_state.setNewItem(new_item) def _applyPoissonDiskSampling(self, event): item = self.app_state.current_item if (item is None or item.data is None): return if (item.type != item.TypeMesh): self.app_state.showError( 'This operation can only be applyed to be a Mesh object.') return if (len(self.tf_poisson_point_count.text()) <= 0 or len(self.tf_poisson_radius.text()) <= 0): return count = int(self.tf_poisson_point_count.text()) radio = float(self.tf_poisson_radius.text()) item.data.pos = item.data.pos.float() new_data = PoissonDiskSampling(count, radio)(item.data) new_item = Item(name='{}-Poisson'.format(item.name), data=new_data, type=Item.TypePointCloud) self.app_state.setNewItem(new_item) def _applyMontecarloSampling(self, event): item = self.app_state.current_item if (item is None or item.data is None): return if (item.type != item.TypeMesh): self.app_state.showError( 'This operation can only be applyed to be a Mesh object.') return if (len(self.tf_montecarlo_point_count.text()) <= 0): return count = int(self.tf_montecarlo_point_count.text()) item.data.pos = item.data.pos.float() new_data = MontecarloSampling(count)(item.data) new_item = Item(name='{}-Montecarlo'.format(item.name), data=new_data, type=Item.TypePointCloud) self.app_state.setNewItem(new_item) def _applyFaceImportanceSampling(self, event): item = self.app_state.current_item if (item is None or item.data is None): return if (item.type != item.TypeMesh): self.app_state.showError( 'This operation can only be applyed to be a Mesh object.') return if (len(self.tf_importance_point_count.text()) <= 0): return count = int(self.tf_importance_point_count.text()) item.data.pos = item.data.pos.float() new_data = SamplePoints(count)(item.data) new_item = Item(name='{}-ImportanceSamped'.format(item.name), data=new_data, type=Item.TypePointCloud) self.app_state.setNewItem(new_item) def _show_pp2_step(self, event): item = self.app_state.current_item if (item is None or item.data is None): return if (item.type != item.TypePointCloud): self.app_state.showError( 'This operation can only be applyed to a Point Clouds.') return if (len(self.tf_show_pp2_centroids.text()) <= 0 or len(self.tf_show_pp2_centroids_with_ball.text()) <= 0): return count = int(self.tf_show_pp2_centroids.text()) rateo = count / item.data.pos.size(0) if (rateo >= 1): self.app_state.showError( 'The number of point to select with FPS({}) have to be less than the total number of points({}).' .format(count, item.data.pos.size(0))) return with_ball = int(self.tf_show_pp2_centroids_with_ball.text()) if (with_ball > count): self.app_state.showError( 'The number of point for which plot the radius({}), have to be less than the sampled ammount({}).' .format(with_ball, count)) return item.data.pos = item.data.pos.double() idx = FPS(item.data.pos, torch.zeros(item.data.pos.size(0)).long(), rateo) radius = 0.0 if len(self.tf_show_pp2_radius.text()) <=0 \ else float(self.tf_show_pp2_radius.text()) render = CenterAndRadious(item.data.pos.size(0), centroids_idx=idx, centroid_to_draw=with_ball, radius=radius) self.app_state.setNewItem(item, render) def _applySaveEdit(self, event): sourceItem = self.app_state.current_item item = Item(data=sourceItem.data, name=sourceItem.name, type=sourceItem.type) self.app_state.addItem(item)
def __init__(self, main): super(PackageWidgetM, self).__init__() self.setObjectName("PackageWidgetM") self.main_win = main self.game_index = 0 self.selected = [ ] # 已选中的channel及所属game列表 如:[{"game": 当前game字典, "channel": 当前channel字典}, {}, {}] self.selected_name = [ ] # 已选中的渠道显示名称列表 如:["10878922-765321", "", "", ""] self.lbps = {} # 打包信息及进度条组合字典 {"765321": {}, "": {}} self.progress = None self.monitor = PackageMonitor() self.monitor.signal.connect(self.complete) v_layout = QVBoxLayout() h_layout1 = QHBoxLayout() # 全部游戏及其下的渠道列表 self.tool_box = QToolBox(self) self.tool_box.setFixedWidth(100) for game in self.main_win.games: clv = QListView() clv.setEditTriggers(QAbstractItemView.NoEditTriggers) clv.setContextMenuPolicy(Qt.CustomContextMenu) self.tool_box.addItem(clv, game['id']) self.tool_box.currentChanged.connect(self.select_game) self.tool_box.setCurrentIndex(self.game_index) channel_list_area = QScrollArea() channel_list_area.setWidget(self.tool_box) h_layout1.addWidget(channel_list_area, 1) # 已选择的渠道列表 self.cslv_model = QStringListModel() self.cslv_model.setStringList([]) self.cslv = QListView() self.cslv.setModel(self.cslv_model) self.cslv.setEditTriggers(QAbstractItemView.NoEditTriggers) self.cslv.doubleClicked.connect(self.delete_channel) self.cslv.setContextMenuPolicy(Qt.CustomContextMenu) h_layout1.addWidget(self.cslv, 2) # 打包进度条显示列表 self.qpb_list_widget = QListWidget() self.qpb_list_widget.setSelectionMode( QAbstractItemView.SingleSelection) self.qpb_list_widget.itemDoubleClicked.connect(self.select_qpb_list) h_layout1.addWidget(self.qpb_list_widget, 5) v_layout.addLayout(h_layout1) h_layout2 = QHBoxLayout() self.back_btn = QPushButton("返 回") self.back_btn.setFixedWidth(100) self.back_btn.clicked.connect(self.back) h_layout2.addWidget(self.back_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) h_layout2.addSpacing(100) select_apk_btn = QPushButton("选择母包:") select_apk_btn.setFixedWidth(100) select_apk_btn.clicked.connect(self.select_apk) h_layout2.addWidget(select_apk_btn) self.apk_path = QLabel() self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) h_layout2.addWidget(self.apk_path) h_layout2.addSpacing(100) self.pack_btn = QPushButton("打 包") self.pack_btn.setFixedWidth(100) self.pack_btn.clicked.connect(self.click) h_layout2.addWidget(self.pack_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout.addLayout(h_layout2) self.setLayout(v_layout)
class PackageWidgetM(QWidget): def __init__(self, main): super(PackageWidgetM, self).__init__() self.setObjectName("PackageWidgetM") self.main_win = main self.game_index = 0 self.selected = [ ] # 已选中的channel及所属game列表 如:[{"game": 当前game字典, "channel": 当前channel字典}, {}, {}] self.selected_name = [ ] # 已选中的渠道显示名称列表 如:["10878922-765321", "", "", ""] self.lbps = {} # 打包信息及进度条组合字典 {"765321": {}, "": {}} self.progress = None self.monitor = PackageMonitor() self.monitor.signal.connect(self.complete) v_layout = QVBoxLayout() h_layout1 = QHBoxLayout() # 全部游戏及其下的渠道列表 self.tool_box = QToolBox(self) self.tool_box.setFixedWidth(100) for game in self.main_win.games: clv = QListView() clv.setEditTriggers(QAbstractItemView.NoEditTriggers) clv.setContextMenuPolicy(Qt.CustomContextMenu) self.tool_box.addItem(clv, game['id']) self.tool_box.currentChanged.connect(self.select_game) self.tool_box.setCurrentIndex(self.game_index) channel_list_area = QScrollArea() channel_list_area.setWidget(self.tool_box) h_layout1.addWidget(channel_list_area, 1) # 已选择的渠道列表 self.cslv_model = QStringListModel() self.cslv_model.setStringList([]) self.cslv = QListView() self.cslv.setModel(self.cslv_model) self.cslv.setEditTriggers(QAbstractItemView.NoEditTriggers) self.cslv.doubleClicked.connect(self.delete_channel) self.cslv.setContextMenuPolicy(Qt.CustomContextMenu) h_layout1.addWidget(self.cslv, 2) # 打包进度条显示列表 self.qpb_list_widget = QListWidget() self.qpb_list_widget.setSelectionMode( QAbstractItemView.SingleSelection) self.qpb_list_widget.itemDoubleClicked.connect(self.select_qpb_list) h_layout1.addWidget(self.qpb_list_widget, 5) v_layout.addLayout(h_layout1) h_layout2 = QHBoxLayout() self.back_btn = QPushButton("返 回") self.back_btn.setFixedWidth(100) self.back_btn.clicked.connect(self.back) h_layout2.addWidget(self.back_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) h_layout2.addSpacing(100) select_apk_btn = QPushButton("选择母包:") select_apk_btn.setFixedWidth(100) select_apk_btn.clicked.connect(self.select_apk) h_layout2.addWidget(select_apk_btn) self.apk_path = QLabel() self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) h_layout2.addWidget(self.apk_path) h_layout2.addSpacing(100) self.pack_btn = QPushButton("打 包") self.pack_btn.setFixedWidth(100) self.pack_btn.clicked.connect(self.click) h_layout2.addWidget(self.pack_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout.addLayout(h_layout2) self.setLayout(v_layout) def select_game(self, p_int): self.game_index = p_int if 'apk' in self.main_win.games[self.game_index]: self.apk_path.setText(self.main_win.games[self.game_index]['apk']) else: self.apk_path.setText("<h3><font color=%s>%s</font></h3>" % ('red', "请浏览选择本地母包路径")) # self.apk_path.setText("请浏览选择本地母包路径") # 当前选中的game,其下的所有渠道列表 self.channels = Utils.get_channels( Utils.get_full_path('games/' + self.main_win.games[p_int]['id'] + '/config.xml')) channel_ids = [] for channel in self.channels: channel_ids.append(channel['channelId']) list_model = QStringListModel() list_model.setStringList(channel_ids) self.clv = self.tool_box.currentWidget() self.clv.doubleClicked.connect(self.select_channel) self.clv.setModel(list_model) # 双击选中当前渠道,更新已选中列表的model def select_channel(self): if 'apk' not in self.main_win.games[self.game_index]: QMessageBox.warning(self, "警告", "请先添加母包!") return channel = self.channels[self.clv.currentIndex().row()] name = self.main_win.games[ self.game_index]['id'] + '-' + channel['channelId'] if name in self.selected_name: return self.selected_name.append(name) self.cslv_model.setStringList(self.selected_name) package = { 'game': self.main_win.games[self.game_index], 'channel': channel } self.selected.append(package) # 双击移除当前渠道,更新已选中列表的model def delete_channel(self): name = self.selected_name[self.cslv.currentIndex().row()] self.selected_name.remove(name) self.cslv_model.setStringList(self.selected_name) package = self.selected[self.cslv.currentIndex().row()] self.selected.remove(package) def select_qpb_list(self): index = self.qpb_list_widget.currentIndex().row() game_id = self.selected[index]['game']['id'] channel_id = self.selected[index]['channel']['channelId'] success = self.lbps[channel_id]['success'] dest_apk_dir = Utils.get_full_path('output/' + game_id + '/' + channel_id) if success: os.startfile(dest_apk_dir) else: QMessageBox.warning(self, "警告", "打包成功了吗?") def back(self): self.monitor.deleteLater() self.main_win.set_main_widget() def select_apk(self): f_name = QFileDialog.getOpenFileName( self, '选择母包', os.path.join(os.path.expanduser('~'), "Desktop"), ("Apk (*.apk)")) if f_name[0]: self.apk_path.setStyleSheet("font-size:12px") self.apk_path.setText(f_name[0]) self.main_win.games[self.game_index]['apk'] = f_name[0] def click(self): if self.pack_btn.text() == "打 包": self.package() elif self.pack_btn.text() == "取 消": self.cancel() def package(self): # 清空上次打包完成后的进度条显示列表 count = self.qpb_list_widget.count() if count > 0: for i in range(count): item = self.qpb_list_widget.takeItem(0) del item if len(self.selected) <= 0: QMessageBox.warning(self, "警告", "请选择需要打包的渠道!") return for package in self.selected: # {"success": 是否成功, "label": 进度条文本view, "qpb": 进度条view, "runnable": 打包任务} lbp = {'success': False} self.set_qpb_list_item(package['channel']['channelId'], lbp) runnable = PackRunnable(package['game'], package['channel'], package['game']['apk']) runnable.signal.signal.connect(self.set_value) self.monitor.add_runnable(runnable) lbp['runnable'] = runnable self.lbps[package['channel']['channelId']] = lbp # 开启监听线程 self.monitor.start() # 开始打包,不可返回,返回按钮禁用;设置打包按钮文本为"取 消" self.back_btn.setDisabled(True) self.pack_btn.setText("取 消") def set_qpb_list_item(self, channel_id, lbp): item = QListWidgetItem(self.qpb_list_widget) item.setSizeHint(QSize(400, 80)) widget = QWidget(self.qpb_list_widget) v_layout = QVBoxLayout() label = QLabel(channel_id + "==>>>等待出包...") v_layout.addWidget(label) lbp['label'] = label qpb = QProgressBar(self.qpb_list_widget) v_layout.addWidget(qpb) lbp['qpb'] = qpb widget.setLayout(v_layout) self.qpb_list_widget.addItem(item) self.qpb_list_widget.setItemWidget(item, widget) def set_value(self, channel_id, result, msg, step): lbp = self.lbps[channel_id] if result: # 打包步骤异常,提示异常,关闭进度条 lbp['label'].setText(channel_id + "==>>>" + msg) lbp['qpb'].close() if step == 100: lbp['success'] = True self.lbps[channel_id] = lbp else: # 打包正常,设置进度条进度 lbp['qpb'].setValue(step) if step == 0: lbp['label'].setText(channel_id + "==>>>" + msg) # 取消打包(全部取消) def cancel(self): self.progress = QProgressDialog(self) self.progress.setFixedWidth(500) self.progress.setFixedHeight(80) self.progress.setWindowTitle("正在取消,请稍等...") self.progress.setCancelButtonText("取消") self.progress.setMinimumDuration(1) self.progress.setWindowModality(Qt.ApplicationModal) self.progress.setRange(0, 0) self.progress.show() # 清空进度条显示列表 count = self.qpb_list_widget.count() for i in range(count): item = self.qpb_list_widget.takeItem(0) del item # 清空任务线程池;线程池清空后,会触发监听线程的完成信号,重置返回和打包按钮 # 因为打包任务调用外部程序,并不能立即终止外部程序连接,所以清空过程有延迟 for channel_id in self.lbps: self.lbps[channel_id]['runnable'].is_close = True self.monitor.clear() # 取消打包(清空任务完成),或打包完成, def complete(self): if self.progress is not None: self.progress.cancel() # 返回按钮解禁;设置打包按钮文本为"打 包" self.back_btn.setDisabled(False) self.pack_btn.setText("打 包")
def _set_tab(self): self.tab_widget = QTabWidget() # self.tab_widget.currentChanged.connect(self.switch_tab) # tab_widget.setMaximumHeight(self.window.geometry().height() // 2) self.main_layout.addWidget(self.tab_widget) toolbox1 = QToolBox() toolbox2 = QToolBox() toolbox3 = QToolBox() toolbox4 = QToolBox() toolbox5 = QToolBox() groupbox1 = QGroupBox() groupbox2 = QGroupBox() groupbox3 = QGroupBox() groupbox4 = QGroupBox() groupbox5 = QGroupBox() toolbox1.addItem(groupbox1, "") toolbox2.addItem(groupbox2, "") toolbox3.addItem(groupbox3, "") toolbox4.addItem(groupbox4, "") toolbox5.addItem(groupbox5, "") self.tab_widget.addTab(toolbox1, "uArm") self.tab_widget.addTab(toolbox2, "xArm") self.tab_widget.addTab(toolbox3, "OpenMV") self.tab_widget.addTab(toolbox4, "Gcode") self.tab_widget.addTab(toolbox5, "WebView") uarm_layout = QVBoxLayout(groupbox1) xarm_layout = QVBoxLayout(groupbox2) openmv_layout = QHBoxLayout(groupbox3) gcode_layout = QVBoxLayout(groupbox4) webview_layout = QVBoxLayout(groupbox5) self.uarm_ui = UArmUI(self, uarm_layout) self.xarm_ui = XArmUI(self, xarm_layout) self.openmv_ui = OpenMV_UI(self, openmv_layout) self.gcode_ui = GcodeUI(self, gcode_layout) self.webview_ui = WebViewUI(self, webview_layout) self.tab_widget.setCurrentIndex(0)