You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Open3D模型编辑窗口按Q关闭时主GUI退出的问题解决请求

问题解决:关闭Open3D编辑窗口不影响主GUI

问题原因

o3d.visualization.draw_geometries_with_editing属于Open3D的旧版阻塞式可视化接口,它会接管整个应用的事件循环。当按下Q关闭该窗口时,会触发全局应用退出信号,导致主GUI窗口也被关闭。

修改方案

通过子进程隔离旧版可视化窗口的运行,子进程的退出不会影响主进程的GUI事件循环。具体逻辑:

  1. 用临时文件传递点云数据(Open3D对象无法直接跨进程传递)
  2. 将裁剪窗口的逻辑放到子进程中执行,主进程继续维持GUI运行

修改后的代码

import open3d.visualization.gui as gui
import open3d.visualization.rendering as rendering
import os
import numpy as np
import multiprocessing
import tempfile

class Open3dApp:
    def __init__(self):
        self.window = gui.Application.instance.create_window("3D Full Body Human Viewer", 1400, 900)
        win = self.window
        self.current_file = None
        self.Clean3DPLY = None
        # Check if file exists
        if os.path.isfile('Clean body.ply'):
            self.Clean3DPLY = 'Clean body.ply'

        # member variables
        self.model_dir = ""
        self.model_name = ""
        self.pcd = None  # to store the loaded .ply file
        self._widget3d = None  # to store the 3D widget
        self.file_path = ""  # 补充初始化缺失变量

        em = win.theme.font_size
        # 3D Widget
        self.test = gui.Combobox()
        self.test.add_item("First")

        self._widget3d = gui.SceneWidget()
        self._widget3d.scene = rendering.Open3DScene(win.renderer)  # 修正原代码中错误的变量引用
        self._widget3d.set_view_controls(gui.SceneWidget.Controls.ROTATE_CAMERA)
        # create a frame that encapsulates the SceneWidget
        self._widget3d.frame = gui.Rect(500, win.content_rect.y, 900, win.content_rect.height)
        mesh = o3d.geometry.TriangleMesh.create_sphere()
        mesh.compute_vertex_normals()
        material = rendering.MaterialRecord()
        material.shader = "defaultLit"
        self._widget3d.scene.add_geometry('mesh', mesh, material)
        self._widget3d.scene.set_background([200, 0, 0, 200])
        self._widget3d.scene.camera.look_at([0, 0, 0], [1, 1, 1], [0, 0, 1])

        # gui layout
        gui_layout = gui.Vert(0, gui.Margins(0.5 * em, 0.5 * em, 0.5 * em, 0.5 * em))
        # create frame that encapsulates the gui
        gui_layout.frame = gui.Rect(win.content_rect.x, win.content_rect.y, 500, win.content_rect.height)
        # File-chooser widget
        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)
        # Add a fixed height empty layout for spacing
        empty_layout = gui.Vert(0)
        empty_layout.add_fixed(1 * em)
        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
        gui_layout.add_child(fileedit_layout)

        # Add a fixed height empty layout for spacing
        empty_layout = gui.Vert(0)
        empty_layout.add_fixed(1 * em)
        gui_layout.add_child(empty_layout)

        # start pre processing
        collapse = gui.CollapsableVert("Pre Processing Data", 0.33 * em,
                                       gui.Margins(em, 0, 0, 0))

        # Additional feature buttons
        feature_button1 = gui.Button("Geometry Cropping")
        feature_button1.set_on_clicked(self.demo_3d)
        feature_button2 = gui.Button("Segmentation")
        feature_button2.set_on_clicked(self.clean3d)

        geometry_button4 = gui.Button("Exit")
        geometry_button4.set_on_clicked(self._on_exit_click)

        gui_layout.add_child(geometry_button4)
        gui_layout.add_child(empty_layout)

        win.add_child(gui_layout)
        win.add_child(self._widget3d)

    def _on_filedlg_button(self):
        # 补充文件选择对话框实现
        dlg = gui.FileDialog(gui.FileDialog.OPEN, "Select Model File", self.window.theme)
        dlg.add_filter(".ply", "PLY files")
        def on_file_selected(filename):
            if filename:
                self.file_path = filename
                self._fileedit.text = filename
        dlg.set_on_cancel(lambda: None)
        dlg.set_on_done(on_file_selected)
        self.window.show_dialog(dlg)

    def clean3d(self):
        # 补充缺失的方法实现
        pass

    def _on_exit_click(self):
        gui.Application.instance.quit()

    def demo_3d(self):
        if not self.file_path:
            print("Error: Please select a file")
        else:
            # Load the point cloud data from the selected file
            indi = o3d.io.read_point_cloud(self.file_path)

            # 保存点云到临时文件,供子进程读取
            with tempfile.NamedTemporaryFile(suffix=".ply", delete=False) as temp_file:
                temp_path = temp_file.name
            o3d.io.write_point_cloud(temp_path, indi)

            # 启动子进程打开裁剪窗口,不阻塞主GUI
            p = multiprocessing.Process(target=self._run_crop_geometry, args=(temp_path,))
            p.start()

            self.current_file = self.Clean3DPLY

    @staticmethod
    def _run_crop_geometry(temp_path):
        # 子进程中读取临时文件并打开编辑窗口
        indi = o3d.io.read_point_cloud(temp_path)
        print("Demo for manual geometry cropping")
        print(
            "1) Press 'Y' twice to align geometry with negative direction of y-axis"
        )
        print("2) Press 'K' to lock screen and to switch to selection mode")
        print("3) Drag for rectangle selection,")
        print("   or use ctrl + left click for polygon selection")
        print("4) Press 'C' to get a selected geometry and to save it")
        print("5) Press 'F' to switch to freeview mode")
        o3d.visualization.draw_geometries_with_editing([indi], width=800, height=900, left=1050, top=100)
        # 子进程结束后删除临时文件
        os.unlink(temp_path)

if __name__ == "__main__":
    # 必须在main函数中启动GUI,避免多进程冲突
    gui.Application.instance.initialize()
    app = Open3dApp()
    gui.Application.instance.run()

关键修改说明

  • 新增multiprocessing和tempfile模块,实现进程隔离与跨进程数据传递
  • 将裁剪窗口逻辑移到静态方法_run_crop_geometry,作为子进程执行目标
  • 在demo_3d中通过临时文件传递点云,启动子进程后立即返回,不阻塞主GUI
  • 修复原代码中的变量引用错误、缺失方法实现等问题

内容的提问来源于stack exchange,提问作者Ali Habib Haidar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 05:20:59