Open3D解决SceneWidget加入布局中消失的问题
Open3D解决SceneWidget加入布局中消失的问题
- Open3D解决SceneWidget加入布局中消失的问题
- 1. 问题
- 2. 问题代码
- 3. 解决
Open3D解决SceneWidget加入布局中消失的问题
1. 问题
把SceneWidget加到布局管理其中图形可以展示出来,但是鼠标点击就消失了。
stackoverflow上已经有人提出这个问题了,还是2022年的时候,可是现在好像也没有解决。
https://stackoverflow.com/questions/71706506/why-does-open3d-visualization-disappear-when-i-left-click-the-window
2. 问题代码
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2024 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------
import open3d as o3d
import open3d.visualization.gui as gui
import open3d.visualization.rendering as rendering
import platform
import random
import threading
import time
isMacOS = (platform.system() == "Darwin")
# This example shows two methods of adding geometry to an existing scene.
# 1) add via a UI callback (in this case a menu, but a button would be similar,
# you would call `button.set_on_clicked(self.on_menu_sphere_)` when
# configuring the button. See `on_menu_sphere()`.
# 2) add asynchronously by polling from another thread. GUI functions must be
# called from the UI thread, so use Application.post_to_main_thread().
# See `on_menu_random()`.
# Running the example will show a simple window with a Debug menu item with the
# two different options. The second method will add random spheres for
# 20 seconds, during which time you can be interacting with the scene, rotating,
# etc.
class SpheresApp:
MENU_SPHERE = 1
MENU_RANDOM = 2
MENU_QUIT = 3
def __init__(self):
self._id = 0
self.window = gui.Application.instance.create_window(
"Add Spheres Example", 800, 600)
# 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)
self.scene = gui.SceneWidget()
self.scene.scene = rendering.Open3DScene(self.window.renderer)
self.scene.scene.show_axes(True)
mesh = o3d.geometry.TriangleMesh.create_sphere()
mesh.compute_vertex_normals()
material = rendering.MaterialRecord()
material.shader = "defaultLit"
self.scene.scene.add_geometry("sphere" + str(self._id), mesh, material)
em = self.window.theme.font_size
self.layout = gui.Horiz(0, gui.Margins(0.25*em,0.25*em,0.25*em,0.254*em))
self.layout.add_child(gui.Label("Model file"))
self.layout.add_fixed(0.25 * em)
self.window.set_on_layout(self.on_window_layout)
self.vlayout = gui.Vert(0, gui.Margins(0.25 * em, 0.25 * em, 0.25 * em, 0.254 * em))
self.vlayout.add_child(self.scene)
self.vlayout.add_fixed(0.25 * em)
self.window.add_child(self.vlayout)
self.window.add_child(self.layout)
def on_window_layout(self, layout: gui.LayoutContext) -> None:
"""
This is called when layout is required for this widgets *immediate children*, like on resize. Only the immediate
children are *manually* positioned here, by setting the x, y, width, and height of their frames. The
grandchildren are *not* touched here; they're automatically handled after this.
"""
# This is the area in this widget available to place child widgets in. The *units* here aren't window screen
# pixels, like from the width/height here when setting up the window: gui.Application.instance.create_window("Test", width=500, height=600).
rect = self.window.content_rect
r = self.window.content_rect
print(r)
print(layout.theme.default_layout_spacing)
print(layout.theme.default_margin)
print(layout.theme.font_size)
# Put the layout (containing the controls) on the left 1/3 and the and scene on the right 2/3.
x_division = rect.width // 3
# Set the layout (gui.Vert) on left to 1/3 available width, full height.
# gui.Rect(x, y, width, height)
# Set the SceneWidget on on right, 2/3 available width, full height
self.vlayout.frame = gui.Rect(0, 0, r.width - x_division, r.height)
self.layout.frame = gui.Rect(r.width - x_division, 0, x_division, r.height)
def add_sphere(self):
self._id += 1
mat = rendering.MaterialRecord()
mat.base_color = [
random.random(),
random.random(),
random.random(), 1.0
]
mat.shader = "defaultLit"
sphere = o3d.geometry.TriangleMesh.create_sphere(0.5)
sphere.compute_vertex_normals()
sphere.translate([
10.0 * random.uniform(-1.0, 1.0), 10.0 * random.uniform(-1.0, 1.0),
10.0 * random.uniform(-1.0, 1.0)
])
self.scene.scene.add_geometry("sphere" + str(self._id), sphere, mat)
def _on_menu_sphere(self):
# GUI callbacks happen on the main thread, so we can do everything
# normally here.
self.scene.scene.clear_geometry()
self.add_sphere()
def _on_menu_random(self):
# This adds spheres asynchronously. This pattern is useful if you have
# data coming in from another source than user interaction.
def thread_main():
for _ in range(0, 20):
# We can only modify GUI objects on the main thread, so we
# need to post the function to call to the main thread.
gui.Application.instance.post_to_main_thread(
self.window, self.add_sphere)
time.sleep(0.5)
threading.Thread(target=thread_main).start()
def _on_menu_quit(self):
gui.Application.instance.quit()
def main():
gui.Application.instance.initialize()
SpheresApp()
gui.Application.instance.run()
if __name__ == "__main__":
main()
3. 解决
不能放在布局中就直接手动布局吧,
不加到布局中去。。。
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2024 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------
import open3d as o3d
import open3d.visualization.gui as gui
import open3d.visualization.rendering as rendering
import platform
import random
import threading
import time
isMacOS = (platform.system() == "Darwin")
# This example shows two methods of adding geometry to an existing scene.
# 1) add via a UI callback (in this case a menu, but a button would be similar,
# you would call `button.set_on_clicked(self.on_menu_sphere_)` when
# configuring the button. See `on_menu_sphere()`.
# 2) add asynchronously by polling from another thread. GUI functions must be
# called from the UI thread, so use Application.post_to_main_thread().
# See `on_menu_random()`.
# Running the example will show a simple window with a Debug menu item with the
# two different options. The second method will add random spheres for
# 20 seconds, during which time you can be interacting with the scene, rotating,
# etc.
class SpheresApp:
MENU_SPHERE = 1
MENU_RANDOM = 2
MENU_QUIT = 3
def __init__(self):
self._id = 0
self.window = gui.Application.instance.create_window(
"Add Spheres Example", 800, 600)
# 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)
self.scene = gui.SceneWidget()
self.scene.scene = rendering.Open3DScene(self.window.renderer)
self.scene.scene.show_axes(True)
mesh = o3d.geometry.TriangleMesh.create_sphere()
mesh.compute_vertex_normals()
material = rendering.MaterialRecord()
material.shader = "defaultLit"
self.scene.scene.add_geometry("sphere" + str(self._id), mesh, material)
em = self.window.theme.font_size
self.layout = gui.Horiz(0, gui.Margins(0.25*em,0.25*em,0.25*em,0.254*em))
self.layout.add_child(gui.Label("Model file"))
self.layout.add_fixed(0.25 * em)
self.window.set_on_layout(self.on_window_layout)
self.window.add_child(self.scene)
self.window.add_child(self.layout)
def on_window_layout(self, layout: gui.LayoutContext) -> None:
"""
This is called when layout is required for this widgets *immediate children*, like on resize. Only the immediate
children are *manually* positioned here, by setting the x, y, width, and height of their frames. The
grandchildren are *not* touched here; they're automatically handled after this.
"""
# This is the area in this widget available to place child widgets in. The *units* here aren't window screen
# pixels, like from the width/height here when setting up the window: gui.Application.instance.create_window("Test", width=500, height=600).
rect = self.window.content_rect
r = self.window.content_rect
print(r)
print(layout.theme.default_layout_spacing)
print(layout.theme.default_margin)
print(layout.theme.font_size)
# Put the layout (containing the controls) on the left 1/3 and the and scene on the right 2/3.
x_division = rect.width // 3
# Set the layout (gui.Vert) on left to 1/3 available width, full height.
# gui.Rect(x, y, width, height)
# Set the SceneWidget on on right, 2/3 available width, full height
self.scene.frame = gui.Rect(0, 0, r.width - x_division, r.height)
self.layout.frame = gui.Rect(r.width - x_division, 0, x_division, r.height)
def add_sphere(self):
self._id += 1
mat = rendering.MaterialRecord()
mat.base_color = [
random.random(),
random.random(),
random.random(), 1.0
]
mat.shader = "defaultLit"
sphere = o3d.geometry.TriangleMesh.create_sphere(0.5)
sphere.compute_vertex_normals()
sphere.translate([
10.0 * random.uniform(-1.0, 1.0), 10.0 * random.uniform(-1.0, 1.0),
10.0 * random.uniform(-1.0, 1.0)
])
self.scene.scene.add_geometry("sphere" + str(self._id), sphere, mat)
def _on_menu_sphere(self):
# GUI callbacks happen on the main thread, so we can do everything
# normally here.
self.scene.scene.clear_geometry()
self.add_sphere()
def _on_menu_random(self):
# This adds spheres asynchronously. This pattern is useful if you have
# data coming in from another source than user interaction.
def thread_main():
for _ in range(0, 20):
# We can only modify GUI objects on the main thread, so we
# need to post the function to call to the main thread.
gui.Application.instance.post_to_main_thread(
self.window, self.add_sphere)
time.sleep(0.5)
threading.Thread(target=thread_main).start()
def _on_menu_quit(self):
gui.Application.instance.quit()
def main():
gui.Application.instance.initialize()
SpheresApp()
gui.Application.instance.run()
if __name__ == "__main__":
main()