官方示例:https://github.com/intel-isl/Open3D/tree/master/examples/python/gui
可以基于官方示例改进
其他博客示例:https://blog.csdn.net/qq_31254435/article/details/122913800
需要安装新版open3d,旧版会报错,测试使用的是0.14.1版
测试用例参考官方示例(vis-gui.py)
注意切换运行路径
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 www.open3d.org
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# ----------------------------------------------------------------------------
import glob
import numpy as np
import open3d as o3d
import open3d.visualization.gui as gui
import open3d.visualization.rendering as rendering
import os
import platform
import sys
isMacOS = (platform.system() == "Darwin")
class Settings:
UNLIT = "defaultUnlit"
LIT = "defaultLit"
NORMALS = "normals"
DEPTH = "depth"
DEFAULT_PROFILE_NAME = "Bright day with sun at +Y [default]"
POINT_CLOUD_PROFILE_NAME = "Cloudy day (no direct sun)"
CUSTOM_PROFILE_NAME = "Custom"
LIGHTING_PROFILES = {
DEFAULT_PROFILE_NAME: {
"ibl_intensity": 45000,
"sun_intensity": 45000,
"sun_dir": [0.577, -0.577, -0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
"Bright day with sun at -Y": {
"ibl_intensity": 45000,
"sun_intensity": 45000,
"sun_dir": [0.577, 0.577, 0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
"Bright day with sun at +Z": {
"ibl_intensity": 45000,
"sun_intensity": 45000,
"sun_dir": [0.577, 0.577, -0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
"Less Bright day with sun at +Y": {
"ibl_intensity": 35000,
"sun_intensity": 50000,
"sun_dir": [0.577, -0.577, -0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
"Less Bright day with sun at -Y": {
"ibl_intensity": 35000,
"sun_intensity": 50000,
"sun_dir": [0.577, 0.577, 0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
"Less Bright day with sun at +Z": {
"ibl_intensity": 35000,
"sun_intensity": 50000,
"sun_dir": [0.577, 0.577, -0.577],
# "ibl_rotation":
"use_ibl": True,
"use_sun": True,
},
POINT_CLOUD_PROFILE_NAME: {
"ibl_intensity": 60000,
"sun_intensity": 50000,
"use_ibl": True,
"use_sun": False,
# "ibl_rotation":
},
}
DEFAULT_MATERIAL_NAME = "Polished ceramic [default]"
PREFAB = {
DEFAULT_MATERIAL_NAME: {
"metallic": 0.0,
"roughness": 0.7,
"reflectance": 0.5,
"clearcoat": 0.2,
"clearcoat_roughness": 0.2,
"anisotropy": 0.0
},
"Metal (rougher)": {
"metallic": 1.0,
"roughness": 0.5,
"reflectance": 0.9,
"clearcoat": 0.0,
"clearcoat_roughness": 0.0,
"anisotropy": 0.0
},
"Metal (smoother)": {
"metallic": 1.0,
"roughness": 0.3,
"reflectance": 0.9,
"clearcoat": 0.0,
"clearcoat_roughness": 0.0,
"anisotropy": 0.0
},
"Plastic": {
"metallic": 0.0,
"roughness": 0.5,
"reflectance": 0.5,
"clearcoat": 0.5,
"clearcoat_roughness": 0.2,
"anisotropy": 0.0
},
"Glazed ceramic": {
"metallic": 0.0,
"roughness": 0.5,
"reflectance": 0.9,
"clearcoat": 1.0,
"clearcoat_roughness": 0.1,
"anisotropy": 0.0
},
"Clay": {
"metallic": 0.0,
"roughness": 1.0,
"reflectance": 0.5,
"clearcoat": 0.1,
"clearcoat_roughness": 0.287,
"anisotropy": 0.0
},
}
def __init__(self):
self.mouse_model = gui.SceneWidget.Controls.ROTATE_CAMERA
self.bg_color = gui.Color(1, 1, 1)
self.show_skybox = False
self.show_axes = False
self.use_ibl = True
self.use_sun = True
self.new_ibl_name = None # clear to None after loading
self.ibl_intensity = 45000
self.sun_intensity = 45000
self.sun_dir = [0.577, -0.577, -0.577]
self.sun_color = gui.Color(1, 1, 1)
self.apply_material = True # clear to False after processing
self._materials = {
Settings.LIT: rendering.MaterialRecord(),
Settings.UNLIT: rendering.MaterialRecord(),
Settings.NORMALS: rendering.MaterialRecord(),
Settings.DEPTH: rendering.MaterialRecord()
}
self._materials[Settings.LIT].base_color = [0.9, 0.9, 0.9, 1.0]
self._materials[Settings.LIT].shader = Settings.LIT
self._materials[Settings.UNLIT].base_color = [0.9, 0.9, 0.9, 1.0]
self._materials[Settings.UNLIT].shader = Settings.UNLIT
self._materials[Settings.NORMALS].shader = Settings.NORMALS
self._materials[Settings.DEPTH].shader = Settings.DEPTH
# Conveniently, assigning from self._materials[...] assigns a reference,
# not a copy, so if we change the property of a material, then switch
# to another one, then come back, the old setting will still be there.
self.material = self._materials[Settings.LIT]
def set_material(self, name):
self.material = self._materials[name]
self.apply_material = True
def apply_material_prefab(self, name):
assert (self.material.shader == Settings.LIT)
prefab = Settings.PREFAB[name]
for key, val in prefab.items():
setattr(self.material, "base_" + key, val)
def apply_lighting_profile(self, name):
profile = Settings.LIGHTING_PROFILES[name]
for key, val in profile.items():
setattr(self, key, val)
class AppWindow:
MENU_OPEN = 1
MENU_EXPORT = 2
MENU_QUIT = 3
MENU_SHOW_SETTINGS = 11
MENU_ABOUT = 21
DEFAULT_IBL = "default"
MATERIAL_NAMES = ["Lit", "Unlit", "Normals", "Depth"]
MATERIAL_SHADERS = [
Settings.LIT, Settings.UNLIT, Settings.NORMALS, Settings.DEPTH
]
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 _apply_settings(self):
bg_color = [
self.settings.bg_color.red, self.settings.bg_color.green,
self.settings.bg_color.blue, self.settings.bg_color.alpha
]
self._scene.scene.set_background(bg_color)
self._scene.scene.show_skybox(self.settings.show_skybox)
self._scene.scene.show_axes(self.settings.show_axes)
if self.settings.new_ibl_name is not None:
self._scene.scene.scene.set_indirect_light(
self.settings.new_ibl_name)
# Clear new_ibl_name, so we don't keep reloading this image every
# time the settings are applied.
self.settings.new_ibl_name = None
self._scene.scene.scene.enable_indirect_light(self.settings.use_ibl)
self._scene.scene.scene.set_indirect_light_intensity(
self.settings.ibl_intensity)
sun_color = [
self.settings.sun_color.red, self.settings.sun_color.green,
self.settings.sun_color.blue
]
self._scene.scene.scene.set_sun_light(self.settings.sun_dir, sun_color,
self.settings.sun_intensity)
self._scene.scene.scene.enable_sun_light(self.settings.use_sun)
if self.settings.apply_material:
self._scene.scene.update_material(self.settings.material)
self.settings.apply_material = False
self._bg_color.color_value = self.settings.bg_color
self._show_skybox.checked = self.settings.show_skybox
self._show_axes.checked = self.settings.show_axes
self._use_ibl.checked = self.settings.use_ibl
self._use_sun.checked = self.settings.use_sun
self._ibl_intensity.int_value = self.settings.ibl_intensity
self._sun_intensity.int_value = self.settings.sun_intensity
self._sun_dir.vector_value = self.settings.sun_dir
self._sun_color.color_value = self.settings.sun_color
self._material_prefab.enabled = (
self.settings.material.shader == Settings.LIT)
c = gui.Color(self.settings.material.base_color[0],
self.settings.material.base_color[1],
self.settings.material.base_color[2],
self.settings.material.base_color[3])
self._material_color.color_value = c
self._point_size.double_value = self.settings.material.point_size
def _on_layout(self, layout_context):
# 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.
r = self.window.content_rect
self._scene.frame = r
width = 17 * layout_context.theme.font_size
height = min(
r.height,
self._settings_panel.calc_preferred_size(
layout_context, gui.Widget.Constraints()).height)
self._settings_panel.frame = gui.Rect(r.get_right() - width, r.y, width,
height)
def _set_mouse_mode_rotate(self):
self._scene.set_view_controls(gui.SceneWidget.Controls.ROTATE_CAMERA)
def _set_mouse_mode_fly(self):
self._scene.set_view_controls(gui.SceneWidget.Controls.FLY)
def _set_mouse_mode_sun(self):
self._scene.set_view_controls(gui.SceneWidget.Controls.ROTATE_SUN)
def _set_mouse_mode_ibl(self):
self._scene.set_view_controls(gui.SceneWidget.Controls.ROTATE_IBL)
def _set_mouse_mode_model(self):
self._scene.set_view_controls(gui.SceneWidget.Controls.ROTATE_MODEL)
def _on_bg_color(self, new_color):
self.settings.bg_color = new_color
self._apply_settings()
def _on_show_skybox(self, show):
self.settings.show_skybox = show
self._apply_settings()
def _on_show_axes(self, show):
self.settings.show_axes = show
self._apply_settings()
def _on_use_ibl(self, use):
self.settings.use_ibl = use
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_use_sun(self, use):
self.settings.use_sun = use
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_lighting_profile(self, name, index):
if name != Settings.CUSTOM_PROFILE_NAME:
self.settings.apply_lighting_profile(name)
self._apply_settings()
def _on_new_ibl(self, name, index):
self.settings.new_ibl_name = gui.Application.instance.resource_path + "/" + name
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_ibl_intensity(self, intensity):
self.settings.ibl_intensity = int(intensity)
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_sun_intensity(self, intensity):
self.settings.sun_intensity = int(intensity)
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_sun_dir(self, sun_dir):
self.settings.sun_dir = sun_dir
self._profiles.selected_text = Settings.CUSTOM_PROFILE_NAME
self._apply_settings()
def _on_sun_color(self, color):
self.settings.sun_color = color
self._apply_settings()
def _on_shader(self, name, index):
self.settings.set_material(AppWindow.MATERIAL_SHADERS[index])
self._apply_settings()
def _on_material_prefab(self, name, index):
self.settings.apply_material_prefab(name)
self.settings.apply_material = True
self._apply_settings()
def _on_material_color(self, color):
self.settings.material.base_color = [
color.red, color.green, color.blue, color.alpha
]
self.settings.apply_material = True
self._apply_settings()
def _on_point_size(self, size):
self.settings.material.point_size = int(size)
self.settings.apply_material = True
self._apply_settings()
def _on_menu_open(self):
dlg = gui.FileDialog(gui.FileDialog.OPEN, "Choose file to load",
self.window.theme)
dlg.add_filter(
".ply .stl .fbx .obj .off .gltf .glb",
"Triangle mesh files (.ply, .stl, .fbx, .obj, .off, "
".gltf, .glb)")
dlg.add_filter(
".xyz .xyzn .xyzrgb .ply .pcd .pts",
"Point cloud files (.xyz, .xyzn, .xyzrgb, .ply, "
".pcd, .pts)")
dlg.add_filter(".ply", "Polygon files (.ply)")
dlg.add_filter(".stl", "Stereolithography files (.stl)")
dlg.add_filter(".fbx", "Autodesk Filmbox files (.fbx)")
dlg.add_filter(".obj", "Wavefront OBJ files (.obj)")
dlg.add_filter(".off", "Object file format (.off)")
dlg.add_filter(".gltf", "OpenGL transfer files (.gltf)")
dlg.add_filter(".glb", "OpenGL binary transfer files (.glb)")
dlg.add_filter(".xyz", "ASCII point cloud files (.xyz)")
dlg.add_filter(".xyzn", "ASCII point cloud with normals (.xyzn)")
dlg.add_filter(".xyzrgb",
"ASCII point cloud files with colors (.xyzrgb)")
dlg.add_filter(".pcd", "Point Cloud Data files (.pcd)")
dlg.add_filter(".pts", "3D Points files (.pts)")
dlg.add_filter("", "All files")
# A file dialog MUST define on_cancel and on_done functions
dlg.set_on_cancel(self._on_file_dialog_cancel)
dlg.set_on_done(self._on_load_dialog_done)
self.window.show_dialog(dlg)
def _on_file_dialog_cancel(self):
self.window.close_dialog()
def _on_load_dialog_done(self, filename):
self.window.close_dialog()
self.load(filename)
def _on_menu_export(self):
dlg = gui.FileDialog(gui.FileDialog.SAVE, "Choose file to save",
self.window.theme)
dlg.add_filter(".png", "PNG files (.png)")
dlg.set_on_cancel(self._on_file_dialog_cancel)
dlg.set_on_done(self._on_export_dialog_done)
self.window.show_dialog(dlg)
def _on_export_dialog_done(self, filename):
self.window.close_dialog()
frame = self._scene.frame
self.export_image(filename, frame.width, frame.height)
def _on_menu_quit(self):
gui.Application.instance.quit()
def _on_menu_toggle_settings_panel(self):
self._settings_panel.visible = not self._settings_panel.visible
gui.Application.instance.menubar.set_checked(
AppWindow.MENU_SHOW_SETTINGS, self._settings_panel.visible)
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 _on_about_ok(self):
self.window.close_dialog()
def load(self, path):
self._scene.scene.clear_geometry()
geometry = None
geometry_type = o3d.io.read_file_geometry_type(path)
mesh = None
if geometry_type & o3d.io.CONTAINS_TRIANGLES:
mesh = o3d.io.read_triangle_mesh(path)
if mesh is not None:
if len(mesh.triangles) == 0:
print(
"[WARNING] Contains 0 triangles, will read as point cloud")
mesh = None
else:
mesh.compute_vertex_normals()
if len(mesh.vertex_colors) == 0:
mesh.paint_uniform_color([1, 1, 1])
geometry = mesh
# Make sure the mesh has texture coordinates
if not mesh.has_triangle_uvs():
uv = np.array([[0.0, 0.0]] * (3 * len(mesh.triangles)))
mesh.triangle_uvs = o3d.utility.Vector2dVector(uv)
else:
print("[Info]", path, "appears to be a point cloud")
if geometry is None:
cloud = None
try:
cloud = o3d.io.read_point_cloud(path)
except Exception:
pass
if cloud is not None:
print("[Info] Successfully read", path)
if not cloud.has_normals():
cloud.estimate_normals()
cloud.normalize_normals()
geometry = cloud
else:
print("[WARNING] Failed to read points", path)
if geometry is not None:
try:
self._scene.scene.add_geometry("__model__", geometry,
self.settings.material)
bounds = geometry.get_axis_aligned_bounding_box()
self._scene.setup_camera(60, bounds, bounds.get_center())
except Exception as e:
print(e)
def export_image(self, path, width, height):
def on_image(image):
img = image
quality = 9 # png
if path.endswith(".jpg"):
quality = 100
o3d.io.write_image(path, img, quality)
self._scene.scene.scene.render_to_image(on_image)
def main():
# We need to initalize the application, which finds the necessary shaders
# for rendering and prepares the cross-platform window abstraction.
gui.Application.instance.initialize()
w = AppWindow(1024, 768)
if len(sys.argv) > 1:
path = sys.argv[1]
if os.path.exists(path):
w.load(path)
else:
w.window.show_message_box("Error",
"Could not open file '" + path + "'")
# Run the event loop. This will not return until the last window is closed.
gui.Application.instance.run()
if __name__ == "__main__":
main()
目测目前官方使用的原生OpenGL,并不是pyqt(要改一下文章名了)
贴一些其他大佬的blog参考一下:
使用python-pcl+pyqt:https://blog.csdn.net/jiugeshao/article/details/109730759
古月居大佬使用PyQtGraph,可以嵌入pyqt中,还有一些其他好用的显示插件:https://www.guyuehome.com/33933
再说一下,open3d格式数据可以和其他比如python-pcl、pyntcloud、pyvista互转,所以用某一个或一些库处理,显示再转格式去显示(要用pyqt的话)
Open3D+PyQT(没细看):
https://blog.csdn.net/weixin_43236944/article/details/124845626