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 __init__(self): self._id = 0 self.window = gui.Application.instance.create_window( "Add Spheres Example", 1024, 768) self.scene = gui.SceneWidget() self.scene.scene = rendering.Open3DScene(self.window.renderer) self.scene.scene.set_background([1, 1, 1, 1]) self.scene.scene.scene.set_sun_light( [-1, -1, -1], # direction [1, 1, 1], # color 100000) # intensity self.scene.scene.scene.enable_sun_light(True) bbox = o3d.geometry.AxisAlignedBoundingBox([-10, -10, -10], [10, 10, 10]) self.scene.setup_camera(60, bbox, [0, 0, 0]) self.window.add_child(self.scene) # 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("Quit", SpheresApp.MENU_QUIT) debug_menu = gui.Menu() debug_menu.add_item("Add Sphere", SpheresApp.MENU_SPHERE) debug_menu.add_item("Add Random Spheres", SpheresApp.MENU_RANDOM) if not isMacOS: debug_menu.add_separator() debug_menu.add_item("Quit", SpheresApp.MENU_QUIT) 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("Debug", debug_menu) else: menu.add_menu("Debug", debug_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. self.window.set_on_menu_item_activated(SpheresApp.MENU_SPHERE, self._on_menu_sphere) self.window.set_on_menu_item_activated(SpheresApp.MENU_RANDOM, self._on_menu_random) self.window.set_on_menu_item_activated(SpheresApp.MENU_QUIT, self._on_menu_quit)
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 low_level(): app = gui.Application.instance app.initialize() points = make_point_cloud(100, (0, 0, 0), 1.0) w = app.create_window("Open3D - 3D Text", 1024, 768) widget3d = gui.SceneWidget() widget3d.scene = rendering.Open3DScene(w.renderer) mat = rendering.MaterialRecord() mat.shader = "defaultUnlit" mat.point_size = 5 * w.scaling widget3d.scene.add_geometry("Points", points, mat) for idx in range(0, len(points.points)): widget3d.add_3d_label(points.points[idx], "{}".format(idx)) bbox = widget3d.scene.bounding_box widget3d.setup_camera(60.0, bbox, bbox.get_center()) w.add_child(widget3d) app.run()
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, 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()