def __init__(self): self.rgb_images = [] for f in os.listdir(RGB_DIR): if f.endswith(".jpg") or f.endswith(".png"): img = o3d.io.read_image(os.path.join(RGB_DIR, f)) self.rgb_images.append(img) self.depth_images = [] for f in os.listdir(DEPTH_DIR): if f.endswith(".jpg") or f.endswith(".png"): img = o3d.io.read_image(os.path.join(DEPTH_DIR, f)) # The images are pretty dark, so rescale them so that it is # obvious that this is a depth image, for the sake of the example img = rescale_greyscale(img) self.depth_images.append(img) assert (len(self.rgb_images) == len(self.depth_images)) self.window = gui.Application.instance.create_window( "Open3D - Video Example", 1000, 500) self.window.set_on_layout(self._on_layout) self.window.set_on_close(self._on_close) self.widget3d = gui.SceneWidget() self.widget3d.scene = rendering.Open3DScene(self.window.renderer) self.window.add_child(self.widget3d) lit = rendering.Material() lit.shader = "defaultLit" tet = o3d.geometry.TriangleMesh.create_tetrahedron() tet.compute_vertex_normals() tet.paint_uniform_color([0.5, 0.75, 1.0]) self.widget3d.scene.add_geometry("tetrahedron", tet, lit) bounds = self.widget3d.scene.bounding_box self.widget3d.setup_camera(60.0, bounds, bounds.get_center()) self.widget3d.scene.show_axes(True) em = self.window.theme.font_size margin = 0.5 * em self.panel = gui.Vert(0.5 * em, gui.Margins(margin)) self.panel.add_child(gui.Label("Color image")) self.rgb_widget = gui.ImageWidget(self.rgb_images[0]) self.panel.add_child(self.rgb_widget) self.panel.add_child(gui.Label("Depth image (normalized)")) self.depth_widget = gui.ImageWidget(self.depth_images[0]) self.panel.add_child(self.depth_widget) self.window.add_child(self.panel) self.is_done = False threading.Thread(target=self._update_thread).start()
def __init__(self, title, message): self._window = None # A Dialog is just a widget, so you make its child a layout just like # a Window. dlg = gui.Dialog(title) # Add the message text em = self.window.theme.font_size dlg_layout = gui.Vert(em, gui.Margins(em, em, em, em)) dlg_layout.add_child(gui.Label(message)) # Add the Ok button. We need to define a callback function to handle # the click. ok_button = gui.Button("Ok") ok_button.set_on_clicked(self._on_ok) # We want the Ok button to be an the right side, so we need to add # a stretch item to the layout, otherwise the button will be the size # of the entire row. A stretch item takes up as much space as it can, # which forces the button to be its minimum size. button_layout = gui.Horiz() button_layout.add_stretch() button_layout.add_child(ok_button) # Add the button layout, dlg_layout.add_child(button_layout) # ... then add the layout as the child of the Dialog dlg.add_child(dlg_layout)
def __init__(self, cloud): # We will create a SceneWidget that fills the entire window, and then # a label in the lower left on top of the SceneWidget to display the # coordinate. app = gui.Application.instance self.window = app.create_window("Open3D - GetCoord Example", 1024, 768) # Since we want the label on top of the scene, we cannot use a layout, # so we need to manually layout the window's children. self.window.set_on_layout(self._on_layout) self.widget3d = gui.SceneWidget() self.window.add_child(self.widget3d) self.info = gui.Label("") self.info.visible = False self.window.add_child(self.info) self.widget3d.scene = rendering.Open3DScene(self.window.renderer) mat = rendering.Material() mat.shader = "defaultUnlit" # Point size is in native pixels, but "pixel" means different things to # different platforms (macOS, in particular), so multiply by Window scale # factor. mat.point_size = 3 * self.window.scaling self.widget3d.scene.add_geometry("Point Cloud", cloud, mat) bounds = self.widget3d.scene.bounding_box center = bounds.get_center() self.widget3d.setup_camera(60, bounds, center) self.widget3d.look_at(center, center - [0, 0, 3], [0, -1, 0]) self.widget3d.set_on_mouse(self._on_mouse_widget3d)
def _on_menu_about(self): # Show a simple dialog. Although the Dialog is actually a widget, you can # treat it similar to a Window for layout and put all the widgets in a # layout which you make the only child of the Dialog. em = self.window.theme.font_size dlg = gui.Dialog("About") # Add the text dlg_layout = gui.Vert(em, gui.Margins(em, em, em, em)) dlg_layout.add_child(gui.Label("Open3D GUI Example")) # Add the Ok button. We need to define a callback function to handle # the click. ok = gui.Button("OK") ok.set_on_clicked(self._on_about_ok) # We want the Ok button to be an the right side, so we need to add # a stretch item to the layout, otherwise the button will be the size # of the entire row. A stretch item takes up as much space as it can, # which forces the button to be its minimum size. h = gui.Horiz() h.add_stretch() h.add_child(ok) h.add_stretch() dlg_layout.add_child(h) dlg.add_child(dlg_layout) self.window.show_dialog(dlg)
def switch_proxy(): self.logo_idx += 1 if self.logo_idx % 3 == 0: proxy.set_widget(None) elif self.logo_idx % 3 == 1: # Add a simple image logo = gui.ImageWidget(basedir + "/icon-32.png") proxy.set_widget(logo) else: label = gui.Label( 'Open3D: A Modern Library for 3D Data Processing') proxy.set_widget(label) w.set_needs_layout()
def __init__(self, vfov=60, max_pcd_vertices=1 << 20, **callbacks): """Initialize. Args: vfov (float): Vertical field of view for the 3D scene. max_pcd_vertices (int): Maximum point clud verties for which memory is allocated. callbacks (dict of kwargs): Callbacks provided by the controller for various operations. """ self.vfov = vfov self.max_pcd_vertices = max_pcd_vertices gui.Application.instance.initialize() self.window = gui.Application.instance.create_window( "Open3D || Online RGBD Video Processing", 1280, 960) # Called on window layout (eg: resize) self.window.set_on_layout(self.on_layout) self.window.set_on_close(callbacks['on_window_close']) self.pcd_material = o3d.visualization.rendering.MaterialRecord() self.pcd_material.shader = "defaultLit" # Set n_pixels displayed for each 3D point, accounting for HiDPI scaling self.pcd_material.point_size = int(4 * self.window.scaling) # 3D scene self.pcdview = gui.SceneWidget() self.window.add_child(self.pcdview) self.pcdview.enable_scene_caching( True) # makes UI _much_ more responsive self.pcdview.scene = rendering.Open3DScene(self.window.renderer) self.pcdview.scene.set_background([1, 1, 1, 1]) # White background self.pcdview.scene.set_lighting( rendering.Open3DScene.LightingProfile.SOFT_SHADOWS, [0, -6, 0]) # Point cloud bounds, depends on the sensor range self.pcd_bounds = o3d.geometry.AxisAlignedBoundingBox([-3, -3, 0], [3, 3, 6]) self.camera_view() # Initially look from the camera em = self.window.theme.font_size # Options panel self.panel = gui.Vert(em, gui.Margins(em, em, em, em)) self.panel.preferred_width = int(360 * self.window.scaling) self.window.add_child(self.panel) toggles = gui.Horiz(em) self.panel.add_child(toggles) toggle_capture = gui.ToggleSwitch("Capture / Play") toggle_capture.is_on = False toggle_capture.set_on_clicked( callbacks['on_toggle_capture']) # callback toggles.add_child(toggle_capture) self.flag_normals = False self.toggle_normals = gui.ToggleSwitch("Colors / Normals") self.toggle_normals.is_on = False self.toggle_normals.set_on_clicked( callbacks['on_toggle_normals']) # callback toggles.add_child(self.toggle_normals) view_buttons = gui.Horiz(em) self.panel.add_child(view_buttons) view_buttons.add_stretch() # for centering camera_view = gui.Button("Camera view") camera_view.set_on_clicked(self.camera_view) # callback view_buttons.add_child(camera_view) birds_eye_view = gui.Button("Bird's eye view") birds_eye_view.set_on_clicked(self.birds_eye_view) # callback view_buttons.add_child(birds_eye_view) view_buttons.add_stretch() # for centering save_toggle = gui.Horiz(em) self.panel.add_child(save_toggle) save_toggle.add_child(gui.Label("Record / Save")) self.toggle_record = None if callbacks['on_toggle_record'] is not None: save_toggle.add_fixed(1.5 * em) self.toggle_record = gui.ToggleSwitch("Video") self.toggle_record.is_on = False self.toggle_record.set_on_clicked(callbacks['on_toggle_record']) save_toggle.add_child(self.toggle_record) save_buttons = gui.Horiz(em) self.panel.add_child(save_buttons) save_buttons.add_stretch() # for centering save_pcd = gui.Button("Save Point cloud") save_pcd.set_on_clicked(callbacks['on_save_pcd']) save_buttons.add_child(save_pcd) save_rgbd = gui.Button("Save RGBD frame") save_rgbd.set_on_clicked(callbacks['on_save_rgbd']) save_buttons.add_child(save_rgbd) save_buttons.add_stretch() # for centering self.video_size = (int(240 * self.window.scaling), int(320 * self.window.scaling), 3) self.show_color = gui.CollapsableVert("Color image") self.show_color.set_is_open(False) self.panel.add_child(self.show_color) self.color_video = gui.ImageWidget( o3d.geometry.Image(np.zeros(self.video_size, dtype=np.uint8))) self.show_color.add_child(self.color_video) self.show_depth = gui.CollapsableVert("Depth image") self.show_depth.set_is_open(False) self.panel.add_child(self.show_depth) self.depth_video = gui.ImageWidget( o3d.geometry.Image(np.zeros(self.video_size, dtype=np.uint8))) self.show_depth.add_child(self.depth_video) self.status_message = gui.Label("") self.panel.add_child(self.status_message) self.flag_exit = False self.flag_gui_init = False
def __init__(self, config, font_id): self.config = config self.window = gui.Application.instance.create_window( 'Open3D - Reconstruction', 1280, 800) w = self.window em = w.theme.font_size spacing = int(np.round(0.25 * em)) vspacing = int(np.round(0.5 * em)) margins = gui.Margins(vspacing) # First panel self.panel = gui.Vert(spacing, margins) ## Items in fixed props self.fixed_prop_grid = gui.VGrid(2, spacing, gui.Margins(em, 0, em, 0)) ### Depth scale slider scale_label = gui.Label('Depth scale') self.scale_slider = gui.Slider(gui.Slider.INT) self.scale_slider.set_limits(1000, 5000) self.scale_slider.int_value = int(config.depth_scale) self.fixed_prop_grid.add_child(scale_label) self.fixed_prop_grid.add_child(self.scale_slider) voxel_size_label = gui.Label('Voxel size') self.voxel_size_slider = gui.Slider(gui.Slider.DOUBLE) self.voxel_size_slider.set_limits(0.003, 0.01) self.voxel_size_slider.double_value = config.voxel_size self.fixed_prop_grid.add_child(voxel_size_label) self.fixed_prop_grid.add_child(self.voxel_size_slider) trunc_multiplier_label = gui.Label('Trunc multiplier') self.trunc_multiplier_slider = gui.Slider(gui.Slider.DOUBLE) self.trunc_multiplier_slider.set_limits(1.0, 20.0) self.trunc_multiplier_slider.double_value = config.trunc_voxel_multiplier self.fixed_prop_grid.add_child(trunc_multiplier_label) self.fixed_prop_grid.add_child(self.trunc_multiplier_slider) est_block_count_label = gui.Label('Est. blocks') self.est_block_count_slider = gui.Slider(gui.Slider.INT) self.est_block_count_slider.set_limits(40000, 100000) self.est_block_count_slider.int_value = config.block_count self.fixed_prop_grid.add_child(est_block_count_label) self.fixed_prop_grid.add_child(self.est_block_count_slider) est_point_count_label = gui.Label('Est. points') self.est_point_count_slider = gui.Slider(gui.Slider.INT) self.est_point_count_slider.set_limits(500000, 8000000) self.est_point_count_slider.int_value = config.est_point_count self.fixed_prop_grid.add_child(est_point_count_label) self.fixed_prop_grid.add_child(self.est_point_count_slider) ## Items in adjustable props self.adjustable_prop_grid = gui.VGrid(2, spacing, gui.Margins(em, 0, em, 0)) ### Reconstruction interval interval_label = gui.Label('Recon. interval') self.interval_slider = gui.Slider(gui.Slider.INT) self.interval_slider.set_limits(1, 500) self.interval_slider.int_value = 50 self.adjustable_prop_grid.add_child(interval_label) self.adjustable_prop_grid.add_child(self.interval_slider) ### Depth max slider max_label = gui.Label('Depth max') self.max_slider = gui.Slider(gui.Slider.DOUBLE) self.max_slider.set_limits(3.0, 6.0) self.max_slider.double_value = config.depth_max self.adjustable_prop_grid.add_child(max_label) self.adjustable_prop_grid.add_child(self.max_slider) ### Depth diff slider diff_label = gui.Label('Depth diff') self.diff_slider = gui.Slider(gui.Slider.DOUBLE) self.diff_slider.set_limits(0.07, 0.5) self.diff_slider.double_value = config.odometry_distance_thr self.adjustable_prop_grid.add_child(diff_label) self.adjustable_prop_grid.add_child(self.diff_slider) ### Update surface? update_label = gui.Label('Update surface?') self.update_box = gui.Checkbox('') self.update_box.checked = True self.adjustable_prop_grid.add_child(update_label) self.adjustable_prop_grid.add_child(self.update_box) ### Ray cast color? raycast_label = gui.Label('Raycast color?') self.raycast_box = gui.Checkbox('') self.raycast_box.checked = True self.adjustable_prop_grid.add_child(raycast_label) self.adjustable_prop_grid.add_child(self.raycast_box) set_enabled(self.fixed_prop_grid, True) ## Application control b = gui.ToggleSwitch('Resume/Pause') b.set_on_clicked(self._on_switch) ## Tabs tab_margins = gui.Margins(0, int(np.round(0.5 * em)), 0, 0) tabs = gui.TabControl() ### Input image tab tab1 = gui.Vert(0, tab_margins) self.input_color_image = gui.ImageWidget() self.input_depth_image = gui.ImageWidget() tab1.add_child(self.input_color_image) tab1.add_fixed(vspacing) tab1.add_child(self.input_depth_image) tabs.add_tab('Input images', tab1) ### Rendered image tab tab2 = gui.Vert(0, tab_margins) self.raycast_color_image = gui.ImageWidget() self.raycast_depth_image = gui.ImageWidget() tab2.add_child(self.raycast_color_image) tab2.add_fixed(vspacing) tab2.add_child(self.raycast_depth_image) tabs.add_tab('Raycast images', tab2) ### Info tab tab3 = gui.Vert(0, tab_margins) self.output_info = gui.Label('Output info') self.output_info.font_id = font_id tab3.add_child(self.output_info) tabs.add_tab('Info', tab3) self.panel.add_child(gui.Label('Starting settings')) self.panel.add_child(self.fixed_prop_grid) self.panel.add_fixed(vspacing) self.panel.add_child(gui.Label('Reconstruction settings')) self.panel.add_child(self.adjustable_prop_grid) self.panel.add_child(b) self.panel.add_stretch() self.panel.add_child(tabs) # Scene widget self.widget3d = gui.SceneWidget() # FPS panel self.fps_panel = gui.Vert(spacing, margins) self.output_fps = gui.Label('FPS: 0.0') self.fps_panel.add_child(self.output_fps) # Now add all the complex panels w.add_child(self.panel) w.add_child(self.widget3d) w.add_child(self.fps_panel) self.widget3d.scene = rendering.Open3DScene(self.window.renderer) self.widget3d.scene.set_background([1, 1, 1, 1]) w.set_on_layout(self._on_layout) w.set_on_close(self._on_close) self.is_done = False self.is_started = False self.is_running = False self.is_surface_updated = False self.idx = 0 self.poses = [] # Start running threading.Thread(name='UpdateMain', target=self.update_main).start()
def __init__(self): self.window = gui.Window("Test") # self.window = gui.Window("Test", 640, 480) # self.window = gui.Window("Test", 640, 480, x=50, y=100) w = self.window # for more concise code # Rather than specifying sizes in pixels, which may vary in size based # on the monitor, especially on macOS which has 220 dpi monitors, use # the em-size. This way sizings will be proportional to the font size, # which will create a more visually consistent size across platforms. em = w.theme.font_size # Widgets are laid out in layouts: gui.Horiz, gui.Vert, # gui.CollapsableVert, and gui.VGrid. By nesting the layouts we can # achieve complex designs. Usually we use a vertical layout as the # topmost widget, since widgets tend to be organized from top to bottom. # Within that, we usually have a series of horizontal layouts for each # row. layout = gui.Vert(0, gui.Margins(0.5 * em, 0.5 * em, 0.5 * em, 0.5 * em)) # Create the menu. The menu is global (because the macOS menu is global), # so only create it once. if gui.Application.instance.menubar is None: menubar = gui.Menu() test_menu = gui.Menu() test_menu.add_item("An option", ExampleWindow.MENU_CHECKABLE) test_menu.set_checked(ExampleWindow.MENU_CHECKABLE, True) test_menu.add_item("Unavailable feature", ExampleWindow.MENU_DISABLED) test_menu.set_enabled(ExampleWindow.MENU_DISABLED, False) test_menu.add_separator() test_menu.add_item("Quit", ExampleWindow.MENU_QUIT) # On macOS the first menu item is the application menu item and will # always be the name of the application (probably "Python"), # regardless of what you pass in here. The application menu is # typically where About..., Preferences..., and Quit go. menubar.add_menu("Test", test_menu) gui.Application.instance.menubar = menubar # Each window needs to know what to do with the menu items, so we need # to tell the window how to handle menu items. w.set_on_menu_item_activated(ExampleWindow.MENU_CHECKABLE, self._on_menu_checkable) w.set_on_menu_item_activated(ExampleWindow.MENU_QUIT, self._on_menu_quit) # Create a file-chooser widget. One part will be a text edit widget for # the filename and clicking on the button will let the user choose using # the file dialog. self._fileedit = gui.TextEdit() filedlgbutton = gui.Button("...") filedlgbutton.horizontal_padding_em = 0.5 filedlgbutton.vertical_padding_em = 0 filedlgbutton.set_on_clicked(self._on_filedlg_button) # (Create the horizontal widget for the row. This will make sure the # text editor takes up as much space as it can.) fileedit_layout = gui.Horiz() fileedit_layout.add_child(gui.Label("Model file")) fileedit_layout.add_child(self._fileedit) fileedit_layout.add_fixed(0.25 * em) fileedit_layout.add_child(filedlgbutton) # add to the top-level (vertical) layout layout.add_child(fileedit_layout) # Create a collapsable vertical widget, which takes up enough vertical # space for all its children when open, but only enough for text when # closed. This is useful for property pages, so the user can hide sets # of properties they rarely use. All layouts take a spacing parameter, # which is the spacinging between items in the widget, and a margins # parameter, which specifies the spacing of the left, top, right, # bottom margins. (This acts like the 'padding' property in CSS.) collapse = gui.CollapsableVert("Widgets", 0.33 * em, gui.Margins(em, 0, 0, 0)) self._label = gui.Label("Lorem ipsum dolor") self._label.text_color = gui.Color(1.0, 0.5, 0.0) collapse.add_child(self._label) # Create a checkbox. Checking or unchecking would usually be used to set # a binary property, but in this case it will show a simple message box, # which illustrates how to create simple dialogs. cb = gui.Checkbox("Enable some really cool effect") cb.set_on_checked(self._on_cb) # set the callback function collapse.add_child(cb) # Create a color editor. We will change the color of the orange label # above when the color changes. color = gui.ColorEdit() color.color_value = self._label.text_color color.set_on_value_changed(self._on_color) collapse.add_child(color) # This is a combobox, nothing fancy here, just set a simple function to # handle the user selecting an item. combo = gui.Combobox() combo.add_item("Show point labels") combo.add_item("Show point velocity") combo.add_item("Show bounding boxes") combo.set_on_selection_changed(self._on_combo) collapse.add_child(combo) # Add a simple image logo = gui.ImageLabel(basedir + "/icon-32.png") collapse.add_child(logo) # Add a list of items lv = gui.ListView() lv.set_items(["Ground", "Trees", "Buildings" "Cars", "People"]) lv.selected_index = lv.selected_index + 2 # initially is -1, so now 1 lv.set_on_selection_changed(self._on_list) collapse.add_child(lv) # Add a tree view tree = gui.TreeView() tree.add_item(tree.get_root_item(), "Camera") geo_id = tree.add_item(tree.get_root_item(), "Geometries") mesh_id = tree.add_item(geo_id, "Mesh") tree.add_item(mesh_id, "Triangles") tree.add_item(mesh_id, "Albedo texture") tree.add_item(mesh_id, "Normal map") points_id = tree.add_item(geo_id, "Points") tree.can_select_items_with_children = True tree.set_on_selection_changed(self._on_tree) # does not call on_selection_changed: user did not change selection tree.selected_item = points_id collapse.add_child(tree) # Add two number editors, one for integers and one for floating point # Number editor can clamp numbers to a range, although this is more # useful for integers than for floating point. intedit = gui.NumberEdit(gui.NumberEdit.INT) intedit.int_value = 0 intedit.set_limits(1, 19) # value coerced to 1 intedit.int_value = intedit.int_value + 2 # value should be 3 doubleedit = gui.NumberEdit(gui.NumberEdit.DOUBLE) numlayout = gui.Horiz() numlayout.add_child(gui.Label("int")) numlayout.add_child(intedit) numlayout.add_fixed( em) # manual spacing (could set it in Horiz() ctor) numlayout.add_child(gui.Label("double")) numlayout.add_child(doubleedit) collapse.add_child(numlayout) # Create a progress bar. It ranges from 0.0 to 1.0. self._progress = gui.ProgressBar() self._progress.value = 0.25 # 25% complete self._progress.value = self._progress.value + 0.08 # 0.25 + 0.08 = 33% prog_layout = gui.Horiz(em) prog_layout.add_child(gui.Label("Progress...")) prog_layout.add_child(self._progress) collapse.add_child(prog_layout) # Create a slider. It acts very similar to NumberEdit except that the # user moves a slider and cannot type the number. slider = gui.Slider(gui.Slider.INT) slider.set_limits(5, 13) slider.set_on_value_changed(self._on_slider) collapse.add_child(slider) # Create a text editor. The placeholder text (if not empty) will be # displayed when there is no text, as concise help, or visible tooltip. tedit = gui.TextEdit() tedit.placeholder_text = "Edit me some text here" # on_text_changed fires whenever the user changes the text (but not if # the text_value property is assigned to). tedit.set_on_text_changed(self._on_text_changed) # on_value_changed fires whenever the user signals that they are finished # editing the text, either by pressing return or by clicking outside of # the text editor, thus losing text focus. tedit.set_on_value_changed(self._on_value_changed) collapse.add_child(tedit) # Create a widget for showing/editing a 3D vector vedit = gui.VectorEdit() vedit.vector_value = [1, 2, 3] vedit.set_on_value_changed(self._on_vedit) collapse.add_child(vedit) # Create a VGrid layout. This layout specifies the number of columns # (two, in this case), and will place the first child in the first # column, the second in the second, the third in the first, the fourth # in the second, etc. # So: # 2 cols 3 cols 4 cols # | 1 | 2 | | 1 | 2 | 3 | | 1 | 2 | 3 | 4 | # | 3 | 4 | | 4 | 5 | 6 | | 5 | 6 | 7 | 8 | # | 5 | 6 | | 7 | 8 | 9 | | 9 | 10 | 11 | 12 | # | ... | | ... | | ... | vgrid = gui.VGrid(2) vgrid.add_child(gui.Label("Trees")) vgrid.add_child(gui.Label("12 items")) vgrid.add_child(gui.Label("People")) vgrid.add_child(gui.Label("2 (93% certainty)")) vgrid.add_child(gui.Label("Cars")) vgrid.add_child(gui.Label("5 (87% certainty)")) collapse.add_child(vgrid) collapse.add_child(vgrid) # Create a tab control. This is really a set of N layouts on top of each # other, but with only one selected. tabs = gui.TabControl() tab1 = gui.Vert() tab1.add_child(gui.Checkbox("Enable option 1")) tab1.add_child(gui.Checkbox("Enable option 2")) tab1.add_child(gui.Checkbox("Enable option 3")) tabs.add_tab("Options", tab1) tab2 = gui.Vert() tab2.add_child(gui.Label("No plugins detected")) tab2.add_stretch() tabs.add_tab("Plugins", tab2) collapse.add_child(tabs) # Quit button. (Typically this is a menu item) button_layout = gui.Horiz() ok_button = gui.Button("Ok") ok_button.set_on_clicked(self._on_ok) button_layout.add_stretch() button_layout.add_child(ok_button) layout.add_child(collapse) layout.add_child(button_layout) # We're done, set the window's layout w.add_child(layout)
def __init__(self, width, height): self.settings = Settings() resource_path = gui.Application.instance.resource_path self.settings.new_ibl_name = resource_path + "/" + AppWindow.DEFAULT_IBL self.window = gui.Application.instance.create_window( "Open3D", width, height) w = self.window # to make the code more concise # 3D widget self._scene = gui.SceneWidget() self._scene.scene = rendering.Open3DScene(w.renderer) self._scene.set_on_sun_direction_changed(self._on_sun_dir) # ---- Settings panel ---- # Rather than specifying sizes in pixels, which may vary in size based # on the monitor, especially on macOS which has 220 dpi monitors, use # the em-size. This way sizings will be proportional to the font size, # which will create a more visually consistent size across platforms. em = w.theme.font_size separation_height = int(round(0.5 * em)) # Widgets are laid out in layouts: gui.Horiz, gui.Vert, # gui.CollapsableVert, and gui.VGrid. By nesting the layouts we can # achieve complex designs. Usually we use a vertical layout as the # topmost widget, since widgets tend to be organized from top to bottom. # Within that, we usually have a series of horizontal layouts for each # row. All layouts take a spacing parameter, which is the spacing # between items in the widget, and a margins parameter, which specifies # the spacing of the left, top, right, bottom margins. (This acts like # the 'padding' property in CSS.) self._settings_panel = gui.Vert( 0, gui.Margins(0.25 * em, 0.25 * em, 0.25 * em, 0.25 * em)) # Create a collapsable vertical widget, which takes up enough vertical # space for all its children when open, but only enough for text when # closed. This is useful for property pages, so the user can hide sets # of properties they rarely use. view_ctrls = gui.CollapsableVert("View controls", 0.25 * em, gui.Margins(em, 0, 0, 0)) self._arcball_button = gui.Button("Arcball") self._arcball_button.horizontal_padding_em = 0.5 self._arcball_button.vertical_padding_em = 0 self._arcball_button.set_on_clicked(self._set_mouse_mode_rotate) self._fly_button = gui.Button("Fly") self._fly_button.horizontal_padding_em = 0.5 self._fly_button.vertical_padding_em = 0 self._fly_button.set_on_clicked(self._set_mouse_mode_fly) self._model_button = gui.Button("Model") self._model_button.horizontal_padding_em = 0.5 self._model_button.vertical_padding_em = 0 self._model_button.set_on_clicked(self._set_mouse_mode_model) self._sun_button = gui.Button("Sun") self._sun_button.horizontal_padding_em = 0.5 self._sun_button.vertical_padding_em = 0 self._sun_button.set_on_clicked(self._set_mouse_mode_sun) self._ibl_button = gui.Button("Environment") self._ibl_button.horizontal_padding_em = 0.5 self._ibl_button.vertical_padding_em = 0 self._ibl_button.set_on_clicked(self._set_mouse_mode_ibl) view_ctrls.add_child(gui.Label("Mouse controls")) # We want two rows of buttons, so make two horizontal layouts. We also # want the buttons centered, which we can do be putting a stretch item # as the first and last item. Stretch items take up as much space as # possible, and since there are two, they will each take half the extra # space, thus centering the buttons. h = gui.Horiz(0.25 * em) # row 1 h.add_stretch() h.add_child(self._arcball_button) h.add_child(self._fly_button) h.add_child(self._model_button) h.add_stretch() view_ctrls.add_child(h) h = gui.Horiz(0.25 * em) # row 2 h.add_stretch() h.add_child(self._sun_button) h.add_child(self._ibl_button) h.add_stretch() view_ctrls.add_child(h) self._show_skybox = gui.Checkbox("Show skymap") self._show_skybox.set_on_checked(self._on_show_skybox) view_ctrls.add_fixed(separation_height) view_ctrls.add_child(self._show_skybox) self._bg_color = gui.ColorEdit() self._bg_color.set_on_value_changed(self._on_bg_color) grid = gui.VGrid(2, 0.25 * em) grid.add_child(gui.Label("BG Color")) grid.add_child(self._bg_color) view_ctrls.add_child(grid) self._show_axes = gui.Checkbox("Show axes") self._show_axes.set_on_checked(self._on_show_axes) view_ctrls.add_fixed(separation_height) view_ctrls.add_child(self._show_axes) self._profiles = gui.Combobox() for name in sorted(Settings.LIGHTING_PROFILES.keys()): self._profiles.add_item(name) self._profiles.add_item(Settings.CUSTOM_PROFILE_NAME) self._profiles.set_on_selection_changed(self._on_lighting_profile) view_ctrls.add_fixed(separation_height) view_ctrls.add_child(gui.Label("Lighting profiles")) view_ctrls.add_child(self._profiles) self._settings_panel.add_fixed(separation_height) self._settings_panel.add_child(view_ctrls) advanced = gui.CollapsableVert("Advanced lighting", 0, gui.Margins(em, 0, 0, 0)) advanced.set_is_open(False) self._use_ibl = gui.Checkbox("HDR map") self._use_ibl.set_on_checked(self._on_use_ibl) self._use_sun = gui.Checkbox("Sun") self._use_sun.set_on_checked(self._on_use_sun) advanced.add_child(gui.Label("Light sources")) h = gui.Horiz(em) h.add_child(self._use_ibl) h.add_child(self._use_sun) advanced.add_child(h) self._ibl_map = gui.Combobox() for ibl in glob.glob(gui.Application.instance.resource_path + "/*_ibl.ktx"): self._ibl_map.add_item(os.path.basename(ibl[:-8])) self._ibl_map.selected_text = AppWindow.DEFAULT_IBL self._ibl_map.set_on_selection_changed(self._on_new_ibl) self._ibl_intensity = gui.Slider(gui.Slider.INT) self._ibl_intensity.set_limits(0, 200000) self._ibl_intensity.set_on_value_changed(self._on_ibl_intensity) grid = gui.VGrid(2, 0.25 * em) grid.add_child(gui.Label("HDR map")) grid.add_child(self._ibl_map) grid.add_child(gui.Label("Intensity")) grid.add_child(self._ibl_intensity) advanced.add_fixed(separation_height) advanced.add_child(gui.Label("Environment")) advanced.add_child(grid) self._sun_intensity = gui.Slider(gui.Slider.INT) self._sun_intensity.set_limits(0, 200000) self._sun_intensity.set_on_value_changed(self._on_sun_intensity) self._sun_dir = gui.VectorEdit() self._sun_dir.set_on_value_changed(self._on_sun_dir) self._sun_color = gui.ColorEdit() self._sun_color.set_on_value_changed(self._on_sun_color) grid = gui.VGrid(2, 0.25 * em) grid.add_child(gui.Label("Intensity")) grid.add_child(self._sun_intensity) grid.add_child(gui.Label("Direction")) grid.add_child(self._sun_dir) grid.add_child(gui.Label("Color")) grid.add_child(self._sun_color) advanced.add_fixed(separation_height) advanced.add_child(gui.Label("Sun (Directional light)")) advanced.add_child(grid) self._settings_panel.add_fixed(separation_height) self._settings_panel.add_child(advanced) material_settings = gui.CollapsableVert("Material settings", 0, gui.Margins(em, 0, 0, 0)) self._shader = gui.Combobox() self._shader.add_item(AppWindow.MATERIAL_NAMES[0]) self._shader.add_item(AppWindow.MATERIAL_NAMES[1]) self._shader.add_item(AppWindow.MATERIAL_NAMES[2]) self._shader.add_item(AppWindow.MATERIAL_NAMES[3]) self._shader.set_on_selection_changed(self._on_shader) self._material_prefab = gui.Combobox() for prefab_name in sorted(Settings.PREFAB.keys()): self._material_prefab.add_item(prefab_name) self._material_prefab.selected_text = Settings.DEFAULT_MATERIAL_NAME self._material_prefab.set_on_selection_changed( self._on_material_prefab) self._material_color = gui.ColorEdit() self._material_color.set_on_value_changed(self._on_material_color) self._point_size = gui.Slider(gui.Slider.INT) self._point_size.set_limits(1, 10) self._point_size.set_on_value_changed(self._on_point_size) grid = gui.VGrid(2, 0.25 * em) grid.add_child(gui.Label("Type")) grid.add_child(self._shader) grid.add_child(gui.Label("Material")) grid.add_child(self._material_prefab) grid.add_child(gui.Label("Color")) grid.add_child(self._material_color) grid.add_child(gui.Label("Point size")) grid.add_child(self._point_size) material_settings.add_child(grid) self._settings_panel.add_fixed(separation_height) self._settings_panel.add_child(material_settings) # ---- # Normally our user interface can be children of all one layout (usually # a vertical layout), which is then the only child of the window. In our # case we want the scene to take up all the space and the settings panel # to go above it. We can do this custom layout by providing an on_layout # callback. The on_layout callback should set the frame # (position + size) of every child correctly. After the callback is # done the window will layout the grandchildren. w.set_on_layout(self._on_layout) w.add_child(self._scene) w.add_child(self._settings_panel) # ---- Menu ---- # The menu is global (because the macOS menu is global), so only create # it once, no matter how many windows are created if gui.Application.instance.menubar is None: if isMacOS: app_menu = gui.Menu() app_menu.add_item("About", AppWindow.MENU_ABOUT) app_menu.add_separator() app_menu.add_item("Quit", AppWindow.MENU_QUIT) file_menu = gui.Menu() file_menu.add_item("Open...", AppWindow.MENU_OPEN) file_menu.add_item("Export Current Image...", AppWindow.MENU_EXPORT) if not isMacOS: file_menu.add_separator() file_menu.add_item("Quit", AppWindow.MENU_QUIT) settings_menu = gui.Menu() settings_menu.add_item("Lighting & Materials", AppWindow.MENU_SHOW_SETTINGS) settings_menu.set_checked(AppWindow.MENU_SHOW_SETTINGS, True) help_menu = gui.Menu() help_menu.add_item("About", AppWindow.MENU_ABOUT) menu = gui.Menu() if isMacOS: # macOS will name the first menu item for the running application # (in our case, probably "Python"), regardless of what we call # it. This is the application menu, and it is where the # About..., Preferences..., and Quit menu items typically go. menu.add_menu("Example", app_menu) menu.add_menu("File", file_menu) menu.add_menu("Settings", settings_menu) # Don't include help menu unless it has something more than # About... else: menu.add_menu("File", file_menu) menu.add_menu("Settings", settings_menu) menu.add_menu("Help", help_menu) gui.Application.instance.menubar = menu # The menubar is global, but we need to connect the menu items to the # window, so that the window can call the appropriate function when the # menu item is activated. w.set_on_menu_item_activated(AppWindow.MENU_OPEN, self._on_menu_open) w.set_on_menu_item_activated(AppWindow.MENU_EXPORT, self._on_menu_export) w.set_on_menu_item_activated(AppWindow.MENU_QUIT, self._on_menu_quit) w.set_on_menu_item_activated(AppWindow.MENU_SHOW_SETTINGS, self._on_menu_toggle_settings_panel) w.set_on_menu_item_activated(AppWindow.MENU_ABOUT, self._on_menu_about) # ---- self._apply_settings()
def push_widget(): self._widget_idx += 1 stack.push_widget(gui.Label(f'Widget {self._widget_idx}'))