forked from SuzanneSoy/SearchBar
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSafeViewer.py
161 lines (137 loc) · 5.89 KB
/
SafeViewer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from PySide import QtGui
import FreeCAD as App
# Define the translation
translate = App.Qt.translate
class SafeViewer(QtGui.QWidget):
"""FreeCAD uses a modified version of QuarterWidget, so the import pivy.quarter one will cause segfaults.
FreeCAD's FreeCADGui.createViewer() puts the viewer widget inside an MDI window, and detaching it without causing segfaults on exit is tricky.
This class contains some kludges to extract the viewer as a standalone widget and destroy it safely.
"""
enabled = App.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").GetBool(
"PreviewEnabled", False
)
instances = []
def __init__(self, parent=None):
super(SafeViewer, self).__init__()
SafeViewer.instances.append(self)
self.init_parent = parent
self.instance_enabled = False # Has this specific instance been enabled?
if SafeViewer.enabled:
self.displaying_warning = False
self.enable()
else:
import FreeCADGui
from PySide import QtCore
self.displaying_warning = True
self.lbl_warning = QtGui.QTextEdit()
self.lbl_warning.setReadOnly(True)
self.lbl_warning.setAlignment(QtCore.Qt.AlignTop)
self.lbl_warning.setText(
translate(
"SearchBar",
"Warning: the 3D preview has some stability issues. It can cause FreeCAD to crash (usually when quitting the application) and could in theory cause data loss, inside and outside of App.",
)
)
self.btn_enable_for_this_session = QtGui.QPushButton(
translate("SearchBar", "Enable 3D preview for this session")
)
self.btn_enable_for_this_session.clicked.connect(
self.enable_for_this_session
)
self.btn_enable_for_future_sessions = QtGui.QPushButton(
translate("SearchBar", "Enable 3D preview for future sessions")
)
self.btn_enable_for_future_sessions.clicked.connect(
self.enable_for_future_sessions
)
self.setLayout(QtGui.QVBoxLayout())
self.layout().addWidget(self.lbl_warning)
self.layout().addWidget(self.btn_enable_for_this_session)
self.layout().addWidget(self.btn_enable_for_future_sessions)
def enable_for_this_session(self):
if not SafeViewer.enabled:
for instance in SafeViewer.instances:
instance.enable()
def enable_for_future_sessions(self):
if not SafeViewer.enabled:
# Store in prefs
App.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").SetBool(
"PreviewEnabled", True
)
# Then enable as usual
self.enable_for_this_session()
def enable(self):
if not self.instance_enabled:
import FreeCADGui
# TODO: use a mutex wrapping the entire method, if possible
SafeViewer.enabled = True
self.instance_enabled = True # Has this specific instance been enabled?
if self.displaying_warning:
self.layout().removeWidget(self.lbl_warning)
self.layout().removeWidget(self.btn_enable_for_this_session)
self.layout().removeWidget(self.btn_enable_for_future_sessions)
self.viewer = FreeCADGui.createViewer()
self.graphicsView = self.viewer.graphicsView()
self.oldGraphicsViewParent = self.graphicsView.parent()
self.oldGraphicsViewParentParent = self.oldGraphicsViewParent.parent()
self.oldGraphicsViewParentParentParent = (
self.oldGraphicsViewParentParent.parent()
)
# Avoid segfault but still hide the undesired window by moving it to a new hidden MDI area.
self.hiddenQMDIArea = QtGui.QMdiArea()
self.hiddenQMDIArea.addSubWindow(self.oldGraphicsViewParentParentParent)
self.private_widget = self.oldGraphicsViewParent
self.private_widget.setParent(self.init_parent)
self.setLayout(QtGui.QVBoxLayout())
self.layout().addWidget(self.private_widget)
self.layout().setContentsMargins(0, 0, 0, 0)
def fin(slf):
slf.finalizer()
import weakref
weakref.finalize(self, fin, self)
self.destroyed.connect(self.finalizer)
def finalizer(self):
# Cleanup in an order that doesn't cause a segfault:
if SafeViewer.enabled:
self.private_widget.setParent(self.oldGraphicsViewParentParent)
self.oldGraphicsViewParentParentParent.close()
self.oldGraphicsViewParentParentParent = None
self.oldGraphicsViewParentParent = None
self.oldGraphicsViewParent = None
self.graphicsView = None
self.viewer = None
# self.parent = None
self.init_parent = None
self.hiddenQMDIArea = None
def showSceneGraph(self, g):
import FreeCAD as App
if SafeViewer.enabled:
self.viewer.getViewer().setSceneGraph(g)
self.viewer.setCameraOrientation(App.Rotation(1, 1, 0, 0.2))
self.viewer.fitAll()
"""
# Example use:
from PySide import QtGui
import pivy
from SafeViewer import SafeViewer
sv = SafeViewer()
def mk(v):
w = QtGui.QMainWindow()
oldFocus = QtGui.QApplication.focusWidget()
sv.widget.setParent(w)
oldFocus.setFocus()
w.show()
col = pivy.coin.SoBaseColor()
col.rgb = (1, 0, 0)
trans = pivy.coin.SoTranslation()
trans.translation.setValue([0, 0, 0])
cub = pivy.coin.SoCube()
myCustomNode = pivy.coin.SoSeparator()
myCustomNode.addChild(col)
myCustomNode.addChild(trans)
myCustomNode.addChild(cub)
sv.viewer.getViewer().setSceneGraph(myCustomNode)
sv.viewer.fitAll()
return w
ww=mk(sv)
"""