def __init__(self, parent=None, show=True, off_screen=True): MainWindow.__init__(self, parent) self.frame = QFrame() vlayout = QVBoxLayout() self.vtk_widget = QtInteractor( parent=self.frame, off_screen=off_screen, stereo=False, ) vlayout.addWidget(self.vtk_widget.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) mainMenu = _create_menu_bar(parent=self) fileMenu = mainMenu.addMenu('File') self.exit_action = QAction('Exit', self) self.exit_action.setShortcut('Ctrl+Q') self.exit_action.triggered.connect(self.close) fileMenu.addAction(self.exit_action) meshMenu = mainMenu.addMenu('Mesh') self.add_sphere_action = QAction('Add Sphere', self) self.exit_action.setShortcut('Ctrl+A') self.add_sphere_action.triggered.connect(self.add_sphere) meshMenu.addAction(self.add_sphere_action) self.signal_close.connect(self.vtk_widget.close) if show: self.show()
def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu to demo functions mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # allow adding a sphere meshMenu = mainMenu.addMenu('Mesh') self.add_sphere_action = Qt.QAction('Add Sphere', self) self.add_sphere_action.triggered.connect(self.add_sphere) meshMenu.addAction(self.add_sphere_action) if show: self.show()
class Bp3DWidget(QWidget): def __init__(self): super(Bp3DWidget, self).__init__() self.frame = QFrame() layout = QVBoxLayout() colormap = ColorMap(*zip(*Gradients["bipolar"]["ticks"])).getLookupTable() / 255.0 self.colormap = ListedColormap(colormap) self.plotter = QtInteractor(self.frame) layout.addWidget(self.plotter.interactor) self.frame.setLayout(layout) self.setLayout(layout) @pyqtSlot() def on_data_changed(self): sender = self.sender() grid = Bp3DWidget.__create_grid(sender.data) self.plotter.clear() self.plotter.add_mesh(grid, scalars=np.rot90(sender.data)[::-1], cmap=self.colormap) @staticmethod def __create_grid(data): shape = np.shape(data) # M.B. plot add to params az_grid = np.deg2rad(np.linspace(-60, 60, shape[1])) el_grid = np.deg2rad(np.linspace(-60, 60, shape[0])) az_mesh, el_mesh = np.meshgrid(az_grid, el_grid) r_mesh = data x_mesh = np.cos(az_mesh) * np.cos(el_mesh) * r_mesh y_mesh = np.sin(az_mesh) * np.cos(el_mesh) * r_mesh z_mesh = np.sin(el_mesh) * r_mesh grid = pv.StructuredGrid(x_mesh, y_mesh, z_mesh) return grid
def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # create cubic skeleton self.cubic_skeleton_action = Qt.QAction('Cubic Skeleton', self) self.cubic_skeleton_action.triggered.connect(self.cubic_skeleton) editMenu.addAction(self.cubic_skeleton_action) # Create voxelized model self.create_voxelized_model = Qt.QAction('Voxelize Model', self) self.create_voxelized_model.triggered.connect(self.voxelize_model) editMenu.addAction(self.create_voxelized_model) # split mesh based on max cube faces # self.max_cube_slice_action = Qt.QAction('Slice27', self) # self.cubic_skeleton_action.triggered.connect(self.max_cube_slice) # editMenu.addAction(self.max_cube_slice_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, color=None, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box=None, box_args=None)
def __init__(self): super(Bp3DWidget, self).__init__() self.frame = QFrame() layout = QVBoxLayout() colormap = ColorMap(*zip(*Gradients["bipolar"]["ticks"])).getLookupTable() / 255.0 self.colormap = ListedColormap(colormap) self.plotter = QtInteractor(self.frame) layout.addWidget(self.plotter.interactor) self.frame.setLayout(layout) self.setLayout(layout)
def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # set centroid self.skewed_centroid_action = Qt.QAction('Skewed Centroid', self) self.skewed_centroid_action.triggered.connect(self.skewed_centroid_check) fileMenu.addAction(self.skewed_centroid_action) # save screenshot self.save_screenshot_action = Qt.QAction('Save Screenshot', self) self.save_screenshot_action.triggered.connect(self.save_screenshot) fileMenu.addAction(self.save_screenshot_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # Show max cube & raytracing process self.max_cube_action = Qt.QAction('Max Cube', self) self.max_cube_action.triggered.connect(self.show_max_cube) editMenu.addAction(self.max_cube_action) # Show max cube & raytracing process self.max_cuboid_action = Qt.QAction('Max Cuboid', self) self.max_cuboid_action.triggered.connect(self.max_cuboid) editMenu.addAction(self.max_cuboid_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box= None, box_args=None)
def load_and_prep_query_mesh(self, data): if not data: return self.load_button.setFont(QtGui.QFont("arial", 10)) # Normalize query mesh normed_data = self.normalizer.mono_run_pipeline(data) normed_mesh = pv.PolyData(normed_data["history"][-1]["data"]["vertices"], normed_data["history"][-1]["data"]["faces"]) normed_data['poly_data'] = normed_mesh # Extract features n_singletons, n_distributionals, mapping_of_labels = get_sizes_features(features_file=self.config_data["FEATURE_DATA_FILE"],with_labels=True, drop_feat=["timestamp"]) mapping_of_labels_reversed = {val: key for key, val in mapping_of_labels.items()} features_dict = FeatureExtractor.mono_run_pipeline_old(normed_data) features_dict_carefully_selected = OrderedDict( sorted({mapping_of_labels.get(key): val for key, val in features_dict.items() if key in mapping_of_labels}.items(), key=lambda t: t[0])) features_df = pd.DataFrame([features_dict_carefully_selected]).T.reset_index() self.hist_labels = [val for key, val in mapping_of_labels.items() if "hist_" in key] self.skeleton_labels = [val for key, val in mapping_of_labels.items() if "skeleton_" in key] # feature_formatted_keys = sing_labels + dist_labels # features_df = pd.DataFrame({'key': list(feature_formatted_keys), 'value': list( # [list(f) if isinstance(f, np.ndarray) else f for f in list(features_dict.values())[3:]])}) # Update plotter & feature table # since unfortunately Qtinteractor which plots the mesh cannot be updated (remove and add new mesh) # it needs to be removed and newly generated each time a mesh gets loaded self.tableWidget.deleteLater() self.vlayout.removeWidget(self.QTIplotter) self.QTIplotter = QtInteractor(self.frame) self.vlayout.addWidget(self.QTIplotter) self.tableWidget = TableWidget(features_dict_carefully_selected, self, mapping_of_labels_reversed) self.tableWidget.show() self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) self.vlayout.addWidget(self.tableWidget) self.QTIplotter.add_mesh(normed_mesh, show_edges=True) self.QTIplotter.isometric_view() self.QTIplotter.show_bounds(grid='front', location='outer', all_edges=True) self.vlayout.addWidget(self.graphWidget) # Compare shapes if self.smlw: self.smlw.deleteLater() if len(self.smlw.smw_list) != 0: self.smlw.smw_list[0].deleteLater() self.smlw = SimilarMeshesListWindow(features_dict) self.buttons = self.tableWidget.get_buttons_in_table() self.hist_dict = features_df.set_index("index").tail(n=len(self.hist_labels)).to_dict() for key, value in self.buttons.items(): value.clicked.connect(lambda state, x=key, y=features_dict_carefully_selected[key]: self.plot_selected_hist(x, y)) self.smlw.show()
def __init__(self, mesh, features): super().__init__() with open('config.json') as f: self.config_data = json.load(f) self.setWindowTitle('Similar Mesh Window') self.mesh = mesh self.mesh_features = features self.QTIplotter = QtInteractor() self.vlayout = Qt.QVBoxLayout() self.setLayout(self.vlayout) # Create and add widgets to layout n_singletons, n_distributionals, mapping_of_labels = get_sizes_features(features_file=self.config_data["FEATURE_DATA_FILE"],with_labels=True, drop_feat=["timestamp"]) mapping_of_labels_reversed = {val: key for key, val in mapping_of_labels.items()} features_dict_carefully_selected = OrderedDict( sorted({mapping_of_labels.get(key): val for key, val in self.mesh_features.items() if key in mapping_of_labels}.items(), key=lambda t: t[0])) features_df = pd.DataFrame([features_dict_carefully_selected]).T.reset_index() self.hist_labels = [val for key, val in mapping_of_labels.items() if "hist_" in key] # Create Table widget self.tableWidget = TableWidget(features_dict_carefully_selected, self, mapping_of_labels_reversed) self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) # Create Plots widget self.graphWidget = pg.PlotWidget() self.graphWidget.setBackground('w') self.hist_dict = features_df.set_index("index").tail(n=len(self.hist_labels)).to_dict() self.buttons = self.tableWidget.get_buttons_in_table() for key, value in self.buttons.items(): value.clicked.connect(lambda state, x=key, y=features_dict_carefully_selected[key]: self.plot_selected_hist(x, y)) self.vlayout.addWidget(self.QTIplotter.interactor) self.vlayout.addWidget(self.tableWidget) self.vlayout.addWidget(self.graphWidget) # Position SimilarMeshWindow screen_topleft = QDesktopWidget().availableGeometry().topLeft() screen_height = QDesktopWidget().availableGeometry().height() width = (QDesktopWidget().availableGeometry().width() * 0.4) self.move((QDesktopWidget().availableGeometry().width() * 0.4) + ((QDesktopWidget().availableGeometry().width() * 0.2)), 0) self.resize(width, screen_height - 50) # Set widgets self.QTIplotter.add_mesh(self.mesh, show_edges=True) self.QTIplotter.isometric_view() self.QTIplotter.show_bounds(grid='front', location='outer', all_edges=True)
def __init__(self, parent): super().__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.plotter = QtInteractor(self, multi_samples=8, line_smoothing=True, point_smoothing=True) layout.addWidget(self.plotter.interactor) self.plotter.enable_anti_aliasing() for key in ["Up", "Down"]: self.plotter.clear_events_for_key(key) self.clear() self.setLayout(layout)
def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) #Create secondary cubes self.cubic_skeleton_action = Qt.QAction('Cubic Skeleton', self) self.cubic_skeleton_action.triggered.connect(self.cubic_skeleton) editMenu.addAction(self.cubic_skeleton_action) if show: self.show()
class TstWindow(MainWindow): def __init__(self, parent=None, show=True, off_screen=True): MainWindow.__init__(self, parent) self.frame = QFrame() vlayout = QVBoxLayout() self.vtk_widget = QtInteractor(parent=self.frame, off_screen=off_screen) vlayout.addWidget(self.vtk_widget.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) mainMenu = _create_menu_bar(parent=self) fileMenu = mainMenu.addMenu('File') self.exit_action = QAction('Exit', self) self.exit_action.setShortcut('Ctrl+Q') self.exit_action.triggered.connect(self.close) fileMenu.addAction(self.exit_action) meshMenu = mainMenu.addMenu('Mesh') self.add_sphere_action = QAction('Add Sphere', self) self.exit_action.setShortcut('Ctrl+A') self.add_sphere_action.triggered.connect(self.add_sphere) meshMenu.addAction(self.add_sphere_action) self.signal_close.connect(self.vtk_widget.close) if show: self.show() def add_sphere(self): sphere = pyvista.Sphere(phi_resolution=6, theta_resolution=6) self.vtk_widget.add_mesh(sphere) self.vtk_widget.reset_camera()
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # set centroid self.skewed_centroid_action = Qt.QAction('Skewed Centroid', self) self.skewed_centroid_action.triggered.connect(self.skewed_centroid_check) fileMenu.addAction(self.skewed_centroid_action) # save screenshot self.save_screenshot_action = Qt.QAction('Save Screenshot', self) self.save_screenshot_action.triggered.connect(self.save_screenshot) fileMenu.addAction(self.save_screenshot_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # Show max cube & raytracing process self.max_cube_action = Qt.QAction('Max Cube', self) self.max_cube_action.triggered.connect(self.show_max_cube) editMenu.addAction(self.max_cube_action) # Show max cube & raytracing process self.max_cuboid_action = Qt.QAction('Max Cuboid', self) self.max_cuboid_action.triggered.connect(self.max_cuboid) editMenu.addAction(self.max_cuboid_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box= None, box_args=None) def open_mesh(self): """ add a mesh to the pyqt frame """ global int_surface, ext_surface, mesh_vol, mesh global x_range, y_range, z_range, Vol_centroid global open_mesh_run global mesh_name # track pre-processing starting time open_mesh_start = time.time() # open file file_info = QtWidgets.QFileDialog.getOpenFileName() file_path = file_info[0] # determine file type and if conversion needed _, file_name = os.path.split(file_path) mesh_name, mesh_type = os.path.splitext(file_name) # read mesh & transform according to principal axes print(file_path) pre = trimesh.load(file_path) orient = pre.principal_inertia_transform pre = pre.apply_transform(orient) post_file_path = 'data/'+ mesh_name + '_oriented.stl' pre.export(post_file_path) ext_surface = pv.read(post_file_path) # scale meshes accordingly if mesh_name == 'elephant': ext_surface.points *= 12 # Elephant elif mesh_name == 'Bracket S24D1': ext_surface.points /= 10 # Bracket elif mesh_name == 'knight': ext_surface.points /= 2 # Knight # create internal offset thickness = 0.1 # inches grid = pv.create_grid(ext_surface).triangulate() solid = grid.clip_surface(ext_surface) solid.compute_implicit_distance(ext_surface, inplace=True) imp_dis_max = max(solid['implicit_distance']) shell_threshold = imp_dis_max - thickness shell = solid.clip_scalar('implicit_distance', value = shell_threshold) int_surface = shell.extract_geometry() meshfix = mf.MeshFix(int_surface) meshfix.repair(verbose=True) mesh = solid.clip_surface(int_surface, invert=False) # print mesh info print("Mesh Name:", mesh_name) print("Mesh Type:", mesh_type[1:]) # find mesh centroid and translate the mesh so that's the origin Vol_centroid = np.array([0,0,0]) self.skewed_centroid_action.setCheckable(True) # reset plotter self.reset_plotter(Vol_centroid) # find the max and min of x,y,z axes of mesh ranges = mesh.bounds x_range = abs(ranges[0] - ranges[1]) y_range = abs(ranges[2] - ranges[3]) z_range = abs(ranges[4] - ranges[5]) print("x:", float(format(x_range, ".2f")), "in") print("y:", float(format(y_range, ".2f")), "in") print("z:", float(format(z_range, ".2f")), "in") # mesh volume mesh_vol = float(format(mesh.volume, ".2f")) print("Mesh Volume:", mesh_vol, "in^3") # track pre-processing ending time & duration open_mesh_end = time.time() open_mesh_run = open_mesh_end - open_mesh_start print("Pre-Processing run time: %g seconds" % (open_mesh_run)) print("Mesh Cells:", mesh.n_cells) def save_screenshot(self): ''' saves screenshot of current render window''' screenshot_path = 'screenshot/' + mesh_name + '.png' self.plotter.screenshot(screenshot_path) def skewed_centroid_check(self): ''' depending if the menu item is checked or not, the centroid is either skewed with the 2nd moment of inertia or being the origin of the principal axes ''' if self.skewed_centroid_action.isChecked(): Vol_centroid = self.centroid(ext_surface) else: Vol_centroid = np.array([0,0,0]) self.reset_plotter(Vol_centroid) return Vol_centroid def reset_plotter(self, Vol_centroid): """ clear plotter of mesh or interactive options """ # clear plotter self.plotter.clear() # callback opened mesh self.plotter.add_mesh(mesh, show_edges = True, color="w", opacity=0.3) # show origin self.plotter.add_axes_at_origin(xlabel='X', ylabel='Y', zlabel='Z', line_width=6, labels_off=True) self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=40, render_points_as_spheres=True) def centroid(self, mesh): """ find centroid volumetrically and indicate on graph """ global V # find the vertices & the vertex indices of each triangular face V = np.array(mesh.points) col = len(V) f_ind = np.array(mesh.faces.reshape((-1,4))[:, 1:4]) # define an arbitrary start point from middle of max and min of X,Y,Z of # all points: in a convex manifold it falls inside the volume (requires # segmentation for general application) start = np.array(mesh.center) X_start = start[0] Y_start = start[1] Z_start = start[2] # initialize variables centroids = [] Vol_total = 0 Sum_vol_x = 0 Sum_vol_y = 0 Sum_vol_z = 0 # find centroid from all tetrahedra made with arbitrary center and triangular faces for i in range(0, col-1, 3): # find the center of each tetrahedron (average of X,Y,Z of # 4 vertices, 3 from the triangle, and one arbitrary start point) X_cent = (X_start + V[f_ind[i,0],0] + V[f_ind[i+1,0],0] + V[f_ind[i+2,0],0]) / 4 Y_cent = (Y_start + V[f_ind[i,1],1] + V[f_ind[i+1,1],1] + V[f_ind[i+2,1],1]) / 4 Z_cent = (Z_start + V[f_ind[i,2],2] + V[f_ind[i+1,2],2] + V[f_ind[i+2,2],2]) / 4 # compute the volume of each tetrahedron V1 = np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 - np.array([X_start, Y_start, Z_start])**2 V2 = np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 - np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 V3 = np.array([V[f_ind[i+2,0],0], V[f_ind[i+2,1],1], V[f_ind[i+2,2],2]])**2 - np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 V1 = V1.reshape((-1,1)) V2 = V2.reshape((-1,1)) V3 = V3.reshape((-1,1)) Vol = abs(np.linalg.det(np.hstack([V1, V2, V3]))) / 6 # tally up each cycle Vol_total = Vol_total + Vol Sum_vol_x = Sum_vol_x + Vol * X_cent Sum_vol_y = Sum_vol_y + Vol * Y_cent Sum_vol_z = Sum_vol_z + Vol * Z_cent centroids.append([X_cent,Y_cent,Z_cent]) # find & show centroid centroids = np.asarray(centroids) Vol_centroid = [Sum_vol_x, Sum_vol_y, Sum_vol_z] / Vol_total return Vol_centroid def show_max_cube(self): # check which centroid is used Vol_centroid = self.skewed_centroid_check() _, max_normal, intxn = self.max_cube_ray(int_surface, Vol_centroid, ext = True) self.max_cuboid(int_surface, intxn, Vol_centroid, max_normal) def max_cube_ray(self, mesh, Vol_centroid, ext = False): """ add a maximally inscribed cube within the opened mesh (via ray tracing) """ global r_len global face_center, max_cube_vol, max_cube, max_cuboid global max_cube_start, max_cube_end, max_cube_run global max_cube_V, max_cube_F # initiate variables max_cube = 0 max_cuboid = 0 # find mesh vertices V = np.array(mesh.points) # find the nearest possible cube vertex from top rays & mesh intersection top_vert = self.cube_center_ray(mesh, Vol_centroid, 'z') top = self.furthest_pt(top_vert, Vol_centroid) # find the nearest possible cube vertex from bottom rays & mesh intersection bottom_vert = self.cube_center_ray(mesh, Vol_centroid, '-z') bottom = self.furthest_pt(bottom_vert, Vol_centroid) # find the nearest possible cube vertex between the two if top[0] < bottom[0]: p = top[1] V = top[2] else: p = bottom[1] V = bottom[2] # set the furthest ray intersection of the mesh as starting vertex of the cube intxn = V[p,:] # create and show max cube max_cube_V, max_cube_F, max_cube_vol = self.create_cube(intxn, Vol_centroid, np.array([0,0,Vol_centroid[2]])) max_cube = pv.PolyData(max_cube_V, max_cube_F) self.plotter.add_mesh(max_cube, show_edges=True, line_width=5, color="orange", opacity = 0.8) # find & show max cube face centers cell_center = pv.PolyData(max_cube_V, max_cube_F).cell_centers() face_center = np.array(cell_center.points) # self.plotter.add_mesh(cell_center, color="r", point_size=8, render_points_as_spheres=True) # find max cube face normals max_normal = pv.PolyData(max_cube_V, max_cube_F).cell_normals # max cube volume if (ext == False): max_cube_vol = float(format(max_cube_vol, ".2f")) print("Cube Center Volume:", max_cube_vol, "in^3") return face_center, max_normal, intxn def cube_center_ray(self, mesh, start, dir): ''' from starting point shoot out n rays to find vertices of possible cubes ''' global r_num, r_rot, r_dec, r_len # initialize variables r_num = 1 r_rot = np.pi/2 r_dec = -2*np.pi/r_num ray_size = np.zeros((4, 3)) r_dir = ray_size r_dir_norm = ray_size r_end = ray_size r_int = [] ori_r_int = [] l_wid = 10 pt_size = 25 rays = [0] * 4 ints = [0] * 4 # set ray length r_len = np.sqrt((x_range/2)**2 + (y_range/2)**2 + (z_range/2)**2) # create rays by rotating the first, which creates the cube with xyz axes as its face normals for i in range(0, r_num): for j in range(0, 4): if (j == 0) and (dir == 'z'): r_dir[0] = np.array([np.sqrt(2)/2 * np.cos(np.pi/4 + r_dec * i), np.sqrt(2)/2 * np.sin(np.pi/4 + r_dec * i), 0.5]) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = start + r_dir_norm[0] * r_len # set rotation matrix about 'z' R = self.rot_axis(np.array([0,0,1])) elif (j == 0) and (dir == '-z'): r_dir[0] = np.array([np.sqrt(2)/2 * np.cos(np.pi/4 + r_dec * i), np.sqrt(2)/2 * np.sin(np.pi/4 + r_dec * i), -0.5]) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = start + r_dir_norm[0] * r_len # set rotation matrix about '-z' R = self.rot_axis(np.array([0,0,-1])) else: r_end[j] = np.dot(R(j*r_rot), (r_end[0] - start).T).T r_end[j] = r_end[j] + start # perform ray trace r_pts, _ = mesh.ray_trace(start, r_end[j]) # show rays # rays[j] = self.plotter.add_mesh(pv.Line(Vol_centroid, r_end[j]), color='w', line_width=l_wid) # ints[j] = self.plotter.add_mesh(pv.PolyData(r_pts[0]), color='w', point_size=pt_size) # create an array of ray intersections r_int = np.append(r_int, r_pts[0]) r_int = np.reshape(r_int, (4,3)) _, ori_p, ori_V = self.nearest_pt(r_int, start) r_int = [] ori_r_int = np.append(ori_r_int, ori_V[ori_p,:]) ori_r_int = np.reshape(ori_r_int, (r_num,3)) return ori_r_int def nearest_pt(self, vert, starting_pt): """ find nearest vertex: for segmented convex manifold, a cube with volume centroid as center and nearest vertex as cube vertex, it falls inside the volume """ # find nearest point from the list of points c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i,0] - starting_pt[0])**2 + (vert[i,1] - starting_pt[1])**2 + (vert[i,2] - starting_pt[2])**2) # find index of the nearest point nearest = min(dist) p = np.where(dist == nearest) p = p[0].item() return nearest, p, vert def furthest_pt(self, vert, starting_pt): global p, furthest, dist """ find furthest vertex among the list of nearest vertices """ # find furthest point from the list of points c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i,0] - starting_pt[0])**2 + (vert[i,1] - starting_pt[1])**2 + (vert[i,2] - starting_pt[2])**2) # find index of the furthest point furthest = max(dist) p = np.where(dist == furthest) p = p[0][0] return furthest, p, vert def create_cube(self, vertex, starting_pt, axis): ''' create cube from the nearest pt & centroid ''' global edge_length if (axis[0] == 0) and (axis[1] == 0) and (axis[2] == 0): axis[2] = 1 vert_trans = np.array([0,0,0]) elif (starting_pt[0] == 0) and (starting_pt[1] == 0) and (starting_pt[2] == 0): vert_trans = np.array([0,0,0]) else: vert_trans = starting_pt for i in range(0,3): if round(axis[i]) == 1 or round(axis[i]) == -1: vert_trans[i] == 0 # find the other 7 vertices # 3 vertices can be found by rotating the first point 90 degrees 3 times around Z axis of centroid # 4 vertices can be found by translating the first four vertices twice the half edge # found from the distance times sin(pi/4) R = self.rot_axis(axis / np.linalg.norm(axis)) # construct the array of the first 4 vertices V_1 = np.array(vertex - vert_trans) V_2 = np.dot(R(np.pi/2), V_1.T).T V_3 = np.dot(R(np.pi), V_1.T).T V_4 = np.dot(R(3*np.pi/2), V_1.T).T # cube_V_start = np.array([V_1, V_2, V_3, V_4]) cube_V_start = np.array([V_1, V_2, V_3, V_4]) + np.ones((4,1)) * [vert_trans] cube_V_start_center = np.array(pv.PolyData(cube_V_start).center) # show nearest vertex of cube V_1 = np.array(vertex) self.plotter.add_mesh(pv.PolyData(V_1), color="y", point_size=30.0, render_points_as_spheres=True) # find the translation distance trans_dis = starting_pt - cube_V_start_center trans_dir = trans_dis / np.linalg.norm(trans_dis) dia_dis = np.sqrt((V_1[0]-cube_V_start_center[0])**2 + (V_1[1]-cube_V_start_center[1])**2 + (V_1[2]-cube_V_start_center[2])**2) half_edge = np.ones((4,1)) * [trans_dir] * dia_dis * np.sin(np.pi/4) edge_length = dia_dis * np.sin(np.pi/4) * 2 print("Center cube edge length:", edge_length) cube_trans = np.asarray(2*half_edge, dtype=np.float64) # construct the cube cube_V_end = np.add(cube_V_start, cube_trans) cube_V = np.vstack((cube_V_start, cube_V_end)) cube_F = np.hstack([[4,0,1,2,3], [4,0,3,7,4], [4,0,1,5,4], [4,1,2,6,5], [4,2,3,7,6], [4,4,5,6,7]]) # cube volume cube_vol = (2 * np.linalg.norm(half_edge[0,:]))**3 return cube_V, cube_F, cube_vol def rot_axis(self, axis): ''' create a rotational matrix about an arbitrary axis ''' t = sp.Symbol('t') R_t = Matrix([[sp.cos(t)+axis[0]**2*(1-sp.cos(t)), axis[0]*axis[1]*(1-sp.cos(t))-axis[2]*sp.sin(t), axis[0]*axis[2]*(1-sp.cos(t))+axis[1]*sp.sin(t)], [axis[1]*axis[0]*(1-sp.cos(t))+axis[2]*sp.sin(t), sp.cos(t)+axis[1]**2*(1-sp.cos(t)), axis[1]*axis[2]*(1-sp.cos(t))-axis[0]*sp.sin(t)], [axis[2]*axis[0]*(1-sp.cos(t))-axis[1]*sp.sin(t), axis[2]*axis[1]*(1-sp.cos(t))+axis[0]*sp.sin(t), sp.cos(t)+axis[2]**2*(1-sp.cos(t))]]) R = lambdify(t, R_t) return R def max_cuboid(self, mesh, nearest_pt, Vol_centroid, max_normal): ''' extend max cube into maximally inscribed cuboid ''' global face_center, max_cuboid, max_cuboid_vol # fix max_normals dir_check = (face_center - Vol_centroid) * 2 / edge_length x_check = np.abs(np.around(max_normal[0,0] - dir_check[0,0])) y_check = np.abs(np.around(max_normal[0,1] - dir_check[0,1])) z_check = np.abs(np.around(max_normal[0,2] - dir_check[0,2])) print(x_check) print(y_check) print(z_check) print(max_normal) print(dir_check) if (x_check == 2) or (y_check == 2) or (z_check == 2): max_normal = -max_normal # find the 3 out of 6 normal directions the max cube can be extended towards ext_dir = np.empty(shape=(3,3)) main_dir = nearest_pt - Vol_centroid ind = 0 for i in range(0, 6): if np.dot(main_dir, max_normal[i]) < 0: ext_dir[ind] = max_normal[i] ind += 1 # extend faces by shooting a ray from the 4 vertices on each extendable face # in the direction of its face normal. Find the nearest intersection and # it would be the limit of extension for that face for i in range(0, 3): F_ind = np.where((np.around(max_normal) == np.around(ext_dir[i])).all(axis=1)) F_ind = F_ind[0][0] np.reshape(max_cube_F, (6,5)) faces = np.reshape(max_cube_F, (6,5)) print(faces) V_ind = faces[F_ind, 1:5] print(V_ind) current_V = np.vstack([max_cube_V[V_ind[0]], max_cube_V[V_ind[1]], max_cube_V[V_ind[2]], max_cube_V[V_ind[3]]]) print(current_V) ext_V = self.ext_ray(mesh, current_V, ext_dir[i]) max_cube_V[V_ind] = ext_V # create & show extended max cube max_cuboid = pv.PolyData(max_cube_V, max_cube_F) self.plotter.add_mesh(max_cuboid, show_edges=True, line_width=5, color="y", opacity = 0.4) # find face centers of extended max cube cell_center = max_cuboid.cell_centers() face_center = np.array(cell_center.points) # find face normals of the extended max cube max_normal = max_cuboid.cell_normals # extended max cube volume max_cuboid_vol = float(format(max_cuboid.volume, ".2f")) print("Extended Max Cube Volume:", max_cuboid_vol) def ext_ray(self, mesh, current_V, ext_dir): ''' shoot rays from vertices of a cube face towards face normal & obtain intersections with mesh ''' # initialize variables ext_end = current_V + ext_dir * np.ones((4,1)) * r_len ext_dis = np.zeros(4) ext_rays = [0] * 6 * r_num ext_ints = [0] * 6 * r_num # set raytracing parameters l_wid = 3 pt_size = 10 # perform ray tracing per extending face vertex for i in range(0,4): ext_int, _ = mesh.ray_trace(current_V[i], ext_end[i]) ext_dis[i] = np.sqrt((ext_int[0][0] - current_V[i][0])**2 + (ext_int[0][1] - current_V[i][1])**2 + (ext_int[0][2] - current_V[i][2])**2) # show rays # ext_rays[i] = self.plotter.add_mesh(pv.Line(current_V[i], ext_end[i]), color='w', line_width=l_wid) # ext_ints[i] = self.plotter.add_mesh(pv.PolyData(ext_int[0]), color='w', point_size=pt_size) # extend vertices by the shortest intersection distance ext_V = current_V + ext_dir * np.ones((4,1)) * min(ext_dis) return ext_V def closeEvent(self, event): reply = QMessageBox.question(self, "Window Close", "Are you sure you want to quit program?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
def __init__(self): QtWidgets.QWidget.__init__(self) # main/ real usable GUI #set windowsize self.setFixedSize(1000, 900) #meshplot frame with interactor self.frame_mp = QtWidgets.QFrame(self) self.frame_mp.setGeometry(100, 20, 500, 250) vlayout_mp = QtWidgets.QVBoxLayout() self.plotter_mp = QtInteractor(self.frame_mp) vlayout_mp.addWidget(self.plotter_mp.interactor) vlayout_mp.setContentsMargins(0, 0, 0, 0) self.frame_mp.setLayout(vlayout_mp) #full PCplot frame with all points self.frame_pc = QtWidgets.QFrame(self) self.frame_pc.setGeometry(100, 320, 500, 250) vlayout_pc = QtWidgets.QVBoxLayout() self.plotter_pc = QtInteractor(self.frame_pc) vlayout_pc.addWidget(self.plotter_pc.interactor) vlayout_pc.setContentsMargins(0, 0, 0, 0) self.frame_pc.setLayout(vlayout_pc) #sample PCplot frame with variable k self.frame_spc = QtWidgets.QFrame(self) self.frame_spc.setGeometry(100, 620, 500, 250) vlayout_spc = QtWidgets.QVBoxLayout() self.plotter_spc = QtInteractor(self.frame_spc) vlayout_spc.addWidget(self.plotter_spc.interactor) vlayout_spc.setContentsMargins(0, 0, 0, 0) self.frame_spc.setLayout(vlayout_spc) #GUI Functions / Buttons #Plot and Inspect single files self.lab_inspectorlab = QtWidgets.QLabel('Display Mesh:', self) self.lab_inspectorlab.setGeometry(630, 20, 200, 20) self.button_sel_file = QtWidgets.QPushButton('Select File', self) self.button_sel_file.setGeometry(650, 70, 100, 25) #select sampletype of PointCloud self.lab_inspectorlab = QtWidgets.QLabel('Model Sampler Settings:', self) self.lab_inspectorlab.setGeometry(630, 170, 200, 20) self.lab_sampkind = QtWidgets.QLabel('sampletype:', self) self.lab_sampkind.setGeometry(650, 220, 100, 20) self.lab_vertexcloud = QtWidgets.QLabel('Vertices', self) self.lab_vertexcloud.setGeometry(750, 220, 100, 20) self.box_vertexcloud = QtWidgets.QCheckBox(self) self.box_vertexcloud.setGeometry(800, 220, 100, 20) self.lab_surfacecloud = QtWidgets.QLabel('Surface', self) self.lab_surfacecloud.setGeometry(850, 220, 100, 20) self.box_surfacecloud = QtWidgets.QCheckBox(self) self.box_surfacecloud.setGeometry(900, 220, 100, 20) #samplesize Option config self.lab_sampsize = QtWidgets.QLabel('samplesize:', self) self.lab_sampsize.setGeometry(650, 270, 100, 20) self.input_sampsize = QtWidgets.QLineEdit(self) self.input_sampsize.setGeometry(750, 270, 70, 20) self.button_set_sampsize = QtWidgets.QPushButton( 'Apply Settings', self) self.button_set_sampsize.setGeometry(850, 268, 100, 25) #plot PointCloud Sample (also plots full pointcloud) self.lab_plotsth = QtWidgets.QLabel('Start / Stop Plot:', self) self.lab_plotsth.setGeometry(630, 350, 100, 20) self.button_plot_samp_cancel = QtWidgets.QPushButton( 'STOP PLOTS', self) self.button_plot_samp_cancel.setGeometry(650, 400, 100, 25) self.button_testmode = QtWidgets.QPushButton('PLOT', self) self.button_testmode.setGeometry(800, 400, 100, 25) #save sample as pytorch tensor object self.lab_savesth = QtWidgets.QLabel( 'Save the Sampled Pointcloud as Tensor:', self) self.lab_savesth.setGeometry(630, 450, 200, 20) self.button_save_sample = QtWidgets.QPushButton('Save', self) self.button_save_sample.setGeometry(650, 500, 100, 25) #Conversion of Folders to Surfacesamples self.lab_conversionlab = QtWidgets.QLabel( 'Convert Folder to Dataset-Ready Surfaceasamples:', self) self.lab_conversionlab.setGeometry(630, 620, 250, 20) self.lab_perspnumlab = QtWidgets.QLabel('Num of Perspectives:', self) self.lab_perspnumlab.setGeometry(650, 670, 210, 20) self.input_perspnum = QtWidgets.QLineEdit(self) self.input_perspnum.setGeometry(800, 670, 70, 20) self.input_perspnum.setText("1") self.button_sel_folder = QtWidgets.QPushButton('Select Folder', self) self.button_sel_folder.setGeometry(650, 720, 100, 25) self.button_save_folder = QtWidgets.QPushButton('Convert', self) self.button_save_folder.setGeometry(800, 720, 100, 25) self.lab_conversionlab = QtWidgets.QLabel('Progress:', self) self.lab_conversionlab.setGeometry(650, 770, 250, 20) self.progbar_folderconv = QtWidgets.QProgressBar(self) self.progbar_folderconv.setGeometry(720, 770, 180, 20) #status label self.lab_label_status = QtWidgets.QLabel('Status:', self) self.lab_label_status.setGeometry(650, 850, 100, 20) self.label_status = QtWidgets.QLabel('', self) self.label_status.setGeometry(750, 850, 200, 20)
class MainGUIWindow(QtWidgets.QWidget): def __init__(self): QtWidgets.QWidget.__init__(self) # main/ real usable GUI #set windowsize self.setFixedSize(1000, 900) #meshplot frame with interactor self.frame_mp = QtWidgets.QFrame(self) self.frame_mp.setGeometry(100, 20, 500, 250) vlayout_mp = QtWidgets.QVBoxLayout() self.plotter_mp = QtInteractor(self.frame_mp) vlayout_mp.addWidget(self.plotter_mp.interactor) vlayout_mp.setContentsMargins(0, 0, 0, 0) self.frame_mp.setLayout(vlayout_mp) #full PCplot frame with all points self.frame_pc = QtWidgets.QFrame(self) self.frame_pc.setGeometry(100, 320, 500, 250) vlayout_pc = QtWidgets.QVBoxLayout() self.plotter_pc = QtInteractor(self.frame_pc) vlayout_pc.addWidget(self.plotter_pc.interactor) vlayout_pc.setContentsMargins(0, 0, 0, 0) self.frame_pc.setLayout(vlayout_pc) #sample PCplot frame with variable k self.frame_spc = QtWidgets.QFrame(self) self.frame_spc.setGeometry(100, 620, 500, 250) vlayout_spc = QtWidgets.QVBoxLayout() self.plotter_spc = QtInteractor(self.frame_spc) vlayout_spc.addWidget(self.plotter_spc.interactor) vlayout_spc.setContentsMargins(0, 0, 0, 0) self.frame_spc.setLayout(vlayout_spc) #GUI Functions / Buttons #Plot and Inspect single files self.lab_inspectorlab = QtWidgets.QLabel('Display Mesh:', self) self.lab_inspectorlab.setGeometry(630, 20, 200, 20) self.button_sel_file = QtWidgets.QPushButton('Select File', self) self.button_sel_file.setGeometry(650, 70, 100, 25) #select sampletype of PointCloud self.lab_inspectorlab = QtWidgets.QLabel('Model Sampler Settings:', self) self.lab_inspectorlab.setGeometry(630, 170, 200, 20) self.lab_sampkind = QtWidgets.QLabel('sampletype:', self) self.lab_sampkind.setGeometry(650, 220, 100, 20) self.lab_vertexcloud = QtWidgets.QLabel('Vertices', self) self.lab_vertexcloud.setGeometry(750, 220, 100, 20) self.box_vertexcloud = QtWidgets.QCheckBox(self) self.box_vertexcloud.setGeometry(800, 220, 100, 20) self.lab_surfacecloud = QtWidgets.QLabel('Surface', self) self.lab_surfacecloud.setGeometry(850, 220, 100, 20) self.box_surfacecloud = QtWidgets.QCheckBox(self) self.box_surfacecloud.setGeometry(900, 220, 100, 20) #samplesize Option config self.lab_sampsize = QtWidgets.QLabel('samplesize:', self) self.lab_sampsize.setGeometry(650, 270, 100, 20) self.input_sampsize = QtWidgets.QLineEdit(self) self.input_sampsize.setGeometry(750, 270, 70, 20) self.button_set_sampsize = QtWidgets.QPushButton( 'Apply Settings', self) self.button_set_sampsize.setGeometry(850, 268, 100, 25) #plot PointCloud Sample (also plots full pointcloud) self.lab_plotsth = QtWidgets.QLabel('Start / Stop Plot:', self) self.lab_plotsth.setGeometry(630, 350, 100, 20) self.button_plot_samp_cancel = QtWidgets.QPushButton( 'STOP PLOTS', self) self.button_plot_samp_cancel.setGeometry(650, 400, 100, 25) self.button_testmode = QtWidgets.QPushButton('PLOT', self) self.button_testmode.setGeometry(800, 400, 100, 25) #save sample as pytorch tensor object self.lab_savesth = QtWidgets.QLabel( 'Save the Sampled Pointcloud as Tensor:', self) self.lab_savesth.setGeometry(630, 450, 200, 20) self.button_save_sample = QtWidgets.QPushButton('Save', self) self.button_save_sample.setGeometry(650, 500, 100, 25) #Conversion of Folders to Surfacesamples self.lab_conversionlab = QtWidgets.QLabel( 'Convert Folder to Dataset-Ready Surfaceasamples:', self) self.lab_conversionlab.setGeometry(630, 620, 250, 20) self.lab_perspnumlab = QtWidgets.QLabel('Num of Perspectives:', self) self.lab_perspnumlab.setGeometry(650, 670, 210, 20) self.input_perspnum = QtWidgets.QLineEdit(self) self.input_perspnum.setGeometry(800, 670, 70, 20) self.input_perspnum.setText("1") self.button_sel_folder = QtWidgets.QPushButton('Select Folder', self) self.button_sel_folder.setGeometry(650, 720, 100, 25) self.button_save_folder = QtWidgets.QPushButton('Convert', self) self.button_save_folder.setGeometry(800, 720, 100, 25) self.lab_conversionlab = QtWidgets.QLabel('Progress:', self) self.lab_conversionlab.setGeometry(650, 770, 250, 20) self.progbar_folderconv = QtWidgets.QProgressBar(self) self.progbar_folderconv.setGeometry(720, 770, 180, 20) #status label self.lab_label_status = QtWidgets.QLabel('Status:', self) self.lab_label_status.setGeometry(650, 850, 100, 20) self.label_status = QtWidgets.QLabel('', self) self.label_status.setGeometry(750, 850, 200, 20) #update GUI functions, triggered by signals @QtCore.pyqtSlot(str) def updateStatus(self, status): self.label_status.setText(status) def updateProgress(self, value): self.progbar_folderconv.setValue(value) def plotmesh(self, filestr): self.plotter_mp.clear() self.plotter_mp.add_text("Mesh View", font_size=10) mesh = pv.read(filestr) self.plotter_mp.add_mesh(mesh, show_edges=True, color="#8cfaab") self.plotter_mp.reset_camera() def plotcloud(self, point_cloud): self.plotter_pc.clear() self.plotter_pc.add_text("PointCloud View", font_size=10) self.plotter_pc.add_mesh(point_cloud, render_points_as_spheres=True, color="#8cfaab") self.plotter_pc.reset_camera() def plotcloudsamp(self, point_cloud): self.plotter_spc.clear() self.plotter_spc.add_text("PointCloud Sample", font_size=10) self.plotter_spc.add_mesh(point_cloud, render_points_as_spheres=True, color="#8cfaab") self.plotter_spc.reset_camera()
def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # inserting maximally inscribed cube self.max_cube_action = Qt.QAction('Max Cube', self) #self.max_cube_action.setCheckable(True) self.max_cube_action.triggered.connect(self.max_cube) editMenu.addAction(self.max_cube_action) #Create Cone in Mesh self.next_cubes_action = Qt.QAction('Next Cubes', self) self.next_cubes_action.triggered.connect(self.next_cubes) editMenu.addAction(self.next_cubes_action) # slice mesh horizontally based on internal cubes self.cube_hslice_action = Qt.QAction('Cube H-Slice', self) self.cube_hslice_action.triggered.connect(self.cube_hslice) editMenu.addAction(self.cube_hslice_action) # slice mesh (interactively) self.slice_action = Qt.QAction('Slice', self) self.slice_action.triggered.connect(self.slice) editMenu.addAction(self.slice_action) # slice mesh with clipping (interactively) self.clip_slice_action = Qt.QAction('Clip Slice', self) self.clip_slice_action.triggered.connect(self.clip_slice) editMenu.addAction(self.clip_slice_action) # create bounding box(es) for mesh (interactively) self.bounding_action = Qt.QAction('Bounding', self) self.bounding_action.triggered.connect(self.bounding_bar) editMenu.addAction(self.bounding_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, color=None, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box=None, box_args=None)
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # inserting maximally inscribed cube self.max_cube_action = Qt.QAction('Max Cube', self) #self.max_cube_action.setCheckable(True) self.max_cube_action.triggered.connect(self.max_cube) editMenu.addAction(self.max_cube_action) #Create Cone in Mesh self.next_cubes_action = Qt.QAction('Next Cubes', self) self.next_cubes_action.triggered.connect(self.next_cubes) editMenu.addAction(self.next_cubes_action) # slice mesh horizontally based on internal cubes self.cube_hslice_action = Qt.QAction('Cube H-Slice', self) self.cube_hslice_action.triggered.connect(self.cube_hslice) editMenu.addAction(self.cube_hslice_action) # slice mesh (interactively) self.slice_action = Qt.QAction('Slice', self) self.slice_action.triggered.connect(self.slice) editMenu.addAction(self.slice_action) # slice mesh with clipping (interactively) self.clip_slice_action = Qt.QAction('Clip Slice', self) self.clip_slice_action.triggered.connect(self.clip_slice) editMenu.addAction(self.clip_slice_action) # create bounding box(es) for mesh (interactively) self.bounding_action = Qt.QAction('Bounding', self) self.bounding_action.triggered.connect(self.bounding_bar) editMenu.addAction(self.bounding_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, color=None, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box=None, box_args=None) def open_mesh(self): """ add a mesh to the pyqt frame """ global mesh # open file file_info = QtWidgets.QFileDialog.getOpenFileName() file_dir = file_info[0] # determine file type and if conversion needed head, tail = os.path.split(file_dir) root, ext = os.path.splitext(tail) # convert mesh file type #if ext != ".vtk" or ext != ".VTK": # mesh = meshio.read(file_dir) # meshio.write(root + ".vtk", mesh) # mesh = pv.read(head + "/" + root + ".vtk") # need to store elsewhere or delete .vtk file in the future #else: # mesh = pv.read(file_dir) # read mesh & transform according to principal axes pre = trimesh.load_mesh(file_dir) orient = pre.principal_inertia_transform pre = pre.apply_transform(orient) pre.export('data/'+ root + '_oriented.STL') mesh = pv.read('data/'+ root + '_oriented.STL') # show transformed mesh self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # reset plotter self.reset_plotter() # find mesh centroid and translate the mesh so that's the origin self.centroid() # show bounding box self.plotter.add_bounding_box(opacity=0.5, color="y") def reset_plotter(self): """ clear plotter of mesh or interactive options """ # clear plotter self.plotter.clear() self.plotter.clear_plane_widgets() self.plotter.reset_camera() self.update() # callback opened mesh self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # show origin #self.plotter.add_axes_at_origin(xlabel='X', ylabel='Y', zlabel='Z', line_width=1, labels_off=False) # show floors #self.plotter.add_floor('-y') #self.plotter.add_floor('-z') def next_cubes(self): #change cones to c0 to c5 global create_cone hi = 12 ang = np.arctan(1/(np.sqrt(2)/2)) ang = float(90 - np.degrees(ang)) create_cone = pv.Cone(center=face_center[0] + [0,0,hi/2], direction = [0.0,0.0,-1.0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone2 = pv.Cone(center=face_center[1] + [-hi/2,-hi/2,0], direction = [1,1,0.0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone3 = pv.Cone(center=face_center[2] + [0,0,hi/2], direction = [0,0,-1], height = hi, radius=None, resolution= 100, angle = ang, capping=False) #create_cone3 = pv.Cone(center=face_center[2] + [hi/2,hi,0], direction = [-1,-1,0.0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone4 = pv.Cone(center=face_center[3] + [hi/4,hi/4,0], direction = [-1,-1,0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone5 = pv.Cone(center=face_center[4] + [0,hi/2,0], direction = [0,-1,0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone6 = pv.Cone(center=face_center[5] + [0,0,-hi/2], direction = [0,0,1], height = hi, radius=None, resolution= 100, angle = ang, capping=False) #create_cone7 = pv.Cone(center=cell_center1[0] + [0,0,hi/2], direction = [0,0,-1], height = hi, radius=None, resolution= 100, angle = ang, capping=False) clipped = mesh.clip_surface(create_cone, invert=True) cone_intersections = np.array(clipped.points) #top = self.nearest_pt(create_cone, face_center[0] + [0,0,hi/2]) clipped6 = mesh.clip_surface(create_cone6, invert=True) cone_intersection6 = np.array(clipped6.points) #nearest_cone = min(cone_intersection6) #self.nearest_pt(create_cone, face_center[0]) print("Bottom Intersection:",cone_intersection6) #nearest_point = np.amin(cone_intersection6[0]) nearest_point = min(cone_intersections, key=min) nearest_point6 = min(cone_intersection6, key=min) x_Min = face_center[5][0] x_Max = nearest_point6[0] x = x_Max - x_Min y_Min = face_center[5][1] y_Max = nearest_point6[1] y = y_Max - y_Min z_Min = face_center[5][2] z_Max = nearest_point6[2] z = z_Max - z_Min x1 = nearest_point[0] - face_center[0][0] y1 = nearest_point[1] - face_center[0][1] z1 = nearest_point[2] - face_center[0][2] #new_array = np.array(x1,y1,z1) #smallest = np.amin(new_array) #cube_bounds = np.array(face_center[0], x_length1, face_center[1], y_length2, face_center[3], z_length3) print(nearest_point, "Closeest Value") #print(largest) #print(smallest) print(x_Max) print(x1, "x") print(y1, "y") print(z1, "z") #print(cube_bounds, "Cube Boundarys") #cone_center = create_cone.cell_centers() #cone_center_points = np.array(cone_center.points) self.plotter.add_mesh(create_cone, color="y", opacity=0.6) #self.plotter.add_mesh(top, show_edges=True, color="r", opacity=0.6) self.plotter.add_mesh(clipped, show_edges=True, color="r", opacity=0.6) #self.plotter.add_mesh(create_cone2, show_edges=True, color="y", opacity=0.6) #self.plotter.add_mesh(create_cone3, show_edges=True, color="y", opacity=0.6) #self.plotter.add_mesh(create_cone4, show_edges=True, color="y", opacity=0.6) #self.plotter.add_mesh(create_cone5, show_edges=True, color="y", opacity=0.6) self.plotter.add_mesh(create_cone6, show_edges=True, color="y", opacity=0.6) self.plotter.add_mesh(clipped6, show_edges=True, color="r", opacity=0.6) "Z is the Max point of the minium points calcualted in order to create cube within space" next_cube = pv.Cube(center = (x_Min,y_Min,face_center[0][2] + (abs(z1)/2)), x_length= abs(z1), y_length = abs(z1), z_length = abs(z1), bounds=None) next_cube6 = pv.Cube(center = (x_Min,y_Min,face_center[5][2] - (abs(z)/2)), x_length= abs(z), y_length= abs(z), z_length = abs(z), bounds=None) cell_center1 = next_cube.cell_centers() #create_cone7 = pv.Cone(center=cell_center1[0] + [0,0,hi/2], direction = [0,0,-1], height = hi, radius=None, resolution= 100, angle = ang, capping=False) # clipped7 = mesh.clip_surface(create_cone7, invert=True) #cone_intersection7 = np.array(clipped7.points) # nearest_point7 = min(cone_intersection7, key=min) #x7 = nearest_point7[0] - cell_center1[0][0] #y7 = nearest_point7[1] - cell_center1[0][1] #z7 = nearest_point7[2] - cell_center1[0][2] #next_cube7 = pv.Cube(center = (x_Min,y_Min,cell_center1[0][2] - (abs(x7)/2)), x_length= abs(x7), y_length= abs(x7), z_length = abs(x7), bounds=None) next_cube_center_top = np.array(cell_center1.points) cell_center6 = next_cube6.cell_centers() next_cube__center_bottom = np.array(cell_center6.points) print(next_cube__center_bottom) self.plotter.add_mesh(next_cube, show_edges=True, color = "b", opacity = 0.6) self.plotter.add_mesh(cell_center1, color="r", point_size=8.0, render_points_as_spheres=True) self.plotter.add_mesh(next_cube6, show_edges=True, color = "b", opacity = 0.6) self.plotter.add_mesh(cell_center6, color="r", point_size=8.0, render_points_as_spheres=True) #self.plotter.add_mesh(next_cube7, show_edges=True, color = "b", opacity = 0.6) #self.plotter.add_mesh(cell_center1, color="r", point_size=8.0, render_points_as_spheres=True) #clip1 = mesh.clip_surface(create_cone, invert=True) #clip2 = mesh.clip_surface(c2, invert=True) #clip = [clip1, clip2] #self.plotter.add_mesh(clip1, opacity=0.6, show_edges=True, color="w") #self.plotter.add_mesh(clip[1], opacity=0.6, show_edges=True, color="g") #self.plotter.add_mesh(cone_center, color="r", point_size=8.0, render_points_as_spheres=True) #print("Cone Center:",cone_center_points) def centroid(self): """ find centroid volumetrically and indicate on graph """ global Vol_centroid, V # find the vertices & the vertex indices of each triangular face V = np.array(mesh.points) col = len(V) f_ind = np.array(mesh.faces.reshape((-1,4))[:, 1:4]) # define an arbitrary start point from middle of max and min of X,Y,Z of # all points: in a convex manifold it falls inside the volume (requires # segmentation for general application) start = np.array(mesh.center) X_start = start[0] Y_start = start[1] Z_start = start[2] # initialize variables centroids = [] Vol_total = 0 Sum_vol_x = 0 Sum_vol_y = 0 Sum_vol_z = 0 # find centroid from all tetrahedra made with arbitrary center and triangular faces for i in range(0, col-1, 3): # find the center of each tetrahedron (average of X,Y,Z of # 4 vertices, 3 from the triangle, and one arbitrary start point) X_cent = (X_start + V[f_ind[i,0],0] + V[f_ind[i+1,0],0] + V[f_ind[i+2,0],0]) / 4 Y_cent = (Y_start + V[f_ind[i,1],1] + V[f_ind[i+1,1],1] + V[f_ind[i+2,1],1]) / 4 Z_cent = (Z_start + V[f_ind[i,2],2] + V[f_ind[i+1,2],2] + V[f_ind[i+2,2],2]) / 4 # compute the volume of each tetrahedron V1 = np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 - np.array([X_start, Y_start, Z_start])**2 V2 = np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 - np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 V3 = np.array([V[f_ind[i+2,0],0], V[f_ind[i+2,1],1], V[f_ind[i+2,2],2]])**2 - np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 V1 = V1.reshape((-1,1)) V2 = V2.reshape((-1,1)) V3 = V3.reshape((-1,1)) Vol = abs(np.linalg.det(np.hstack([V1, V2, V3]))) / 6 # tally up each cycle Vol_total = Vol_total + Vol Sum_vol_x = Sum_vol_x + Vol * X_cent Sum_vol_y = Sum_vol_y + Vol * Y_cent Sum_vol_z = Sum_vol_z + Vol * Z_cent centroids.append([X_cent,Y_cent,Z_cent]) # find & show centroid centroids = np.asarray(centroids) Vol_centroid = [Sum_vol_x, Sum_vol_y, Sum_vol_z] / Vol_total print("Total Volume:", Vol_total) print("Centroid:", Vol_centroid) def max_cube(self): """ add a maximally inscribed cube within the opened mesh """ global max_c1, max_c2 # reset plotter self.reset_plotter() # project cones to from centroid to find maximally inscribed cube ranges = mesh.bounds h = (abs(ranges[4]) + abs(ranges[5]))/3 ang = np.arctan(0.5/(np.sqrt(2)/2)) ang = float(90 - np.degrees(ang)) max_c1 = pv.Cone(center=Vol_centroid+[0,0,h/2], direction=[0.0, 0.0, -1.0], height=h, radius=None, capping=False, angle=ang, resolution=100) max_c2 = pv.Cone(center=Vol_centroid-[0,0,h/2], direction=[0.0, 0.0, 1.0], height=h, radius=None, capping=False, angle=ang, resolution=100) #self.plotter.add_mesh(max_c1,color="r", opacity=0.2) #self.plotter.add_mesh(max_c2,color="r", opacity=0.2) # show centroid self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) # find the nearest possible max cube vertex top = self.nearest_pt(max_c1, Vol_centroid) bottom = self.nearest_pt(max_c2, Vol_centroid) if top[0] < bottom[0]: p = top[1] V = top[2] else: p = bottom[1] V = bottom[2] # create and show max cube self.create_cube(V[p,:], Vol_centroid) self.plotter.add_mesh(max_cube, show_edges=True, color="b", opacity=0.6) #self.plotter.add_mesh(cell_center, color="r", point_size=8.0, render_points_as_spheres=True) # re-assign V as points of mesh V = np.array(mesh.points) def nearest_pt(self, cone, starting_pt): """ find nearest vertex: for segmented convex manifold, a cube with volume centroid as center and nearest vertex as cube vertex, it falls inside the volume """ global vert, p, clip clip = mesh.clip_surface(cone, invert=True) #clip2 = mesh.clip_surface(cone, invert=False) #diff = clip1.boolean_difference(clip2) #vert = np.array(diff.points) #self.plotter.add_mesh(clip, opacity=0.6, show_edges=True, color="g") # find nearest point in the clipped mesh vert = np.array(clip.points) c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i,0] - starting_pt[0])**2 + (vert[i,1] - starting_pt[1])**2 + (vert[i,2] - starting_pt[2])**2) # find index of the nearest point nearest = min(dist) p = np.where(dist == nearest) p = p[0].item() return nearest, p, vert def create_cube(self, vertex, centroid): ''' create cube from the nearest pt & centroid ''' global face_center, max_cube # find the other 7 vertices # 3 vertices can be found by rotating the first point 90 degrees 3 times around Z axis of centroid # 4 vertices can be found by translating the first four vertices twice the half edge # found from the distance times sin(pi/4) t = sp.Symbol('t') Rz_t = Matrix([[sp.cos(t), -sp.sin(t), 0], [sp.sin(t), sp.cos(t), 0], [0, 0, 1]]) #Txy = Matrix([[1, 0, centroid[0]], # [0, 1, centroid[1]], # [0, 0, 1]]) #T_x_y = Matrix([[1, 0, -centroid[0]], # [0, 1, -centroid[1]], # [0, 0, 1]]) #Rz_Txy_t = Txy * Rz_t * T_x_y #Rz_Txy = lambdify(t, Rz_Txy_t) Rz = lambdify(t, Rz_t) # construct the array of the first 4 vertices V_a = np.array(vertex-[centroid[0], centroid[1], 0]) a_2 = np.dot(Rz(np.pi/2), V_a.T).T + np.array([centroid[0], centroid[1], 0]) a_3 = np.dot(Rz(np.pi), V_a.T).T + np.array([centroid[0], centroid[1], 0]) a_4 = np.dot(Rz(3*np.pi/2), V_a.T).T + np.array([centroid[0], centroid[1], 0]) V_a = np.array(vertex) cube_V_start = np.array([V_a, a_2, a_3, a_4]) print(cube_V_start) # find the translation distance half_edge = np.ones((4,1)) * [[0, 0, np.sign(centroid[2]-vertex[2])]] * np.sqrt((vertex[0]-centroid[0])**2 + (vertex[1]-centroid[1])**2) * sp.sin(sp.pi/4) translate = np.asarray(2*half_edge, dtype=np.float64) # construct the cube cube_V_end = np.add(cube_V_start, translate) cube_V = np.vstack((cube_V_start, cube_V_end)) # construct the 6 faces of the cube cube_F =np.hstack([[4,0,1,2,3], [4,0,3,7,4], [4,0,1,5,4], [4,1,2,6,5], [4,2,3,7,6], [4,4,5,6,7]]) max_cube = pv.PolyData(cube_V, cube_F) cell_center = max_cube.cell_centers() face_center = np.array(cell_center.points) #print("Center of Faces:",face_center) def next_cubes(self): ''' create cubes within the mesh from the face centers of the first cube''' hi = 12 ang = np.arctan(1/(np.sqrt(2)/2)) ang = float(90 - np.degrees(ang)) create_cone = pv.Cone(center=face_center[0] + [0,0,hi/2], direction = [0.0,0.0,-1.0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone2 = pv.Cone(center=face_center[1] + [0,0,hi/2], direction = [-1.0,-1.0,0.0], height = hi, radius=None, resolution= 100, angle = ang, capping=False) create_cone5 = pv.Cone(center=face_center[5] + [0,0,-hi/2], direction = [0,0,1], height = hi, radius=None, resolution= 100, angle = ang, capping=False) clipped = mesh.clip_surface(create_cone, invert=True) clipped5 = mesh.clip_surface(create_cone5, invert=True) cone_intersection5 = np.array(clipped5.points) #nearest_cone = min(cone_intersection5) print("Bottom Intersection:",cone_intersection5) #cone_center = create_cone.cell_centers() #cone_center_points = np.array(cone_center.points) self.plotter.add_mesh(create_cone, color="y", opacity=0.6) self.plotter.add_mesh(clipped, show_edges=True, color="r", opacity=0.6) self.plotter.add_mesh(create_cone2, show_edges=True, color="y", opacity=0.6) self.plotter.add_mesh(create_cone5, show_edges=True, color="y", opacity=0.6) self.plotter.add_mesh(clipped5, show_edges=True, color="r", opacity=0.6) #clip1 = mesh.clip_surface(create_cone, invert=True) #clip2 = mesh.clip_surface(max_c2, invert=True) #clip = [clip1, clip2] #self.plotter.add_mesh(clip1, opacity=0.6, show_edges=True, color="w") #self.plotter.add_mesh(clip[1], opacity=0.6, show_edges=True, color="g") #self.plotter.add_mesh(cone_center, color="r", point_size=8.0, render_points_as_spheres=True) #print("Cone Center:",cone_center_points) def cube_hslice(self): """ slice mesh horizontally based on internal cubes """ # reset plotter self.reset_plotter() # create sliced parts part1 = mesh.clip_closed_surface('z', origin=face_center[0]) part2_a = mesh.clip_closed_surface('-z', origin=face_center[0]) part2 = part2_a.clip_closed_surface('z', origin=face_center[5]) part3 = mesh.clip_closed_surface('-z', origin=face_center[5]) # display sliced parts self.plotter.clear() self.plotter.add_mesh(max_cube, show_edges=True, color="b", opacity=0.6) self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) self.plotter.add_mesh(part1, show_edges=True, color="r", opacity=0.4) self.plotter.add_mesh(part2, show_edges=True, color="w", opacity=0.4) self.plotter.add_mesh(part3, show_edges=True, color="g", opacity=0.4) def slice(self): """ slice the mesh interactively """ # reset plotter self.reset_plotter() self.plotter.add_mesh_slice_orthogonal(mesh) def clip_slice(self): """ slice & clip the mesh interactively """ # reset plotter self.reset_plotter() self.plotter.add_mesh_clip_plane(mesh) def bounding(self, level): level = int(level) bound = mesh.obbTree bound.SetMaxLevel(10) bound.GenerateRepresentation(level, boxes) self.plotter.add_mesh(boxes, opacity=0.2, color="g") return def bounding_bar(self): """ show various levels of OBB (Oriented Bounding Box) interactively """ # initialize bounding boxes mesh global boxes boxes = pv.PolyData() # reset plotter self.reset_plotter() self.plotter.add_slider_widget(self.bounding, [0, 10], title='Level') def closeEvent(self, event): reply = QMessageBox.question(self, "Window Close", "Are you sure you want to quit program?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # inserting maximally inscribed cube via cone intersection # self.max_cube_cone_action = Qt.QAction('Max Cube - Cone', self) # self.max_cube_cone_action.triggered.connect(self.max_cube_cone) # editMenu.addAction(self.max_cube_cone_action) # inserting maximally inscribed cube via ray tracing self.max_cube_ray_action = Qt.QAction('Max Cube - Ray', self) self.max_cube_ray_action.triggered.connect(self.max_cube_ray) editMenu.addAction(self.max_cube_ray_action) # inserting maximally inscribed cube via ray tracing self.ext_max_cube_action = Qt.QAction('Extend Max Cube', self) self.ext_max_cube_action.triggered.connect(self.ext_max_cube) editMenu.addAction(self.ext_max_cube_action) #Create Cone in Mesh self.next_cubes_action = Qt.QAction('Next Cubes', self) self.next_cubes_action.triggered.connect(self.next_cubes_ray) editMenu.addAction(self.next_cubes_action) # slice mesh horizontally based on internal cubes self.cube_hslice_action = Qt.QAction('Cube H-Slice', self) self.cube_hslice_action.triggered.connect(self.cube_hslice) editMenu.addAction(self.cube_hslice_action) # slice mesh (interactively) self.slice_action = Qt.QAction('Slice', self) self.slice_action.triggered.connect(self.slice) editMenu.addAction(self.slice_action) # slice mesh with clipping (interactively) self.clip_slice_action = Qt.QAction('Clip Slice', self) self.clip_slice_action.triggered.connect(self.clip_slice) editMenu.addAction(self.clip_slice_action) # create bounding box(es) for mesh (interactively) self.bounding_action = Qt.QAction('Bounding', self) self.bounding_action.triggered.connect(self.bounding_bar) editMenu.addAction(self.bounding_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, color=None, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box=None, box_args=None) def open_mesh(self): """ add a mesh to the pyqt frame """ global mesh, mesh_vol # open file file_info = QtWidgets.QFileDialog.getOpenFileName() print(file_info) file_path = file_info[0] # determine file type and if conversion needed file_dir, file_name = os.path.split(file_path) mesh_name, mesh_type = os.path.splitext(file_name) # convert mesh file type #if ext != ".vtk" or ext != ".VTK": # mesh = meshio.read(file_path) # meshio.write(root + ".vtk", mesh) # mesh = pv.read(head + "/" + root + ".vtk") # need to store elsewhere or delete .vtk file in the future #else: # mesh = pv.read(file_path) # read mesh & transform according to principal axes pre = trimesh.load_mesh(file_path) orient = pre.principal_inertia_transform pre = pre.apply_transform(orient) pre.export('data/'+ mesh_name + '_oriented.STL') mesh = pv.read('data/'+ mesh_name + '_oriented.STL') # print mesh info print("Mesh Name:", mesh_name) print("Mesh Type:", mesh_type[1:]) # show transformed mesh self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # reset plotter self.reset_plotter() # find mesh centroid and translate the mesh so that's the origin self.centroid() # show bounding box # self.plotter.add_bounding_box(opacity=0.5, color="y") # mesh volume mesh_vol = float(format(mesh.volume, ".5f")) print("Mesh Volume:", mesh_vol) def reset_plotter(self): """ clear plotter of mesh or interactive options """ # clear plotter self.plotter.clear() self.plotter.clear_plane_widgets() self.plotter.reset_camera() # callback opened mesh self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # show origin self.plotter.add_axes_at_origin(xlabel='X', ylabel='Y', zlabel='Z', line_width=6, labels_off=True) # show floors #self.plotter.add_floor('-y') #self.plotter.add_floor('-z') def centroid(self): """ find centroid volumetrically and indicate on graph """ global Vol_centroid, V # find the vertices & the vertex indices of each triangular face V = np.array(mesh.points) col = len(V) f_ind = np.array(mesh.faces.reshape((-1,4))[:, 1:4]) # define an arbitrary start point from middle of max and min of X,Y,Z of # all points: in a convex manifold it falls inside the volume (requires # segmentation for general application) start = np.array(mesh.center) X_start = start[0] Y_start = start[1] Z_start = start[2] # initialize variables centroids = [] Vol_total = 0 Sum_vol_x = 0 Sum_vol_y = 0 Sum_vol_z = 0 # find centroid from all tetrahedra made with arbitrary center and triangular faces for i in range(0, col-1, 3): # find the center of each tetrahedron (average of X,Y,Z of # 4 vertices, 3 from the triangle, and one arbitrary start point) X_cent = (X_start + V[f_ind[i,0],0] + V[f_ind[i+1,0],0] + V[f_ind[i+2,0],0]) / 4 Y_cent = (Y_start + V[f_ind[i,1],1] + V[f_ind[i+1,1],1] + V[f_ind[i+2,1],1]) / 4 Z_cent = (Z_start + V[f_ind[i,2],2] + V[f_ind[i+1,2],2] + V[f_ind[i+2,2],2]) / 4 # compute the volume of each tetrahedron V1 = np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 - np.array([X_start, Y_start, Z_start])**2 V2 = np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 - np.array([V[f_ind[i,0],0], V[f_ind[i,1],1], V[f_ind[i,2],2]])**2 V3 = np.array([V[f_ind[i+2,0],0], V[f_ind[i+2,1],1], V[f_ind[i+2,2],2]])**2 - np.array([V[f_ind[i+1,0],0], V[f_ind[i+1,1],1], V[f_ind[i+1,2],2]])**2 V1 = V1.reshape((-1,1)) V2 = V2.reshape((-1,1)) V3 = V3.reshape((-1,1)) Vol = abs(np.linalg.det(np.hstack([V1, V2, V3]))) / 6 # tally up each cycle Vol_total = Vol_total + Vol Sum_vol_x = Sum_vol_x + Vol * X_cent Sum_vol_y = Sum_vol_y + Vol * Y_cent Sum_vol_z = Sum_vol_z + Vol * Z_cent centroids.append([X_cent,Y_cent,Z_cent]) # find & show centroid centroids = np.asarray(centroids) Vol_centroid = [Sum_vol_x, Sum_vol_y, Sum_vol_z] / Vol_total # print("Total Volume:", Vol_total) # print("Centroid:", Vol_centroid) # def max_cube_cone(self): # """ add a maximally inscribed cube within the opened mesh (via cone intersection) """ # global ranges, max_c1, max_c2, nearest_vert # global face_center, max_cube, max_normal, max_cube_vol # global cube_V, cube_F # global Vol_centroid, V # # find the vertices & the vertex indices of each triangular face # V = np.array(mesh.points) # # reset plotter # self.reset_plotter() # # show centroid # #Vol_centroid = np.array([0,0,0]) # self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) # # project cones to from centroid to find maximally inscribed cube # ranges = mesh.bounds # h = (abs(ranges[4]) + abs(ranges[5]))/3 # ang = np.arctan(0.5/(np.sqrt(2)/2)) # ang = float(90 - np.degrees(ang)) # max_c1 = pv.Cone(center=Vol_centroid+[0,0,h/2], direction=[0,0,-1], height=h, radius=None, capping=False, angle=ang, resolution=100) # max_c2 = pv.Cone(center=Vol_centroid-[0,0,h/2], direction=[0,0,1], height=h, radius=None, capping=False, angle=ang, resolution=100) # self.plotter.add_mesh(max_c1, color="r", show_edges=True, opacity=0.4) # self.plotter.add_mesh(max_c2, color="r", show_edges=True, opacity=0.4) # # find the nearest possible cube vertex from top cone & mesh intersection # top_clip = mesh.clip_surface(max_c1, invert=True) # top_vert = np.array(top_clip.points) # top = self.nearest_pt(top_vert, Vol_centroid) # # find the nearest possible cube vertex from bottom cone & mesh intersection # bottom_clip = mesh.clip_surface(max_c2, invert=True) # bottom_vert = np.array(bottom_clip.points) # bottom = self.nearest_pt(bottom_vert, Vol_centroid) # # show top & bottom clipped surfaces # #self.plotter.add_mesh(top_clip, opacity=0.6, show_edges=True, color="g") # #self.plotter.add_mesh(bottom_clip, opacity=0.6, show_edges=True, color="g") # # find the nearest possible cube vertex between the two # if top[0] < bottom[0]: # p = top[1] # V = top[2] # else: # p = bottom[1] # V = bottom[2] # # create max cube from nearest possible cube vertex # cube_V, cube_F = self.create_cube(V[p,:], Vol_centroid, np.array([0,0,1])) # max_cube = pv.PolyData(cube_V, cube_F) # # show max cube # self.plotter.add_mesh(max_cube, show_edges=True, color="b", opacity=0.6) # # record nearest vertex # nearest_vert = V[p,:] # # find & show max cube face centers # cell_center = max_cube.cell_centers() # face_center = np.array(cell_center.points) # self.plotter.add_mesh(cell_center, color="r", point_size=8.0, render_points_as_spheres=True) # # find max cube face normals # max_normal = max_cube.cell_normals # # max cube volume # max_cube_vol = float(format(max_cube.volume, ".5f")) # print("Max Cube Volume:", max_cube_vol) def max_cube_ray(self): """ add a maximally inscribed cube within the opened mesh (via ray tracing) """ global ranges, nearest_vert global face_center, max_cube, max_normal, max_cube_vol global max_cube_V, max_cube_F global Vol_centroid, V global max_cube_start, max_cube_end, max_cube_run # track starting time max_cube_start = time.time() # find the vertices & the vertex indices of each triangular face V = np.array(mesh.points) # reset plotter self.reset_plotter() # show centroid Vol_centroid = np.array([0,0,0]) self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) # project rays from centroid to find maximally inscribed cube ranges = mesh.bounds # find the nearest possible cube vertex from top rays & mesh intersection top_vert = self.cube_center_ray(Vol_centroid, 'z') top = self.nearest_pt(top_vert, Vol_centroid) # find the nearest possible cube vertex from bottom rays & mesh intersection bottom_vert = self.cube_center_ray(Vol_centroid, '-z') bottom = self.nearest_pt(bottom_vert, Vol_centroid) # find the nearest possible cube vertex between the two if top[0] < bottom[0]: p = top[1] V = top[2] else: p = bottom[1] V = bottom[2] # create and show max cube max_cube_V, max_cube_F, max_cube_vol = self.create_cube(V[p,:], Vol_centroid, np.array([0,0,Vol_centroid[2]])) max_cube = pv.PolyData(max_cube_V, max_cube_F) self.plotter.add_mesh(max_cube, show_edges=True, line_width=3, color="g", opacity=0.6) # record nearest vertex nearest_vert = V[p,:] # find & show max cube face centers cell_center = max_cube.cell_centers() face_center = np.array(cell_center.points) print("Max Cube Face Centers:", face_center) self.plotter.add_mesh(cell_center, color="r", point_size=8, render_points_as_spheres=True) # find max cube face normals max_normal = max_cube.cell_normals print("Max Cube Normals:", max_normal) # max cube volume max_cube_vol = float(format(max_cube_vol, ".5f")) print("Max Cube Volume:", max_cube_vol) # track ending time & duration max_cube_end = time.time() max_cube_run = max_cube_end - max_cube_start def cube_center_ray(self, start, dir): ''' from starting point shoot out 8 rays to find vertices of a possible cube, whose face normals would be in either in x,y,z direction, or rotated 45 deg along z-axis ''' # set ray directions if dir == 'z': r1_dir = np.array([1,1,1]) #r2_dir = np.array([1,0,1]) r3_dir = np.array([1,-1,1]) #r4_dir = np.array([0,-1,1]) r5_dir = np.array([-1,-1,1]) #r6_dir = np.array([-1,0,1]) r7_dir = np.array([-1,1,1]) #r8_dir = np.array([0,1,1]) elif dir == '-z': r1_dir = np.array([1,1,-1]) #r2_dir = np.array([1,0,-1]) r3_dir = np.array([1,-1,-1]) #r4_dir = np.array([0,-1,-1]) r5_dir = np.array([-1,-1,-1]) #r6_dir = np.array([-1,0,-1]) r7_dir = np.array([-1,1,-1]) #r8_dir = np.array([0,1,-1]) # set ray length r_len = abs(ranges[4] - ranges[5])/2 * np.sqrt(0.5**2 + (np.sqrt(2)/2)**2) # set ray end points r1_end = start + r1_dir / np.linalg.norm(r1_dir) * r_len #r2_end = start + r2_dir / np.linalg.norm(r2_dir) * r_len r3_end = start + r3_dir / np.linalg.norm(r3_dir) * r_len #r4_end = start + r4_dir / np.linalg.norm(r4_dir) * r_len r5_end = start + r5_dir / np.linalg.norm(r5_dir) * r_len #r6_end = start + r6_dir / np.linalg.norm(r6_dir) * r_len r7_end = start + r7_dir / np.linalg.norm(r7_dir) * r_len #r8_end = start + r8_dir / np.linalg.norm(r8_dir) * r_len # perform ray trace r1_pts, r1_ind = mesh.ray_trace(start, r1_end) #r2_pts, r2_ind = mesh.ray_trace(start, r2_end) r3_pts, r3_ind = mesh.ray_trace(start, r3_end) #r4_pts, r4_ind = mesh.ray_trace(start, r4_end) r5_pts, r5_ind = mesh.ray_trace(start, r5_end) #r6_pts, r6_nd = mesh.ray_trace(start, r6_end) r7_pts, r7_ind = mesh.ray_trace(start, r7_end) #r8_pts, r8_ind = mesh.ray_trace(start, r8_end) # initialize rays r1 = pv.Line(start, r1_end) #r2 = pv.Line(start, r2_end) r3 = pv.Line(start, r3_end) #r4 = pv.Line(start, r4_end) r5 = pv.Line(start, r5_end) #r6 = pv.Line(start, r6_end) r7 = pv.Line(start, r7_end) #r8 = pv.Line(start, r8_end) # initialize intersections r1_int = pv.PolyData(r1_pts[0]) #r2_int = pv.PolyData(r2_pts[0]) r3_int = pv.PolyData(r3_pts[0]) #r4_int = pv.PolyData(r4_pts[0]) r5_int = pv.PolyData(r5_pts[0]) #r6_int = pv.PolyData(r6_pts[0]) r7_int = pv.PolyData(r7_pts[0]) #r8_int = pv.PolyData(r8_pts[0]) # show rays l_wid = 6 #self.plotter.add_mesh(r1, color='w', line_width=l_wid) #self.plotter.add_mesh(r2, color='w', line_width=l_wid) #self.plotter.add_mesh(r3, color='w', line_width=l_wid) #self.plotter.add_mesh(r4, color='w', line_width=l_wid) #self.plotter.add_mesh(r5, color='w', line_width=l_wid) #self.plotter.add_mesh(r6, color='w', line_width=l_wid) #self.plotter.add_mesh(r7, color='w', line_width=l_wid) #self.plotter.add_mesh(r8, color='w', line_width=l_wid) # show intersections pt_size = 20 #self.plotter.add_mesh(r1_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r2_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r3_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r4_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r5_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r6_int, color='w', point_size=pt_size) #elf.plotter.add_mesh(r7_int, color='w', point_size=pt_size) #self.plotter.add_mesh(r8_int, color='w', point_size=pt_size) # array of intersections # r_int = np.vstack([r1_int.points, r2_int.points, r3_int.points, r4_int.points, # r5_int.points, r6_int.points, r7_int.points, r8_int.points]) r_int = np.vstack([r1_int.points, r3_int.points, r5_int.points, r7_int.points]) return r_int def nearest_pt(self, vert, starting_pt): """ find nearest vertex: for segmented convex manifold, a cube with volume centroid as center and nearest vertex as cube vertex, it falls inside the volume """ # find nearest point from the list of points c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i,0] - starting_pt[0])**2 + (vert[i,1] - starting_pt[1])**2 + (vert[i,2] - starting_pt[2])**2) # find index of the nearest point nearest = min(dist) p = np.where(dist == nearest) p = p[0].item() return nearest, p, vert def rot_axis(self, axis): ''' create a rotational matrix about an arbitrary axis ''' t = sp.Symbol('t') R_t = Matrix([[sp.cos(t)+axis[0]**2*(1-sp.cos(t)), axis[0]*axis[1]*(1-sp.cos(t))-axis[2]*sp.sin(t), axis[0]*axis[2]*(1-sp.cos(t))+axis[1]*sp.sin(t)], [axis[1]*axis[0]*(1-sp.cos(t))+axis[2]*sp.sin(t), sp.cos(t)+axis[1]**2*(1-sp.cos(t)), axis[1]*axis[2]*(1-sp.cos(t))-axis[0]*sp.sin(t)], [axis[2]*axis[0]*(1-sp.cos(t))-axis[1]*sp.sin(t), axis[2]*axis[1]*(1-sp.cos(t))+axis[0]*sp.sin(t), sp.cos(t)+axis[2]**2*(1-sp.cos(t))]]) R = lambdify(t, R_t) return R def create_cube(self, vertex, starting_pt, axis): ''' create cube from the nearest pt & centroid ''' if (axis[0] == 0) and (axis[1] == 0) and (axis[2] == 0): axis[2] = 1 vert_trans = np.array([0,0,0]) elif (Vol_centroid[0] == 0) and (Vol_centroid[1] == 0) and (Vol_centroid[2] == 0): vert_trans = np.array([0,0,0]) else: vert_trans = starting_pt for i in range(0,3): if round(axis[i]) == 1 or round(axis[i]) == -1: vert_trans[i] == 0 # find the other 7 vertices # 3 vertices can be found by rotating the first point 90 degrees 3 times around Z axis of centroid # 4 vertices can be found by translating the first four vertices twice the half edge # found from the distance times sin(pi/4) R = self.rot_axis(axis / np.linalg.norm(axis)) # construct the array of the first 4 vertices V_1 = np.array(vertex - vert_trans) V_2 = np.dot(R(np.pi/2), V_1.T).T V_3 = np.dot(R(np.pi), V_1.T).T V_4 = np.dot(R(3*np.pi/2), V_1.T).T # cube_V_start = np.array([V_1, V_2, V_3, V_4]) cube_V_start = np.array([V_1, V_2, V_3, V_4]) + np.ones((4,1)) * [vert_trans] cube_V_start_center = np.array(pv.PolyData(cube_V_start).center) # show nearest vertex of cube V_1 = np.array(vertex) #self.plotter.add_mesh(pv.PolyData(V_1), color="y", point_size=30.0, render_points_as_spheres=True) # find the translation distance trans_dis = starting_pt - cube_V_start_center trans_dir = trans_dis / np.linalg.norm(trans_dis) dia_dis = np.sqrt((V_1[0]-cube_V_start_center[0])**2 + (V_1[1]-cube_V_start_center[1])**2 + (V_1[2]-cube_V_start_center[2])**2) half_edge = np.ones((4,1)) * [trans_dir] * dia_dis * np.sin(np.pi/4) cube_trans = np.asarray(2*half_edge, dtype=np.float64) # construct the cube cube_V_end = np.add(cube_V_start, cube_trans) cube_V = np.vstack((cube_V_start, cube_V_end)) cube_F = np.hstack([[4,0,1,2,3], [4,0,3,7,4], [4,0,1,5,4], [4,1,2,6,5], [4,2,3,7,6], [4,4,5,6,7]]) # test for cube-ness # face_edge = np.sqrt((V_1[0]-V_2[0])**2 + (V_1[1]-V_2[1])**2 + (V_1[2]-V_2[2])**2) # trans_edge = np.sqrt((V_1[0]-cube_V_end[0][0])**2 + (V_1[1]-cube_V_end[0][1])**2 + (V_1[2]-cube_V_end[0][2])**2) # if round(face_edge, 4) == round(trans_edge, 4): # print('Face Edge = ', round(face_edge, 4)) # print('Translation Edge = ', round(trans_edge, 4)) # print('Cube\n') # else: # print('Face Edge = ', round(face_edge, 4)) # print('Translation Edge = ', round(trans_edge, 4)) # print('Not Cube\n') # cube volume cube_vol = (2 * np.linalg.norm(half_edge[0,:]))**3 return cube_V, cube_F, cube_vol def ext_max_cube(self): ''' extend max cube into maximally inscribed cuboid ''' global face_center, ext_max_cube, max_normal, ext_max_cube_vol # find the 3 out of 6 normal directions the max cube can be extended towards ext_dir = np.empty(shape=(3,3)) main_dir = Vol_centroid - nearest_vert ind = 0 for i in range(0, 6): if np.dot(max_normal[i,:], main_dir) > 0: ext_dir[ind,:] = max_normal[i,:] ind +=1 # extend faces by shooting a ray from the 4 vertices on each extendable face # in the direction of its face normal. Find the nearest intersection and # it would be the limit of extension for that face for i in range(0, 3): F_ind = np.where((max_normal == ext_dir[i]).all(axis=1)) np.reshape(max_cube_F, (6,5)) faces = np.reshape(max_cube_F, (6,5)) V_ind = faces[F_ind][0,1:5] current_V = np.vstack([max_cube_V[V_ind[0]], max_cube_V[V_ind[1]], max_cube_V[V_ind[2]], max_cube_V[V_ind[3]]]) ext_V = self.ext_ray(current_V, ext_dir[i]) max_cube_V[V_ind] = ext_V # create & show extended max cube ext_max_cube = pv.PolyData(max_cube_V, max_cube_F) self.plotter.add_mesh(ext_max_cube, show_edges=True, color="y", opacity=0.6) # find face centers of extended max cube cell_center = ext_max_cube.cell_centers() face_center = np.array(cell_center.points) # find face normals of the extended max cube max_normal = ext_max_cube.cell_normals # extended max cube volume ext_max_cube_vol = float(format(ext_max_cube.volume, ".5f")) print("Extended Max Cube Volume:", ext_max_cube_vol) def ext_ray(self, current_V, ext_dir): ''' shoot rays from vertices of a cube face towards face normal & obtain intersections with mesh ''' # initialize variables ext_end = current_V + ext_dir * np.ones((4,1)) ext_int = [None] * 4 ext_dis = np.zeros(4) # perform ray tracing per extending face vertex for i in range(0,4): ext_int, ind = mesh.ray_trace(current_V[i], ext_end[i]) ext_dis[i] = np.sqrt((ext_int[0][0] - current_V[i][0])**2 + (ext_int[0][1] - current_V[i][1])**2 + (ext_int[0][2] - current_V[i][2])**2) # extend vertices by the shortest intersection distance ext_V = current_V + ext_dir * np.ones((4,1)) * min(ext_dis) return ext_V def next_cubes_ray(self): ''' create cubes within the mesh from the face centers of the first cube''' global next_cube_vol, max_normal # track starting time next_cube_start = time.time() # initiate variable next_cube_vol_sum = 0 # fix max_normal normal = face_center[0] - Vol_centroid if (np.sign(normal[2]) != np.sign(max_normal[0,2])): max_normal = np.negative(max_normal) # loop through all 6 faces of max cube for i in range(0, 6): # create rotaional matrix about max cube normals R = self.rot_axis(max_normal[i]) # initialize variables (8 rays) # r_dir = np.zeros((8, 3)) # r_dir_norm = np.zeros((8, 3)) # r_end = np.zeros((8, 3)) # initialize variables (4 rays) ray_size = np.zeros((4, 3)) r_dir = ray_size #r_flat_dir = ray_size #r_flat_dir_norm = ray_size r_dir_norm = ray_size r_end = ray_size #r_flat_end = ray_size # initialize ray trace parameters l_wid = 5 pt_size = 20 x_range = abs(ranges[0] - ranges[1]) y_range = abs(ranges[2] - ranges[3]) z_range = abs(ranges[4] - ranges[5]) r_len = np.sqrt((x_range/2)**2 + (y_range/2)**2 + (z_range/2)**2) * np.sqrt(1**2 + (np.sqrt(2)/2)**2) r_int = np.array([]) # create a set of 8 rays from each face # for j in range(0, 8): # if j == 0: # if (i == 0) or (i == 5): # r_dir[j] = np.array(max_normal[i] + [0.5,0.5,0]) # r_dir_norm[j] = r_dir[j] / np.linalg.norm(r_dir[j]) # r_end[j] = face_center[i] + r_dir_norm[j] * r_len_z # else: # r_dir[j] = np.array(max_normal[i] + [0,0,np.sqrt(2)/2]) # r_dir_norm[j] = r_dir[j] / np.linalg.norm(r_dir[j]) # r_end[j] = face_center[i] + r_dir_norm[j] * r_len_xy # else: # r_dir[j] = np.dot(R(j*np.pi/4), r_dir[0].T).T # r_dir_norm[j] = r_dir[j] / np.linalg.norm(r_dir[j]) # if (i == 0) or (i == 5): # r_end[j] = face_center[i] + r_dir_norm[j] * r_len_z # else: # r_end[j] = face_center[i] + r_dir_norm[j] * r_len_xy # # perform ray trace # r_pts, r_ind = mesh.ray_trace(face_center[i], r_end[j]) # # show rays # self.plotter.add_mesh(pv.Line(face_center[i], r_end[j]), color='y', line_width=l_wid) # self.plotter.add_mesh(pv.PolyData(r_pts[0]), color='y', point_size=pt_size) # # create an array of ray intersections # r_int = np.append(r_int, r_pts[0]) for j in range(0, 4): if j == 0: r_dir[0] = np.array(max_normal[i] + ([1,1,1] - abs(max_normal[i])) / 2) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = face_center[i] + r_dir_norm[0] * r_len else: r_end[j] = np.dot(R(j*np.pi/2), (r_end[0]-Vol_centroid).T).T r_end[j] = r_end[j] + Vol_centroid # max_cube_faces = np.reshape(max_cube_F, (6,5)) # r_flat_dir[j] = max_cube_V[max_cube_faces[i,j+1]] - face_center[i] # perform ray trace r_pts, r_ind = mesh.ray_trace(face_center[i], r_end[j]) # r_flat_dir_norm[j] = r_flat_dir[j] / np.linalg.norm(r_flat_dir[j]) # r_flat_end[j] = face_center[i] + r_flat_dir_norm[j] * r_len # r_flat_pts, r_flat_ind = mesh.ray_trace(face_center[i], r_flat_end[j]) # show rays # self.plotter.add_mesh(pv.Line(face_center[i], r_end[j]), color='w', line_width=l_wid) # self.plotter.add_mesh(pv.PolyData(r_pts[0]), color='w', point_size=pt_size) # self.plotter.add_mesh(pv.Line(face_center[i], r_flat_end[j]), color='w', line_width=l_wid) # self.plotter.add_mesh(pv.PolyData(r_flat_pts[0]), color='w', point_size=pt_size) # create an array of ray intersections r_int = np.append(r_int, r_pts[0]) # r_int = np.append(r_int, r_flat_pts[0]) # find nearest vertice among the ray intersections r_int = np.reshape(r_int, (4,3)) r = self.nearest_pt(r_int, face_center[i]) # create cube from nearest vertice next_cube_V, next_cube_F, next_cube_vol = self.create_cube(r[2][r[1],:], face_center[i], max_normal[i]) next_cube = pv.PolyData(next_cube_V, next_cube_F) self.plotter.add_mesh(next_cube, show_edges=True, line_width=3, color="g", opacity=0.6) # show cut # u1 = next_cube.triangulate().boolean_union(mesh) # u2 = u1.boolean_difference(next_cube.triangulate()) #diff = next_cube.delaunay_3d().clip_surface(mesh, invert=False) #self.plotter.clear() #self.plotter.add_mesh(next_cube.triangulate(), show_edges=True, line_width=3, color="g", opacity=0.6) #self.plotter.add_mesh(diff, show_edges=True, line_width=3, color="y", opacity=0.6) # testing # test_cube_V = next_cube_V[:-4,:] # test_cube_F = np.array([4,0,1,2,3]) # test_cube = pv.PolyData(test_cube_V, test_cube_F) # self.plotter.add_mesh(test_cube, show_edges=True, line_width=3, color="g", opacity=0.6) # show next cube #self.plotter.add_mesh(next_cube, show_edges=True, line_width=3, color="g", opacity=0.6) # next cube volume next_cube_vol_sum = next_cube_vol_sum + next_cube_vol # show packing efficiency next_cube_vol_sum = float(format(next_cube_vol_sum, ".5f")) pack_vol = float(format((max_cube_vol + next_cube_vol_sum), ".5f")) pack_percent = "{:.1%}".format(pack_vol / mesh_vol) print("Next Cubes Volume:", next_cube_vol_sum) print("Packed Volume:", pack_vol) print("Packing Efficiency:", pack_percent) # track starting time next_cube_end = time.time() next_cube_run = next_cube_end - next_cube_start print("Total elapsed run time: %g seconds" % (max_cube_run + next_cube_run)) def cube_hslice(self): """ slice mesh horizontally based on internal cubes """ # reset plotter self.reset_plotter() # create sliced parts part1 = mesh.clip_closed_surface('z', origin=face_center[0]) part2_a = mesh.clip_closed_surface('-z', origin=face_center[0]) part2 = part2_a.clip_closed_surface('z', origin=face_center[5]) part3 = mesh.clip_closed_surface('-z', origin=face_center[5]) # display sliced parts self.plotter.clear() self.plotter.add_mesh(max_cube, show_edges=True, color="b", opacity=0.6) self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) self.plotter.add_mesh(part1, show_edges=True, color="r", opacity=0.4) self.plotter.add_mesh(part2, show_edges=True, color="w", opacity=0.4) self.plotter.add_mesh(part3, show_edges=True, color="g", opacity=0.4) # volume test chunck_vol = format(part2.volume, ".5f") print(chunck_vol) def slice(self): """ slice the mesh interactively """ # reset plotter self.reset_plotter() self.plotter.add_mesh_slice_orthogonal(mesh) def clip_slice(self): """ slice & clip the mesh interactively """ # reset plotter self.reset_plotter() self.plotter.add_mesh_clip_plane(mesh) def bounding(self, level): level = int(level) bound = mesh.obbTree bound.SetMaxLevel(10) bound.GenerateRepresentation(level, boxes) self.plotter.add_mesh(boxes, opacity=0.2, color="g") return def bounding_bar(self): """ show various levels of OBB (Oriented Bounding Box) interactively """ # initialize bounding boxes mesh global boxes boxes = pv.PolyData() # reset plotter self.reset_plotter() self.plotter.add_slider_widget(self.bounding, [0, 10], title='Level') def closeEvent(self, event): reply = QMessageBox.question(self, "Window Close", "Are you sure you want to quit program?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
def __init__(self, datalog=None, parent=None, show=True): self.time_speed = 1 self.earth_av = 7.2921150 * 360.0 * 1e-5 / (2 * np.pi) self.init_sideral = 0 self.current_sideral = 0 self.vector_point = np.zeros(3) self.datalog_flag = False self.show_ref_vector_point = False self.run_flag = False self.pause_flag = False self.stop_flag = False self.gs_flag = False self.thread = None self.countTime = 0 self.last_index = 0 self.max_index = 0 self.screen = None self.simulation_index = -1 self.q_t_i2b = None self.spacecraft_pos_i = None self.datalog = datalog self.data_handler = None QMainWindow.__init__(self, parent) self.window = Ui_MainWindow() self.window.setupUi(self) self.quaternion_t0 = None # Set-up signals and slots # self.window.actionGeneratePlot.triggered.connect(self.plot_slot) # self.window.control_spinbox.valueChanged.connect(self.window.control_slider.setValue) # self.window.control_slider.valueChanged.connect(self.window.control_spinbox.setValue) ######################################### vlayout = QVBoxLayout() self.window.view_frame.setLayout(vlayout) self.vtk_widget = QtInteractor(self.window.view_frame, shape=(1, 2)) self.vtk_widget.set_background([0.25, 0.25, 0.25]) vlayout.addWidget(self.vtk_widget) self.preview_plot_widget = QtWidgets.QVBoxLayout( self.window.PlotWidget) self.canvas_ = FigureCanvas(Figure(figsize=(5, 3))) self.preview_plot_widget.addWidget(self.canvas_) self.plot_canvas = self.canvas_.figure.subplots() self.plot_canvas.grid() self.plot_canvas.set_xlabel('Time [s]') GeometricElements.__init__(self, self.vtk_widget) self.add_bar() self.add_i_frame_attitude() self.add_gs_item() # -------------------------------------------------------------------------------------------------------------- # simple menu to functions main_menu = self.menuBar() # -------------------------------------------------------------------------------------------------------------- # File option self.window.actionLoadCsv.triggered.connect(self.load_csv_file) if datalog is not None: self.load_csv_file() # -------------------------------------------------------------------------------------------------------------- self.window.actionRun.triggered.connect(self.run_simulation) self.window.actionPause.triggered.connect(self.pause_simulation) self.window.actionStop.triggered.connect(self.stop_simulation) self.window.actionAddGS.triggered.connect(self.add_gs_item) # sim_menu.addAction(run_action) # sim_menu.addAction(pause_action) # sim_menu.addAction(stop_action) # -------------------------------------------------------------------------------------------------------------- self.window.actionGeneratePlot.triggered.connect(self.add_graph2d) self.window.PlotSelectedData.clicked.connect(self.plot_selected_data) self.window.listWidget.clicked.connect(self.preview_plot_data)
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() # add the pyvista interactor object self.plotter = QtInteractor(self.frame) vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # simple menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') editMenu = mainMenu.addMenu('Edit') # opening a mesh file self.open_mesh_action = Qt.QAction('Open Mesh...', self) self.open_mesh_action.triggered.connect(self.open_mesh) fileMenu.addAction(self.open_mesh_action) # exit button exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) # create cubic skeleton self.cubic_skeleton_action = Qt.QAction('Cubic Skeleton', self) self.cubic_skeleton_action.triggered.connect(self.cubic_skeleton) editMenu.addAction(self.cubic_skeleton_action) # split mesh based on max cube faces # self.max_cube_slice_action = Qt.QAction('Slice27', self) # self.cubic_skeleton_action.triggered.connect(self.max_cube_slice) # editMenu.addAction(self.max_cube_slice_action) if show: self.show() self.plotter.add_axes(interactive=None, line_width=2, color=None, x_color=None, y_color=None, z_color=None, xlabel='X', ylabel='Y', zlabel='Z', labels_off=False, box=None, box_args=None) def open_mesh(self): """ add a mesh to the pyqt frame """ global mesh, mesh_vol # open file file_info = QtWidgets.QFileDialog.getOpenFileName() print(file_info) file_path = file_info[0] # determine file type and if conversion needed file_dir, file_name = os.path.split(file_path) mesh_name, mesh_type = os.path.splitext(file_name) # convert mesh file type #if ext != ".vtk" or ext != ".VTK": # mesh = meshio.read(file_path) # meshio.write(root + ".vtk", mesh) # mesh = pv.read(head + "/" + root + ".vtk") # need to store elsewhere or delete .vtk file in the future #else: # mesh = pv.read(file_path) # read mesh & transform according to principal axes pre = trimesh.load_mesh(file_path) orient = pre.principal_inertia_transform pre = pre.apply_transform(orient) pre.export('data/' + mesh_name + '_oriented.STL') mesh = pv.read('data/' + mesh_name + '_oriented.STL') # print mesh info print("Mesh Name:", mesh_name) print("Mesh Type:", mesh_type[1:]) # show transformed mesh #self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # reset plotter self.reset_plotter() # find mesh centroid and translate the mesh so that's the origin self.centroid() # show bounding box # self.plotter.add_bounding_box(opacity=0.5, color="y") # mesh volume mesh_vol = float(format(mesh.volume, ".5f")) print("Mesh Volume:", mesh_vol) def reset_plotter(self): """ clear plotter of mesh or interactive options """ # clear plotter self.plotter.clear() #self.plotter.clear_plane_widgets() #self.plotter.reset_camera() # callback opened mesh self.plotter.add_mesh(mesh, show_edges=True, color="w", opacity=0.6) # show origin self.plotter.add_axes_at_origin(xlabel='X', ylabel='Y', zlabel='Z', line_width=6, labels_off=True) def centroid(self): """ find centroid volumetrically and indicate on graph """ global Vol_centroid, V # find the vertices & the vertex indices of each triangular face V = np.array(mesh.points) col = len(V) f_ind = np.array(mesh.faces.reshape((-1, 4))[:, 1:4]) # define an arbitrary start point from middle of max and min of X,Y,Z of # all points: in a convex manifold it falls inside the volume (requires # segmentation for general application) start = np.array(mesh.center) X_start = start[0] Y_start = start[1] Z_start = start[2] # initialize variables centroids = [] Vol_total = 0 Sum_vol_x = 0 Sum_vol_y = 0 Sum_vol_z = 0 # find centroid from all tetrahedra made with arbitrary center and triangular faces for i in range(0, col - 1, 3): # find the center of each tetrahedron (average of X,Y,Z of # 4 vertices, 3 from the triangle, and one arbitrary start point) X_cent = (X_start + V[f_ind[i, 0], 0] + V[f_ind[i + 1, 0], 0] + V[f_ind[i + 2, 0], 0]) / 4 Y_cent = (Y_start + V[f_ind[i, 1], 1] + V[f_ind[i + 1, 1], 1] + V[f_ind[i + 2, 1], 1]) / 4 Z_cent = (Z_start + V[f_ind[i, 2], 2] + V[f_ind[i + 1, 2], 2] + V[f_ind[i + 2, 2], 2]) / 4 # compute the volume of each tetrahedron V1 = np.array([ V[f_ind[i, 0], 0], V[f_ind[i, 1], 1], V[f_ind[i, 2], 2] ])**2 - np.array([X_start, Y_start, Z_start])**2 V2 = np.array([ V[f_ind[i + 1, 0], 0], V[f_ind[i + 1, 1], 1], V[f_ind[i + 1, 2], 2] ])**2 - np.array( [V[f_ind[i, 0], 0], V[f_ind[i, 1], 1], V[f_ind[i, 2], 2]])**2 V3 = np.array([ V[f_ind[i + 2, 0], 0], V[f_ind[i + 2, 1], 1], V[f_ind[i + 2, 2], 2] ])**2 - np.array([ V[f_ind[i + 1, 0], 0], V[f_ind[i + 1, 1], 1], V[f_ind[i + 1, 2], 2] ])**2 V1 = V1.reshape((-1, 1)) V2 = V2.reshape((-1, 1)) V3 = V3.reshape((-1, 1)) Vol = abs(np.linalg.det(np.hstack([V1, V2, V3]))) / 6 # tally up each cycle Vol_total = Vol_total + Vol Sum_vol_x = Sum_vol_x + Vol * X_cent Sum_vol_y = Sum_vol_y + Vol * Y_cent Sum_vol_z = Sum_vol_z + Vol * Z_cent centroids.append([X_cent, Y_cent, Z_cent]) # find & show centroid centroids = np.asarray(centroids) Vol_centroid = [Sum_vol_x, Sum_vol_y, Sum_vol_z] / Vol_total def cubic_skeleton(self): ''' fill mesh with cubic skeleton''' global max_cube_stored max_cube_stored = 0 # user input number of rays for next cubes # self.plotter.add_text_slider_widget(self.max_cube_ray, ['10 rays','15 rays', '20 rays'], value=0) self.plotter.add_text_slider_widget(self.next_cubes_ray, ['10 rays', '15 rays', '20 rays'], value=1) def max_cube_ray(self, value): """ add a maximally inscribed cube within the opened mesh (via ray tracing) """ global x_range, y_range, z_range, Vol_centroid global face_center, max_normal, max_cube_vol, max_cube global max_cube_start, max_cube_end, max_cube_run global top_rays, top_ints, bottom_rays, bottom_ints # bypass error try: top_rays, top_ints, bottom_rays, bottom_ints, max_cube, r_num, max_cube_stored except NameError: top_rays = None top_ints = None bottom_rays = None bottom_ints = None max_cube = None max_cube_stored = None r_num = 0 # remove old rays if (r_num != 0) and (r_num == int(value[0])): return elif (r_num != 0) and (max_cube_stored != None): self.plotter.remove_actor(max_cube_stored) for i in range(0, r_num): self.plotter.remove_actor(top_rays[i]) self.plotter.remove_actor(top_ints[i]) self.plotter.remove_actor(bottom_rays[i]) self.plotter.remove_actor(bottom_ints[i]) # track starting time max_cube_start = time.time() # find mesh vertices V = np.array(mesh.points) # find the max and min of x,y,z axes of mesh ranges = mesh.bounds x_range = abs(ranges[0] - ranges[1]) y_range = abs(ranges[2] - ranges[3]) z_range = abs(ranges[4] - ranges[5]) # show centroid Vol_centroid = np.array( [0, 0, 0]) # overwrite centroid with origin at principle axes self.plotter.add_mesh(pv.PolyData(Vol_centroid), color='r', point_size=20.0, render_points_as_spheres=True) # find the nearest possible cube vertex from top rays & mesh intersection top_vert, top_rays, top_ints = self.cube_center_ray( Vol_centroid, 'z', value) top = self.furthest_pt(top_vert, Vol_centroid) # find the nearest possible cube vertex from bottom rays & mesh intersection bottom_vert, bottom_rays, bottom_ints = self.cube_center_ray( Vol_centroid, '-z', value) bottom = self.furthest_pt(bottom_vert, Vol_centroid) # find the nearest possible cube vertex between the two if top[0] < bottom[0]: p = top[1] V = top[2] else: p = bottom[1] V = bottom[2] # create and show max cube max_cube_V, max_cube_F, max_cube_vol = self.create_cube( V[p, :], Vol_centroid, np.array([0, 0, Vol_centroid[2]])) max_cube = self.plotter.add_mesh(pv.PolyData(max_cube_V, max_cube_F), show_edges=True, line_width=3, color="g", opacity=0.6) max_cube_stored = max_cube # find & show max cube face centers cell_center = pv.PolyData(max_cube_V, max_cube_F).cell_centers() face_center = np.array(cell_center.points) #self.plotter.add_mesh(cell_center, color="r", point_size=8, render_points_as_spheres=True) # find max cube face normals max_normal = pv.PolyData(max_cube_V, max_cube_F).cell_normals # max cube volume max_cube_vol = float(format(max_cube_vol, ".5f")) print("Max Cube Volume:", max_cube_vol) # track ending time & duration max_cube_end = time.time() max_cube_run = max_cube_end - max_cube_start return def cube_center_ray(self, start, dir, value): ''' from starting point shoot out n rays to find vertices of possible cubes ''' global r_num, r_rot, r_dec # initialize variables idx = value.index(" ") r_num = 0 for i in range(0, idx): r_num = r_num + int(value[i]) + (idx - i)**10 r_rot = np.pi / 2 r_dec = -2 * np.pi / r_num l_wid = 5 pt_size = 20 ray_size = np.zeros((4, 3)) r_dir = ray_size r_dir_norm = ray_size r_end = ray_size rays = [0] * r_num ints = [0] * r_num r_int = [] ori_r_int = [] # set ray length r_len = np.sqrt((x_range / 2)**2 + (y_range / 2)**2 + (z_range / 2)**2) # create rays by rotating the first, which creates the cube with xyz axes as its face normals for i in range(0, r_num): for j in range(0, 4): if (j == 0) and (dir == 'z'): r_dir[0] = np.array([ np.sqrt(2) / 2 * np.cos(np.pi / 4 + r_dec * i), np.sqrt(2) / 2 * np.sin(np.pi / 4 + r_dec * i), 0.5 ]) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = Vol_centroid + r_dir_norm[0] * r_len # set rotation matrix about 'z' R = self.rot_axis(np.array([0, 0, 1])) elif (j == 0) and (dir == '-z'): r_dir[0] = np.array([ np.sqrt(2) / 2 * np.cos(np.pi / 4 + r_dec * i), np.sqrt(2) / 2 * np.sin(np.pi / 4 + r_dec * i), -0.5 ]) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = Vol_centroid + r_dir_norm[0] * r_len # set rotation matrix about '-z' R = self.rot_axis(np.array([0, 0, -1])) else: r_end[j] = np.dot(R(j * r_rot), (r_end[0] - Vol_centroid).T).T r_end[j] = r_end[j] + Vol_centroid # perform ray trace r_pts, r_ind = mesh.ray_trace(Vol_centroid, r_end[j]) # show rays # rays[j] = self.plotter.add_mesh(pv.Line(Vol_centroid, r_end[j]), color='w', line_width=l_wid) # ints[j] = self.plotter.add_mesh(pv.PolyData(r_pts[0]), color='w', point_size=pt_size) # create an array of ray intersections r_int = np.append(r_int, r_pts[0]) r_int = np.reshape(r_int, (4, 3)) ori_nearest, ori_p, ori_V = self.nearest_pt(r_int, Vol_centroid) r_int = [] ori_r_int = np.append(ori_r_int, ori_V[ori_p, :]) ori_r_int = np.reshape(ori_r_int, (r_num, 3)) return ori_r_int, rays, ints def nearest_pt(self, vert, starting_pt): """ find nearest vertex: for segmented convex manifold, a cube with volume centroid as center and nearest vertex as cube vertex, it falls inside the volume """ # find nearest point from the list of points c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i, 0] - starting_pt[0])**2 + (vert[i, 1] - starting_pt[1])**2 + (vert[i, 2] - starting_pt[2])**2) # find index of the nearest point nearest = min(dist) p = np.where(dist == nearest) p = p[0].item() return nearest, p, vert def furthest_pt(self, vert, starting_pt): global p, furthest, dist """ find furthest vertex among the list of nearest vertices """ # find furthest point from the list of points c = len(vert) dist = np.zeros(c) for i in range(0, c): dist[i] = np.sqrt((vert[i, 0] - starting_pt[0])**2 + (vert[i, 1] - starting_pt[1])**2 + (vert[i, 2] - starting_pt[2])**2) # find index of the furthest point furthest = max(dist) p = np.where(dist == furthest) p = p[0][0] return furthest, p, vert def create_cube(self, vertex, starting_pt, axis): ''' create cube from the nearest pt & centroid ''' if (axis[0] == 0) and (axis[1] == 0) and (axis[2] == 0): axis[2] = 1 vert_trans = np.array([0, 0, 0]) elif (starting_pt[0] == 0) and (starting_pt[1] == 0) and (starting_pt[2] == 0): vert_trans = np.array([0, 0, 0]) else: vert_trans = starting_pt for i in range(0, 3): if round(axis[i]) == 1 or round(axis[i]) == -1: vert_trans[i] == 0 # find the other 7 vertices # 3 vertices can be found by rotating the first point 90 degrees 3 times around Z axis of centroid # 4 vertices can be found by translating the first four vertices twice the half edge # found from the distance times sin(pi/4) R = self.rot_axis(axis / np.linalg.norm(axis)) # construct the array of the first 4 vertices V_1 = np.array(vertex - vert_trans) V_2 = np.dot(R(np.pi / 2), V_1.T).T V_3 = np.dot(R(np.pi), V_1.T).T V_4 = np.dot(R(3 * np.pi / 2), V_1.T).T # cube_V_start = np.array([V_1, V_2, V_3, V_4]) cube_V_start = np.array([V_1, V_2, V_3, V_4]) + np.ones( (4, 1)) * [vert_trans] cube_V_start_center = np.array(pv.PolyData(cube_V_start).center) # show nearest vertex of cube V_1 = np.array(vertex) self.plotter.add_mesh(pv.PolyData(V_1), color="y", point_size=30.0, render_points_as_spheres=True) # find the translation distance trans_dis = starting_pt - cube_V_start_center trans_dir = trans_dis / np.linalg.norm(trans_dis) dia_dis = np.sqrt((V_1[0] - cube_V_start_center[0])**2 + (V_1[1] - cube_V_start_center[1])**2 + (V_1[2] - cube_V_start_center[2])**2) half_edge = np.ones((4, 1)) * [trans_dir] * dia_dis * np.sin(np.pi / 4) cube_trans = np.asarray(2 * half_edge, dtype=np.float64) # construct the cube cube_V_end = np.add(cube_V_start, cube_trans) cube_V = np.vstack((cube_V_start, cube_V_end)) cube_F = np.hstack([[4, 0, 1, 2, 3], [4, 0, 3, 7, 4], [4, 0, 1, 5, 4], [4, 1, 2, 6, 5], [4, 2, 3, 7, 6], [4, 4, 5, 6, 7]]) # cube volume cube_vol = (2 * np.linalg.norm(half_edge[0, :]))**3 return cube_V, cube_F, cube_vol def rot_axis(self, axis): ''' create a rotational matrix about an arbitrary axis ''' t = sp.Symbol('t') R_t = Matrix( [[ sp.cos(t) + axis[0]**2 * (1 - sp.cos(t)), axis[0] * axis[1] * (1 - sp.cos(t)) - axis[2] * sp.sin(t), axis[0] * axis[2] * (1 - sp.cos(t)) + axis[1] * sp.sin(t) ], [ axis[1] * axis[0] * (1 - sp.cos(t)) + axis[2] * sp.sin(t), sp.cos(t) + axis[1]**2 * (1 - sp.cos(t)), axis[1] * axis[2] * (1 - sp.cos(t)) - axis[0] * sp.sin(t) ], [ axis[2] * axis[0] * (1 - sp.cos(t)) - axis[1] * sp.sin(t), axis[2] * axis[1] * (1 - sp.cos(t)) + axis[0] * sp.sin(t), sp.cos(t) + axis[2]**2 * (1 - sp.cos(t)) ]]) R = lambdify(t, R_t) return R def next_cubes_ray(self, value): ''' create cubes within the mesh from the face centers of the first cube''' global next_cube_vol, max_normal global next_rays, next_ints, next_cubes # find max cube self.max_cube_ray(value) # # bypass error # try: # next_rays, next_ints, next_cubes, r_num # except NameError: # next_rays = None # next_ints = None # next_cubes = None # r_num = 0 # # remove old rays # if (r_num != 0) and (r_num == int(value[0])): # return # elif (r_num != 0) and (next_cubes != None): # for i in range(0,6): # self.plotter.remove_actor(next_cubes[i]) # for j in range(0, r_num): # self.plotter.remove_actor(next_rays[i*r_num+j]) # self.plotter.remove_actor(next_ints[i*r_num+j]) # track starting time next_cube_start = time.time() # initiate variables next_cube_vol_sum = 0 next_cubes = [0] * 6 next_rays = [0] * 6 * r_num next_ints = [0] * 6 * r_num # fix max_normal normal = face_center[0] - Vol_centroid if (np.sign(normal[2]) != np.sign(max_normal[0, 2])): max_normal = np.negative(max_normal) # loop through all 6 faces of max cube for i in range(0, 6): # create rotaional matrix about max cube normals R = self.rot_axis(max_normal[i]) # initialize variables ray_size = np.zeros((4, 3)) r_dir = ray_size r_dir_norm = ray_size r_end = ray_size # initialize ray trace parameters l_wid = 3 pt_size = 10 r_len = np.sqrt((x_range / 2)**2 + (y_range / 2)**2 + (z_range / 2)**2) r_int = [] ori_r_int = [] for j in range(0, r_num): for k in range(0, 4): if k == 0: if (i == 0) or (i == 5): r_dir[0] = np.array([ np.sqrt(2) / 2 * np.cos(np.pi / 4 + r_dec * j), np.sqrt(2) / 2 * np.sin(np.pi / 4 + r_dec * j), max_normal[i][2] ]) else: x, y = sp.symbols('x,y') f = sp.Eq( max_normal[i][0] * x + max_normal[i][1] * y, 0) g = sp.Eq(x**2 + y**2, 0.5**2) inc = sp.solve([f, g], (x, y)) r_dir[0] = np.array(max_normal[i] + [inc[0][0], inc[0][1], 0.5]) r_dir_norm[0] = r_dir[0] / np.linalg.norm(r_dir[0]) r_end[0] = face_center[i] + r_dir_norm[0] * r_len r_end[0] = np.dot(R(j * r_dec), (r_end[0] - Vol_centroid).T).T else: r_end[k] = np.dot(R(k * r_rot), (r_end[0] - Vol_centroid).T).T r_end[k] = r_end[k] + Vol_centroid # perform ray trace r_pts, r_ind = mesh.ray_trace(face_center[i], r_end[k]) # show rays # next_rays[i*r_num+k] = self.plotter.add_mesh(pv.Line(face_center[i], r_end[k]), color='w', line_width=l_wid) # next_ints[i*r_num+k] = self.plotter.add_mesh(pv.PolyData(r_pts[0]), color='w', point_size=pt_size) # create an array of ray intersections r_int = np.append(r_int, r_pts[0]) # find nearest vertice among the ray intersections r_int = np.reshape(r_int, (4, 3)) ori_nearest, ori_p, ori_V = self.nearest_pt( r_int, face_center[i]) r_int = [] ori_r_int = np.append(ori_r_int, ori_V[ori_p, :]) ori_r_int = np.reshape(ori_r_int, (r_num, 3)) face = self.furthest_pt(ori_r_int, face_center[i]) # create cube from nearest vertice next_cube_V, next_cube_F, next_cube_vol = self.create_cube( face[2][face[1], :], face_center[i], max_normal[i]) next_cubes[i] = self.plotter.add_mesh(pv.PolyData( next_cube_V, next_cube_F), show_edges=True, line_width=3, color="g", opacity=0.6) # next cube volume next_cube_vol_sum = next_cube_vol_sum + next_cube_vol # show packing efficiency next_cube_vol_sum = float(format(next_cube_vol_sum, ".5f")) pack_vol = float(format((max_cube_vol + next_cube_vol_sum), ".5f")) pack_percent = "{:.1%}".format(pack_vol / mesh_vol) print("Next Cubes Volume:", next_cube_vol_sum) print("Packed Volume:", pack_vol) print("Packing Efficiency:", pack_percent) # track starting time next_cube_end = time.time() next_cube_run = next_cube_end - next_cube_start print("Total elapsed run time: %g seconds" % (max_cube_run + next_cube_run)) return def closeEvent(self, event): reply = QMessageBox.question(self, "Window Close", "Are you sure you want to quit program?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
def __init__(self, parent=None, show=True, winsize=(640,480), campos=[(0.0,0.0,-5.0),(0.0,0.0,-2.0),(0.0,-1.0,0.0)],kinectid=0): ''' Basic class to show 3D projection :param parent: inherit from Qt.Mainvindow :param show: ''' Qt.QMainWindow.__init__(self, parent) self.pc_points = None self.pc_colors = None # initial size self.setMinimumSize(winsize[0],winsize[1]) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() vlayout.setContentsMargins(0,0,0,0) #update rate self.UPDATE_RATE = 80 # add a menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) self.add_save_action = Qt.QAction('Save out.ply & out.jpg', self) self.add_save_action.triggered.connect(self.save_ply) fileMenu.addAction(self.add_save_action) # add the pyvista interactor object self.plotter = QtInteractor(self.frame,auto_update=0.0,point_smoothing=False) self.plotter.show_axes_all() # give depth perspective for the points #self.plotter.disable_eye_dome_lighting() self.plotter.enable_depth_peeling() self.plotter.disable_parallel_projection() # set initial camera pozition self.cpos = np.array(campos) self.plotter.camera_position = self.cpos vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # start serving the kinect frames self.kin = KinectHandler(kinectid) # set up timer for cyclic update timer = QtCore.QTimer(self) timer.timeout.connect(self.PlotUpdate) timer.start(self.UPDATE_RATE) if show: self.show()
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True, winsize=(640,480), campos=[(0.0,0.0,-5.0),(0.0,0.0,-2.0),(0.0,-1.0,0.0)],kinectid=0): ''' Basic class to show 3D projection :param parent: inherit from Qt.Mainvindow :param show: ''' Qt.QMainWindow.__init__(self, parent) self.pc_points = None self.pc_colors = None # initial size self.setMinimumSize(winsize[0],winsize[1]) # create the frame self.frame = Qt.QFrame() vlayout = Qt.QVBoxLayout() vlayout.setContentsMargins(0,0,0,0) #update rate self.UPDATE_RATE = 80 # add a menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) self.add_save_action = Qt.QAction('Save out.ply & out.jpg', self) self.add_save_action.triggered.connect(self.save_ply) fileMenu.addAction(self.add_save_action) # add the pyvista interactor object self.plotter = QtInteractor(self.frame,auto_update=0.0,point_smoothing=False) self.plotter.show_axes_all() # give depth perspective for the points #self.plotter.disable_eye_dome_lighting() self.plotter.enable_depth_peeling() self.plotter.disable_parallel_projection() # set initial camera pozition self.cpos = np.array(campos) self.plotter.camera_position = self.cpos vlayout.addWidget(self.plotter.interactor) self.frame.setLayout(vlayout) self.setCentralWidget(self.frame) # start serving the kinect frames self.kin = KinectHandler(kinectid) # set up timer for cyclic update timer = QtCore.QTimer(self) timer.timeout.connect(self.PlotUpdate) timer.start(self.UPDATE_RATE) if show: self.show() def PlotUpdate(self): ''' Plot the point cloud :return: ''' self.pc_points, self.pc_colors = self.kin.get_registred_depth_rgb() if self.pc_colors is not None and self.pc_colors is not None: # clean the view self.plotter.clear() point_cloud = pv.PolyData(self.pc_points) if args.rgb == 'c': point_cloud["colors"] = self.pc_colors.reshape(-1,3) else: dc = cv2.normalize(self.pc_points[:, -1], None, 0.0, 1.0, cv2.NORM_MINMAX) point_cloud["colors"] = dc # add data to be visualized self.plotter.add_mesh(point_cloud, point_size=2.0, lighting=False, render_points_as_spheres=False, color=True, interpolate_before_map=False, show_scalar_bar=False) def save_ply(self): ''' Save curent layout to an out.ply file :return: ''' ply_header = '''ply format ascii 1.0 element vertex %(vert_num)d property float x property float y property float z property uchar red property uchar green property uchar blue end_header ''' verts = self.pc_points.reshape(-1, 3) colors = np.ones(verts.shape) verts = np.hstack([verts, self.pc_colors.reshape(-1,3)]) with open('out.ply', 'wb') as f: f.write((ply_header % dict(vert_num=len(verts))).encode('utf-8')) np.savetxt(f, verts, fmt='%f %f %f %d %d %d ') #save image img = cv2.cvtColor(self.kin.rgb,cv2.COLOR_BGR2RGB) cv2.imwrite('out.jpg',img)
def createUI(self): self.x1 = self.createSlider(0, 0, self.dims[0]) self.x2 = self.createSlider(self.dims[0], 0, self.dims[0]) self.y1 = self.createSlider(0, 0, self.dims[1]) self.y2 = self.createSlider(self.dims[1], 0, self.dims[1]) self.z1 = self.createSlider(0, 0, self.dims[2]) self.z2 = self.createSlider(self.dims[2], 0, self.dims[2]) self.field = QComboBox() for name in self.db.getFields(): self.field.addItem(name) self.field.setCurrentText(self.db.getFields()[0]) self.type = QComboBox() for name in ('vr', 'threshold', 'isovalue', 'slice'): self.type.addItem(name) self.type.setCurrentText('vr') self.opacity = QComboBox() for name in ('linear', 'geom', 'geom_r', 'sigmoid', 'sigmoid_3', 'sigmoid_4', 'sigmoid_5', 'sigmoid_6', 'sigmoid_7', 'sigmoid_8', 'sigmoid_9', 'sigmoid_10'): self.opacity.addItem(name) self.opacity.setCurrentText('sigmoid') self.cmap = QComboBox() for name in ('gist_ncar', 'bone', 'cool', 'viridis', 'coolwarm', 'magma'): self.cmap.addItem(name) self.cmap.setCurrentText('gist_ncar') self.blending = QComboBox() for name in ('additive', 'maximum', 'minimum', 'composite', 'average'): self.blending.addItem(name) self.blending.setCurrentText('additive') self.max_mb = QComboBox() for name in ('8', '16', '32', '64', '128', '256', '512', '1024', '2048'): self.max_mb.addItem(name) self.max_mb.setCurrentText('32') self.run = QPushButton("Run") self.run.clicked.connect(self.showVolume) main_layout = QVBoxLayout() # plotter if True: frame = QFrame() self.plotter = QtInteractor(frame) main_layout.addWidget(self.plotter) if True: layout = self.createVerticalLayout([ self.createGridLayout( ([QLabel("x1"), self.x1, QLabel("x2"), self.x2], [QLabel("y1"), self.y1, QLabel("y2"), self.y2], [QLabel("z1"), self.z1, QLabel("z2"), self.z2])), self.createHorizontalLayout([QLabel("field"), self.field]), self.createHorizontalLayout([QLabel("type"), self.type]), self.createHorizontalLayout([QLabel("opacity"), self.opacity]), self.createHorizontalLayout([QLabel("cmap"), self.cmap]), self.createHorizontalLayout( [QLabel("blending"), self.blending]), self.createHorizontalLayout([QLabel("MBytes"), self.max_mb]), self.run ]) main_layout.addLayout(layout) central_widget = QFrame() central_widget.setLayout(main_layout) self.setCentralWidget(central_widget) self.showMaximized()
class Explorer3d(QMainWindow): # construtor def __init__(self, url, parent=None): super().__init__(parent) self.db = LoadDataset(url) self.dims = self.db.getLogicBox()[1] print("dims", self.dims) self.createUI() self.showVolume() self.show() # createUI def createUI(self): self.x1 = self.createSlider(0, 0, self.dims[0]) self.x2 = self.createSlider(self.dims[0], 0, self.dims[0]) self.y1 = self.createSlider(0, 0, self.dims[1]) self.y2 = self.createSlider(self.dims[1], 0, self.dims[1]) self.z1 = self.createSlider(0, 0, self.dims[2]) self.z2 = self.createSlider(self.dims[2], 0, self.dims[2]) self.field = QComboBox() for name in self.db.getFields(): self.field.addItem(name) self.field.setCurrentText(self.db.getFields()[0]) self.type = QComboBox() for name in ('vr', 'threshold', 'isovalue', 'slice'): self.type.addItem(name) self.type.setCurrentText('vr') self.opacity = QComboBox() for name in ('linear', 'geom', 'geom_r', 'sigmoid', 'sigmoid_3', 'sigmoid_4', 'sigmoid_5', 'sigmoid_6', 'sigmoid_7', 'sigmoid_8', 'sigmoid_9', 'sigmoid_10'): self.opacity.addItem(name) self.opacity.setCurrentText('sigmoid') self.cmap = QComboBox() for name in ('gist_ncar', 'bone', 'cool', 'viridis', 'coolwarm', 'magma'): self.cmap.addItem(name) self.cmap.setCurrentText('gist_ncar') self.blending = QComboBox() for name in ('additive', 'maximum', 'minimum', 'composite', 'average'): self.blending.addItem(name) self.blending.setCurrentText('additive') self.max_mb = QComboBox() for name in ('8', '16', '32', '64', '128', '256', '512', '1024', '2048'): self.max_mb.addItem(name) self.max_mb.setCurrentText('32') self.run = QPushButton("Run") self.run.clicked.connect(self.showVolume) main_layout = QVBoxLayout() # plotter if True: frame = QFrame() self.plotter = QtInteractor(frame) main_layout.addWidget(self.plotter) if True: layout = self.createVerticalLayout([ self.createGridLayout( ([QLabel("x1"), self.x1, QLabel("x2"), self.x2], [QLabel("y1"), self.y1, QLabel("y2"), self.y2], [QLabel("z1"), self.z1, QLabel("z2"), self.z2])), self.createHorizontalLayout([QLabel("field"), self.field]), self.createHorizontalLayout([QLabel("type"), self.type]), self.createHorizontalLayout([QLabel("opacity"), self.opacity]), self.createHorizontalLayout([QLabel("cmap"), self.cmap]), self.createHorizontalLayout( [QLabel("blending"), self.blending]), self.createHorizontalLayout([QLabel("MBytes"), self.max_mb]), self.run ]) main_layout.addLayout(layout) central_widget = QFrame() central_widget.setLayout(main_layout) self.setCentralWidget(central_widget) self.showMaximized() # getVolumeBounds def getVolumeBounds(self): x1 = self.x1.value() x2 = self.x2.value() x1, x2 = min(x1, x2), max(x1, x2) y1 = self.x1.value() y2 = self.x2.value() y1, y2 = min(y1, y2), max(y1, y2) z1 = self.x1.value() z2 = self.x2.value() z1, z2 = min(z1, z2), max(z1, z2) return x1, x2, y1, y2, z1, z2 # extractVolume def extractVolume(self): field = self.db.getField(self.field.currentText()) x1, x2, y1, y2, z1, z2 = self.getVolumeBounds() # guess quality dtype = field.dtype sample_size = dtype.getByteSize() mem_size = int( (x2 - x1) * (y2 - y1) * (z2 - z1) * sample_size / (1024 * 1024)) max_mb = int(self.max_mb.currentText()) quality = 0 while mem_size > max_mb: quality -= 1 mem_size = int(mem_size / 2) data = self.db.read(x=[x1, x2], y=[y1, y2], z=[z1, z2], field=self.field.currentText(), quality=quality) print("Extracted volume", "shape", data.shape, "dtype", data.dtype, "mb", int(sys.getsizeof(data) / (1024 * 1024)), "quality", quality) return data # showVolume def showVolume(self): data = self.extractVolume() self.plotter.clear() # self.plotter.show_bounds(grid=True, location='back') grid = pv.UniformGrid() grid.dimensions = numpy.array(data.shape) + 1 x1, x2, y1, y2, z1, z2 = self.getVolumeBounds() grid.origin = (0, 0, 0) # The bottom left corner of the data set w, h, d = data.shape grid.spacing = ((x2 - x1) / w, (y2 - y1) / h, (z2 - z1) / d ) # These are the cell sizes along each axis grid.cell_arrays["values"] = data.flatten(order="F") # ///////////////////////////////////////////////////////// # example https://docs.pyvista.org/examples/00-load/create-uniform-grid.html if self.type.currentText() == 'vr': self.plotter.add_volume(grid, opacity=self.opacity.currentText(), cmap=self.cmap.currentText(), blending=self.blending.currentText()) elif self.type.currentText() == 'threshold': self.plotter.add_mesh_threshold(grid) elif self.type.currentText() == 'isovalue': self.plotter.add_mesh_isovalue( pv.wrap(data)) # TODO, aspect ratio not working elif self.type.currentText() == 'slice': self.plotter.add_mesh_slice(grid) else: raise Exception("internal error") self.plotter.reset_camera() # createSlider def createSlider(self, value, m, M): ret = QSlider(Qt.Horizontal, self) ret.setRange(m, M) ret.setValue(value) ret.setFocusPolicy(Qt.NoFocus) ret.setPageStep(5) # ret.valueChanged.connect(self.showVolume) return ret # createHorizontalLayout def createHorizontalLayout(self, widgets): ret = QHBoxLayout() for widget in widgets: try: ret.addWidget(widget) except: ret.addLayout(widget) return ret # createVerticalLayout def createVerticalLayout(self, widgets): ret = QVBoxLayout() for widget in widgets: try: ret.addWidget(widget) except: ret.addLayout(widget) return ret # createGridLayout def createGridLayout(self, rows): ret = QGridLayout() nrow = 0 for row in rows: ncol = 0 for widget in row: if widget: ret.addWidget(widget, nrow, ncol) ncol += 1 nrow += 1 return ret
class Viewer(GeometricElements, QtWidgets.QMainWindow): def __init__(self, datalog=None, parent=None, show=True): self.time_speed = 1 self.earth_av = 7.2921150 * 360.0 * 1e-5 / (2 * np.pi) self.init_sideral = 0 self.current_sideral = 0 self.vector_point = np.zeros(3) self.datalog_flag = False self.show_ref_vector_point = False self.run_flag = False self.pause_flag = False self.stop_flag = False self.gs_flag = False self.thread = None self.countTime = 0 self.last_index = 0 self.max_index = 0 self.screen = None self.simulation_index = -1 self.q_t_i2b = None self.spacecraft_pos_i = None self.datalog = datalog self.data_handler = None QMainWindow.__init__(self, parent) self.window = Ui_MainWindow() self.window.setupUi(self) self.quaternion_t0 = None # Set-up signals and slots # self.window.actionGeneratePlot.triggered.connect(self.plot_slot) # self.window.control_spinbox.valueChanged.connect(self.window.control_slider.setValue) # self.window.control_slider.valueChanged.connect(self.window.control_spinbox.setValue) ######################################### vlayout = QVBoxLayout() self.window.view_frame.setLayout(vlayout) self.vtk_widget = QtInteractor(self.window.view_frame, shape=(1, 2)) self.vtk_widget.set_background([0.25, 0.25, 0.25]) vlayout.addWidget(self.vtk_widget) self.preview_plot_widget = QtWidgets.QVBoxLayout( self.window.PlotWidget) self.canvas_ = FigureCanvas(Figure(figsize=(5, 3))) self.preview_plot_widget.addWidget(self.canvas_) self.plot_canvas = self.canvas_.figure.subplots() self.plot_canvas.grid() self.plot_canvas.set_xlabel('Time [s]') GeometricElements.__init__(self, self.vtk_widget) self.add_bar() self.add_i_frame_attitude() self.add_gs_item() # -------------------------------------------------------------------------------------------------------------- # simple menu to functions main_menu = self.menuBar() # -------------------------------------------------------------------------------------------------------------- # File option self.window.actionLoadCsv.triggered.connect(self.load_csv_file) if datalog is not None: self.load_csv_file() # -------------------------------------------------------------------------------------------------------------- self.window.actionRun.triggered.connect(self.run_simulation) self.window.actionPause.triggered.connect(self.pause_simulation) self.window.actionStop.triggered.connect(self.stop_simulation) self.window.actionAddGS.triggered.connect(self.add_gs_item) # sim_menu.addAction(run_action) # sim_menu.addAction(pause_action) # sim_menu.addAction(stop_action) # -------------------------------------------------------------------------------------------------------------- self.window.actionGeneratePlot.triggered.connect(self.add_graph2d) self.window.PlotSelectedData.clicked.connect(self.plot_selected_data) self.window.listWidget.clicked.connect(self.preview_plot_data) # -------------------------------------------------------------------------------------------------------------- def add_item_to_list(self): _translate = QtCore.QCoreApplication.translate for elem in self.data_handler.auxiliary_datalog_keys: item = QtWidgets.QListWidgetItem() item.setCheckState(QtCore.Qt.Unchecked) item.setText(_translate("MainWindow", elem)) self.window.listWidget.addItem(item) flat_basic_list = [ item for sublist in self.data_handler.basic_datalog_keys[1:] for item in sublist ] for elem in flat_basic_list: item = QtWidgets.QListWidgetItem() item.setCheckState(QtCore.Qt.Unchecked) item.setText(_translate("MainWindow", elem)) self.window.listWidget.addItem(item) return def add_graph2d(self): self.screen = MainGraph(self.data_handler) self.screen.win.show() def load_csv_file(self): def read_data(file_path): df = pd.read_csv(file_path, delimiter=',') sim_data = df return sim_data options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog filename, _ = QFileDialog.getOpenFileName(self, "Select CSV data file", "", "CSV Files (*.csv)", options=options) if filename: if self.datalog is not None: self.window.listWidget.clear() self.datalog = read_data(filename) self.datalog_flag = True print('Data log loaded') else: print('Could not load data log') if self.datalog_flag: self.data_handler = DataHandler(self.datalog) self.data_handler.create_variable() # Add actors to orbit view self.spacecraft_pos_i = np.array([ self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[2][0]], self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[2][1]], self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[2][2]] ]).transpose() self.max_index = len(self.spacecraft_pos_i) self.add_orbit(self.spacecraft_pos_i) # self.add_aries_arrow() # Add actors to attitude view self.q_t_i2b = np.array([ self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[4][0]], self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[4][1]], self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[4][2]], self.data_handler.basic_datalog[ self.data_handler.basic_datalog_keys[4][3]] ]).transpose() self.add_spacecraft_2_orbit(self.spacecraft_pos_i[0, :], self.q_t_i2b) self.add_spacecraft_2_attitude(self.q_t_i2b) try: date_time_data = self.data_handler.auxiliary_datalog[ 'Date time'] init_time = date_time_data[0] datetime_array = datetime.strptime(init_time, '%Y-%m-%d %H:%M:%S') except KeyError: dp = InitDateForm() # self.vtk_widget.setWindowModality(QtCore.Qt.ApplicationModal) dp.exec() print(dp.current_date) init_time = dp.current_date.toUTC().toString(Qt.ISODate) datetime_array = datetime.strptime(init_time, '%Y-%m-%dT%H:%M:%SZ') init_jd = self.jday(datetime_array.year, datetime_array.month, datetime_array.day, datetime_array.hour, datetime_array.minute, datetime_array.second) self.init_sideral = rad2deg * self.gstime(init_jd) self.sphere.rotate_z(self.init_sideral) if self.gs_flag: self.update_gs_location(rad2deg * self.gstime(init_jd)) if 'Vector_tar_i(X) [-]' in self.data_handler.auxiliary_datalog.keys( ): self.vector_point = np.array([ self.data_handler.auxiliary_datalog['Vector_tar_i(X) [-]'] [0], self.data_handler.auxiliary_datalog['Vector_tar_i(Y) [-]'] [0], self.data_handler.auxiliary_datalog['Vector_tar_i(Z) [-]'] [0] ]) if np.linalg.norm(self.vector_point) != 0: self.show_ref_vector_point = True self.add_vector_line_in_orbit(self.spacecraft_pos_i[0, :], self.vector_point) self.add_b_frame_attitude( show_ref_vector_point=self.show_ref_vector_point, vector_point=self.vector_point) self.add_item_to_list() def run_simulation(self): self.run_flag = True self.stop_flag = False self.pause_flag = False self.run_orbit_3d() print('Running...') def pause_simulation(self): self.pause_flag = True self.run_flag = False print('Paused...') def stop_simulation(self): self.stop_flag = True if self.thread: self.thread.join() self.thread = None self.update_meshes(0) self.reset_time() def reset_time(self): self.countTime = 0 self.simulation_index = 1 def update_time(self): self.countTime = self.data_handler.stepTime * self.simulation_index def update_meshes(self, index): if index == 0: print('Resetting') self.sphere.rotate_z(-self.current_sideral) tr_vector = self.spacecraft_pos_i[0, :] - np.array([0, 0, 34 / 2]) self.spacecraft_model_2_orbit.translate( -self.spacecraft_model_2_orbit.center_of_mass()) self.update_attitude(index, self.spacecraft_model_2_attitude, self.spacecraft_model_2_orbit) self.spacecraft_model_2_orbit.translate( self.spacecraft_model_2_orbit.center_of_mass()) if self.show_ref_vector_point: self.update_vector_line(self.spacecraft_pos_i[index, :], self.vector_point) self.spacecraft_model_2_orbit.translate(tr_vector) self.body_x_i.translate(tr_vector) self.body_y_i.translate(tr_vector) self.body_z_i.translate(tr_vector) else: # Update Earth sideral = self.earth_av * self.data_handler.stepTime * int( self.time_speed) self.current_sideral += sideral self.sphere.rotate_z(sideral) if self.gs_flag: self.update_gs_location(sideral) # Update Orbit tr_vector = self.spacecraft_pos_i[ index, :] - self.spacecraft_pos_i[self.last_index, :] self.spacecraft_model_2_orbit.translate( -self.spacecraft_pos_i[self.last_index, :]) self.update_attitude(index, self.spacecraft_model_2_attitude, self.spacecraft_model_2_orbit) self.spacecraft_model_2_orbit.translate( self.spacecraft_pos_i[self.last_index, :]) if self.show_ref_vector_point: self.update_vector_line(self.spacecraft_pos_i[index, :], self.vector_point) self.spacecraft_model_2_orbit.translate(tr_vector) self.body_x_i.translate(tr_vector) self.body_y_i.translate(tr_vector) self.body_z_i.translate(tr_vector) # Update widget self.vtk_widget.update() self.last_index = index def rotate_th(self): self.vtk_widget.subplot(0, 0) self.reset_time() while not self.stop_flag and self.countTime < self.data_handler.endTime: self.update_meshes(self.simulation_index) # time.sleep(self.data_handler.stepTime / self.time_speed) time.sleep(self.data_handler.stepTime) # Update time self.simulation_index += 1 * int(self.time_speed) self.update_time() while self.pause_flag: if self.stop_flag: return elif self.run_flag: self.pause_flag = False else: time.sleep(1) def run_orbit_3d(self): if self.thread is None: self.thread = Thread(target=self.rotate_th, daemon=True) self.thread.start() def sim_speed(self, value): self.time_speed = value return def update_vector_line(self, center_point, vector_point): self.vtk_widget.subplot(0, 0) new_points = np.array( [center_point, center_point + 2e7 * vector_point]) self.vector_line_from_sc.points = new_points cells = np.full((len(new_points) - 1, 3), 2, dtype=np.int) cells[:, 1] = np.arange(0, len(new_points) - 1, dtype=np.int) cells[:, 2] = np.arange(1, len(new_points), dtype=np.int) self.vector_line_from_sc.lines = cells def update_gs_location(self, sideral): self.tar_pos_eci.rotate_z(sideral) return def update_attitude(self, n, sc_model1, sc_model2=None): quaternion_tn = Quaternion(self.q_t_i2b[n, :]).unit inv_quaternion = self.quaternion_t0.inverse d_quaternion = quaternion_tn * inv_quaternion k_matrix = d_quaternion.transformation_matrix self.body_x.transform(k_matrix) self.body_y.transform(k_matrix) self.body_z.transform(k_matrix) sc_model1.transform(k_matrix) if sc_model2 is not None: sc_model2.transform(k_matrix) self.quaternion_t0 = quaternion_tn if self.show_ref_vector_point: curr_vector_point = np.array([ self.data_handler.auxiliary_datalog['Vector_tar_i(X) [-]'][n], self.data_handler.auxiliary_datalog['Vector_tar_i(Y) [-]'][n], self.data_handler.auxiliary_datalog['Vector_tar_i(Z) [-]'][n] ]) vec = np.cross(self.vector_point, curr_vector_point) arg = np.dot(self.vector_point, curr_vector_point) if arg > 1.0: arg = 1.0 elif arg < -1.0: arg = -1.0 ang = np.arccos(arg) if np.linalg.norm(vec) == 0: vec = np.array([0, 0, 1]) self.body_ref_point.transform( Quaternion(axis=vec, angle=ang).transformation_matrix) self.vector_point = curr_vector_point def preview_plot_data(self): self.plot_canvas.cla() is_data = False self.plot_canvas.grid() self.plot_canvas.set_xlabel('Time [s]') flat_basic_list = [ item for sublist in self.data_handler.basic_datalog_keys[1:] for item in sublist ] len_aux_keys = len(self.data_handler.auxiliary_datalog_keys) for index in range(self.window.listWidget.count()): if self.window.listWidget.item(index).checkState() == Qt.Checked: if index < len_aux_keys: elem = self.data_handler.auxiliary_datalog_keys[index] self.plot_canvas.plot( self.data_handler.basic_datalog['time[sec]'], self.data_handler.auxiliary_datalog[elem], label=elem) else: elem = flat_basic_list[index - len_aux_keys] self.plot_canvas.plot( self.data_handler.basic_datalog['time[sec]'], self.data_handler.basic_datalog[elem], label=elem) is_data = True if is_data: self.plot_canvas.legend() self.canvas_.draw() return def plot_selected_data(self): plt.figure() plt.grid() plt.xlabel('Time [s]') flat_basic_list = [ item for sublist in self.data_handler.basic_datalog_keys[1:] for item in sublist ] len_aux_keys = len(self.data_handler.auxiliary_datalog_keys) for index in range(self.window.listWidget.count()): if self.window.listWidget.item(index).checkState() == Qt.Checked: if index < len_aux_keys: elem = self.data_handler.auxiliary_datalog_keys[index] plt.plot(self.data_handler.basic_datalog['time[sec]'], self.data_handler.auxiliary_datalog[elem], label=elem) else: elem = flat_basic_list[index - len_aux_keys] plt.plot(self.data_handler.basic_datalog['time[sec]'], self.data_handler.basic_datalog[elem], label=elem) plt.legend() plt.show() return def gstime(self, jdut1): tut1 = (jdut1 - 2451545.0) / 36525.0 temp = -6.2e-6 * tut1 * tut1 * tut1 + 0.093104 * tut1 * tut1 + \ (876600.0 * 3600 + 8640184.812866) * tut1 + 67310.54841 # sec temp = (temp * deg2rad / 240.0) % twopi # 360/86400 = 1/240, to deg, to rad # ------------------------ check quadrants --------------------- if temp < 0.0: temp += twopi return temp def jday(self, year, mon, day, hr, minute, sec): return (367.0 * year - 7.0 * (year + ((mon + 9.0) // 12.0)) * 0.25 // 1.0 + 275.0 * mon // 9.0 + day + 1721013.5 + ((sec / 60.0 + minute) / 60.0 + hr) / 24.0 # ut in days # - 0.5*sgn(100.0*year + mon - 190002.5) + 0.5; )
class MainWindow(Qt.QMainWindow): def __init__(self, parent=None, show=True): Qt.QMainWindow.__init__(self, parent) with open('config.json') as f: self.config_data = json.load(f) self.query_matcher = QueryMatcher(self.config_data["FEATURE_DATA_FILE"]) self.supported_file_types = [".ply", ".off"] self.buttons = {} self.ds = reader.DataSet("") self.meshes = [] self.normalizer = Normalizer() self.smlw = None self.setWindowTitle('Source Mesh Window') self.frame = Qt.QFrame() self.QTIplotter = None self.vlayout = Qt.QVBoxLayout() self.frame.setLayout(self.vlayout) self.setCentralWidget(self.frame) self.hist_dict = {} self.setAcceptDrops(True) # Create main menu mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') exitButton = Qt.QAction('Exit', self) exitButton.setShortcut('Ctrl+Q') exitButton.triggered.connect(self.close) fileMenu.addAction(exitButton) viewMenu = mainMenu.addMenu('View') exitButton = Qt.QAction('Plot tSNE', self) exitButton.triggered.connect(self.plot_tsne) viewMenu.addAction(exitButton) # Create load button and init action self.load_button = QPushButton("Load or drop mesh to query") self.load_button.clicked.connect(lambda: self.load_and_prep_query_mesh(self.open_file_name_dialog())) self.load_button.setFont(QtGui.QFont("arial", 30)) # Create Plots widget self.graphWidget = pg.PlotWidget() self.graphWidget.setBackground('w') # Create and add widgets to layout n_sing, n_hist, mapping_of_labels = get_sizes_features(features_file=self.config_data["FEATURE_DATA_FILE"],with_labels=True) # self.hist_labels = list({**FeatureExtractor.get_pipeline_functions()[1]}.values()) self.hist_labels = [val for key, val in mapping_of_labels.items() if "hist_" in key] self.tableWidget = TableWidget({}, self, {}) self.tableWidget.hide() self.vlayout.addWidget(self.load_button) # Position MainWindow screen_topleft = QDesktopWidget().availableGeometry().topLeft() screen_height = QDesktopWidget().availableGeometry().height() width = (QDesktopWidget().availableGeometry().width() * 0.4) self.move(screen_topleft) self.resize(width, screen_height - 50) if show: self.show() def dragEnterEvent(self, e): if e.mimeData().hasUrls(): e.accept() else: e.ignore() def dropEvent(self, e): if len(e.mimeData().urls()) == 1: file = QUrl.toLocalFile(e.mimeData().urls()[0]) self.load_and_prep_query_mesh(DataSet._read(file)) else: error_dialog = QtWidgets.QErrorMessage(parent=self) error_dialog.showMessage("Please drag only one mesh at the time.") def check_file(self, fileName): if fileName[-4:] not in self.supported_file_types: error_dialog = QtWidgets.QErrorMessage(parent=self) error_dialog.showMessage(("Selected file not supported." f"\nPlease select mesh files of type: {self.supported_file_types}")) return False def open_file_name_dialog(self): fileName, _ = QFileDialog.getOpenFileName(self, caption="Choose shape to view.", filter="All Files (*);; Model Files (.obj, .off, .ply, .stl)") if not (fileName or self.check_file(fileName)): return False mesh = DataSet._read(fileName) return mesh def load_and_prep_query_mesh(self, data): if not data: return self.load_button.setFont(QtGui.QFont("arial", 10)) # Normalize query mesh normed_data = self.normalizer.mono_run_pipeline(data) normed_mesh = pv.PolyData(normed_data["history"][-1]["data"]["vertices"], normed_data["history"][-1]["data"]["faces"]) normed_data['poly_data'] = normed_mesh # Extract features n_singletons, n_distributionals, mapping_of_labels = get_sizes_features(features_file=self.config_data["FEATURE_DATA_FILE"],with_labels=True, drop_feat=["timestamp"]) mapping_of_labels_reversed = {val: key for key, val in mapping_of_labels.items()} features_dict = FeatureExtractor.mono_run_pipeline_old(normed_data) features_dict_carefully_selected = OrderedDict( sorted({mapping_of_labels.get(key): val for key, val in features_dict.items() if key in mapping_of_labels}.items(), key=lambda t: t[0])) features_df = pd.DataFrame([features_dict_carefully_selected]).T.reset_index() self.hist_labels = [val for key, val in mapping_of_labels.items() if "hist_" in key] self.skeleton_labels = [val for key, val in mapping_of_labels.items() if "skeleton_" in key] # feature_formatted_keys = sing_labels + dist_labels # features_df = pd.DataFrame({'key': list(feature_formatted_keys), 'value': list( # [list(f) if isinstance(f, np.ndarray) else f for f in list(features_dict.values())[3:]])}) # Update plotter & feature table # since unfortunately Qtinteractor which plots the mesh cannot be updated (remove and add new mesh) # it needs to be removed and newly generated each time a mesh gets loaded self.tableWidget.deleteLater() self.vlayout.removeWidget(self.QTIplotter) self.QTIplotter = QtInteractor(self.frame) self.vlayout.addWidget(self.QTIplotter) self.tableWidget = TableWidget(features_dict_carefully_selected, self, mapping_of_labels_reversed) self.tableWidget.show() self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) self.vlayout.addWidget(self.tableWidget) self.QTIplotter.add_mesh(normed_mesh, show_edges=True) self.QTIplotter.isometric_view() self.QTIplotter.show_bounds(grid='front', location='outer', all_edges=True) self.vlayout.addWidget(self.graphWidget) # Compare shapes if self.smlw: self.smlw.deleteLater() if len(self.smlw.smw_list) != 0: self.smlw.smw_list[0].deleteLater() self.smlw = SimilarMeshesListWindow(features_dict) self.buttons = self.tableWidget.get_buttons_in_table() self.hist_dict = features_df.set_index("index").tail(n=len(self.hist_labels)).to_dict() for key, value in self.buttons.items(): value.clicked.connect(lambda state, x=key, y=features_dict_carefully_selected[key]: self.plot_selected_hist(x, y)) self.smlw.show() def plot_selected_hist(self, hist_title, hist_data): self.graphWidget.clear() styles = {"color": "#f00", "font-size": "15px"} pen = pg.mkPen(color=(255, 0, 0), width=5, style=QtCore.SolidLine) self.graphWidget.setTitle(hist_title, color="b", size="15pt") self.graphWidget.setLabel("left", "Values", **styles) self.graphWidget.setLabel("bottom", "Bins", **styles) self.graphWidget.addLegend() self.graphWidget.showGrid(x=True, y=True) self.graphWidget.setXRange(1, len(hist_data)) self.graphWidget.setYRange(min(hist_data), max(hist_data)) self.graphWidget.plot(np.arange(0, len(hist_data)), hist_data, pen=pen) def plot_tsne(self): labels = [dic["label"].replace("_", " ").title() for dic in self.query_matcher.features_raw] filename = "tsne_visualizer" tsne_plotter = TsneVisualiser( self.query_matcher, labels, # labels_coarse, filename, False, False) tsne_plotter.plot()
class Viewer3D(QtWidgets.QWidget): def __init__(self, parent): super().__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.plotter = QtInteractor(self, multi_samples=8, line_smoothing=True, point_smoothing=True) layout.addWidget(self.plotter.interactor) self.plotter.enable_anti_aliasing() for key in ["Up", "Down"]: self.plotter.clear_events_for_key(key) self.clear() self.setLayout(layout) def clear(self): self.plotter.camera_set = False self.actors = [] self.plotter.clear() def fix_camera(self): self.plotter.camera_set = True def enable(self, enabled): if enabled: self.plotter.enable() self.plotter.interactor.setHidden(False) else: self.plotter.disable() self.plotter.interactor.setHidden(True) def add_mesh(self, mesh, transform=None, **kwargs): actor = self.plotter.add_mesh(mesh, **kwargs) if transform is not None: actor.SetUserTransform(vtk_transform(transform)) self.actors.append(actor) return actor def set_point_size(self, size): for actor in self.actors: actor.GetProperty().SetPointSize(size) self.update() def set_line_size(self, size): for actor in self.actors: actor.GetProperty().SetLineWidth(size) self.update() def update(self): self.plotter.interactor.update() def current_viewport(self): vp = save_viewport(self.plotter) return vp def set_viewport(self, viewport): set_viewport(self.plotter, viewport) self.update() def camera_viewport(self, camera, pose): return camera_viewport(camera.intrinsic, pose, self.plotter.window_size)