总目录 >> PythonOCC入门进阶到实战(目前已更新入门篇、基础篇和进阶篇)
写在前面
刚开始的时候,只是比较多的关注功能层的实现,但是随着项目的推进,慢慢发现现有官方提供的主界面过于简单,再加上一些cad开发者也在不断地询问哪里有界面开发的教程,我想稍微总结一下之前做界面的经历,希望能够给大家一些启发和帮助。如果你觉得有用,关注下我,这就是对我最大的支持。
1.开发前提
首先要确认你已经安装了pyqt5,没有安装的,自行安装pip install pyqt5
关于使用pyside,wxpython的朋友,这方面我没太研究,但是本质应该差别不大。
本教程只是对主界面进行一个初步开发,如果你想拥有比较完善美观的界面,可以参考下这本书:pyqt5快速开发与实践
2.依赖文件 qtDisplay. py
将如下代码复制到名为qtDisplay. py,放到和主程序mainwindow(下面讲)同一文件夹
import logging
import os
import sys
from OCC.Display import OCCViewer
from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL
# check if signal available, not available
# on PySide
HAVE_PYQT_SIGNAL = hasattr(QtCore, 'pyqtSignal')
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
log = logging.getLogger(__name__)
class point(object):
def __init__(self, obj=None):
self.x = 0
self.y = 0
if obj is not None:
self.set(obj)
def set(self, obj):
self.x = obj.x()
self.y = obj.y()
class qtBaseViewer(QtOpenGL.QGLWidget):
''' The base Qt Widget for an OCC viewer
'''
def __init__(self, parent=None):
QtOpenGL.QGLWidget.__init__(self, parent)
self._display = None
self._inited = False
# enable Mouse Tracking
self.setMouseTracking(True)
# Strong focus
self.setFocusPolicy(QtCore.Qt.WheelFocus)
# required for overpainting the widget
self.setAttribute(QtCore.Qt.WA_PaintOnScreen)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setAutoFillBackground(False)
def GetHandle(self):
''' returns an the identifier of the GUI widget.
It must be an integer
'''
win_id = self.winId() # this returns either an int or voitptr
if "%s" % type(win_id) == "<type 'PyCObject'>": # PySide
### with PySide, self.winId() does not return an integer
if sys.platform == "win32":
## Be careful, this hack is py27 specific
## does not work with python31 or higher
## since the PyCObject api was changed
import ctypes
ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ctypes.c_void_p
ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = [
ctypes.py_object]
win_id = ctypes.pythonapi.PyCObject_AsVoidPtr(win_id)
elif not isinstance(win_id, int): # PyQt4 or 5
## below integer cast may be required because self.winId() can
## returns a sip.voitptr according to the PyQt version used
## as well as the python version
win_id = int(win_id)
return win_id
def resizeEvent(self, event):
if self._inited:
super(qtBaseViewer,self).resizeEvent(event)
self._display.OnResize()
class qtViewer3d(qtBaseViewer):
# emit signal when selection is changed
# is a list of TopoDS_*
if HAVE_PYQT_SIGNAL:
sig_topods_selected = QtCore.pyqtSignal(list)
def __init__(self, *kargs):
qtBaseViewer.__init__(self, *kargs)
self.setObjectName("qt_viewer_3d")
self._drawbox = False
self._zoom_area = False
self._select_area = False
self._inited = False
self._leftisdown = False
self._middleisdown = False
self._rightisdown = False
self._selection = None
self._drawtext = True
self._qApp = QtWidgets.QApplication.instance()
self._key_map = {}
self._current_cursor = "arrow"
self._available_cursors = {}
@property
def qApp(self):
# reference to QApplication instance
return self._qApp
@qApp.setter
def qApp(self, value):
self._qApp = value
def InitDriver(self):
self._display = OCCViewer.Viewer3d(self.GetHandle())
self._display.Create()
# background gradient
self._display.set_bg_gradient_color(206, 215, 222, 128, 128, 128)
# background gradient
self._display.display_trihedron()
self._display.SetModeShaded()
self._display.DisableAntiAliasing()
self._inited = True
# dict mapping keys to functions
self._SetupKeyMap()
#
self._display.thisown = False
self.createCursors()
def createCursors(self):
module_pth = os.path.abspath(os.path.dirname(__file__))
icon_pth = os.path.join(module_pth, "icons")
_CURSOR_PIX_ROT = QtGui.QPixmap(os.path.join(icon_pth, "cursor-rotate.png"))
_CURSOR_PIX_PAN = QtGui.QPixmap(os.path.join(icon_pth, "cursor-pan.png"))
_CURSOR_PIX_ZOOM = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify.png"))
_CURSOR_PIX_ZOOM_AREA = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify-area.png"))
self._available_cursors = {
"arrow": QtGui.QCursor(QtCore.Qt.ArrowCursor), # default
"pan": QtGui.QCursor(_CURSOR_PIX_PAN),
"rotate": QtGui.QCursor(_CURSOR_PIX_ROT),
"zoom": QtGui.QCursor(_CURSOR_PIX_ZOOM),
"zoom-area": QtGui.QCursor(_CURSOR_PIX_ZOOM_AREA),
}
self._current_cursor = "arrow"
def _SetupKeyMap(self):
self._key_map = {ord('W'): self._display.SetModeWireFrame,
ord('S'): self._display.SetModeShaded,
ord('A'): self._display.EnableAntiAliasing,
ord('B'): self._display.DisableAntiAliasing,
ord('H'): self._display.SetModeHLR,
ord('F'): self._display.FitAll,
ord('G'): self._display.SetSelectionMode}
def keyPressEvent(self, event):
code = event.key()
if code in self._key_map:
self._key_map[code]()
else:
log.info("key: %s \\nnot mapped to any function", code)
def Test(self):
if self._inited:
self._display.Test()
def focusInEvent(self, event):
if self._inited:
self._display.Repaint()
def focusOutEvent(self, event):
if self._inited:
self._display.Repaint()
def paintEvent(self, event):
if self._inited:
self._display.Context.UpdateCurrentViewer()
# important to allow overpainting of the OCC OpenGL context in Qt
self.swapBuffers()
if self._drawbox:
self.makeCurrent()
painter = QtGui.QPainter(self)
painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1))
rect = QtCore.QRect(*self._drawbox)
painter.drawRect(rect)
painter.end()
self.doneCurrent()
def ZoomAll(self, evt):
self._display.FitAll()
def wheelEvent(self, event):
try: # PyQt4/PySide
delta = event.delta()
except: # PyQt5
delta = event.angleDelta().y()
if delta > 0:
zoom_factor = 2.
else:
zoom_factor = 0.5
self._display.Repaint()
self._display.ZoomFactor(zoom_factor)
def dragMoveEvent(self, event):
pass
@property
def cursor(self):
return self._current_cursor
@cursor.setter
def cursor(self, value):
if not self._current_cursor == value:
self._current_cursor = value
cursor = self._available_cursors.get(value)
if cursor:
self.qApp.setOverrideCursor(cursor)
else:
self.qApp.restoreOverrideCursor()
def mousePressEvent(self, event):
self.setFocus()
self.dragStartPos = point(event.pos())
self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y)
def mouseReleaseEvent(self, event):
pt = point(event.pos())
modifiers = event.modifiers()
if event.button() == QtCore.Qt.LeftButton:
if self._select_area:
[Xmin, Ymin, dx, dy] = self._drawbox
self._display.SelectArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
self._select_area = False
else:
# multiple select if shift is pressed
if modifiers == QtCore.Qt.ShiftModifier:
self._display.ShiftSelect(pt.x, pt.y)
else:
# single select otherwise
self._display.Select(pt.x, pt.y)
if (self._display.selected_shapes is not None) and HAVE_PYQT_SIGNAL:
self.sig_topods_selected.emit(self._display.selected_shapes)
elif event.button() == QtCore.Qt.RightButton:
if self._zoom_area:
[Xmin, Ymin, dx, dy] = self._drawbox
self._display.ZoomArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
self._zoom_area = False
self.cursor = "arrow"
def DrawBox(self, event):
tolerance = 2
pt = point(event.pos())
dx = pt.x - self.dragStartPos.x
dy = pt.y - self.dragStartPos.y
if abs(dx) <= tolerance and abs(dy) <= tolerance:
return
self._drawbox = [self.dragStartPos.x, self.dragStartPos.y, dx, dy]
self.update()
def mouseMoveEvent(self, evt):
pt = point(evt.pos())
buttons = int(evt.buttons())
modifiers = evt.modifiers()
# ROTATE
if (buttons == QtCore.Qt.LeftButton and
not modifiers == QtCore.Qt.ShiftModifier):
dx = pt.x - self.dragStartPos.x
dy = pt.y - self.dragStartPos.y
self.cursor = "rotate"
self._display.Rotation(pt.x, pt.y)
self._drawbox = False
# DYNAMIC ZOOM
elif (buttons == QtCore.Qt.RightButton and
not modifiers == QtCore.Qt.ShiftModifier):
self.cursor = "zoom"
self._display.Repaint()
self._display.DynamicZoom(abs(self.dragStartPos.x),
abs(self.dragStartPos.y), abs(pt.x),
abs(pt.y))
self.dragStartPos.x = pt.x
self.dragStartPos.y = pt.y
self._drawbox = False
# PAN
elif buttons == QtCore.Qt.MidButton:
dx = pt.x - self.dragStartPos.x
dy = pt.y - self.dragStartPos.y
self.dragStartPos.x = pt.x
self.dragStartPos.y = pt.y
self.cursor = "pan"
self._display.Pan(dx, -dy)
self._drawbox = False
# DRAW BOX
# ZOOM WINDOW
elif (buttons == QtCore.Qt.RightButton and
modifiers == QtCore.Qt.ShiftModifier):
self._zoom_area = True
self.cursor = "zoom-area"
self.DrawBox(evt)
# SELECT AREA
elif (buttons == QtCore.Qt.LeftButton and
modifiers == QtCore.Qt.ShiftModifier):
self._select_area = True
self.DrawBox(evt)
else:
self._drawbox = False
self._display.MoveTo(pt.x, pt.y)
self.cursor = "arrow"
3.主文件 mainwindow. py
# -*- coding: UTF-8 -*-
import sys
from OCC.gp import gp_Pnt
from OCC.Extend.DataExchange import read_iges_file
from PyQt5.QtGui import QIcon
from qtDisplay import qtViewer3d
from PyQt5 import QtCore
from PyQt5.QtWidgets import QFileDialog, QAction, QApplication, QMenu, QMainWindow, QHBoxLayout, QDockWidget, \\
QListWidget, QLabel, qApp
class MainWindow(QMainWindow):
def __init__(self, *args):
''' 初始化界面 '''
# 中心控件
# 中心控件
QMainWindow.__init__(self, *args)
layout=QHBoxLayout() # 水平布局
self.canva = qtViewer3d(self)
# 显示设置
self.canva.InitDriver() # canva的驱动,设置驱动后,才能成功display
display = self.canva._display
display.set_bg_gradient_color(206, 215, 222, 128, 128, 128) # 设置背景渐变色
display.display_trihedron() # display black trihedron
self.setWindowTitle("Aim")
# self.setWindowIcon(QIcon('./icons/aim.png')) #设置程序图标
self.setCentralWidget(self.canva)
self.resize(1024,768)
self.centerOnScreen()
# 容器控件
# 容器控件
self.items=QDockWidget('功能区',self)
self.listWidget=QListWidget()
self.listWidget.addItem('item1')
self.listWidget.addItem('item2')
self.listWidget.addItem('item3')
self.items.setWidget(self.listWidget)
self.items.setFloating(False)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea,self.items)
# #### 菜单栏
# #### 菜单栏
menubar = self.menuBar()
# 第一个菜单栏‘打开文件’
file=menubar.addMenu('&打开文件')
file.addAction('模型导入')
file.addAction('打开表格文件')
file.addAction('打开图片')
# #### 工具栏
#工具一:打开文件
exitAction = QAction(QIcon('./icons/open.gif'), '打开文件', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(QFileDialog.getOpenFileName)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
# #### label标签
label=QLabel(self)
label.move(900,0)
label.setText("<a href='https://blog.csdn.net/weixin_42755384/article/details/87893697'>PythonOCC帮助</a>")
label.setOpenExternalLinks(True)
self.setLayout(layout)
# 临时读入一个文件
P0 = gp_Pnt(0, 0, 1)
P1 = gp_Pnt(0, 30, 20)
display.DisplayShape(P0)
display.DisplayShape(P1)
# display.DisplayShape(shapes,update=True)
def centerOnScreen(self):
'''屏幕居中'''
resolution = QApplication.desktop().screenGeometry()
x = (resolution.width() - self.frameSize().width()) / 2
y = (resolution.height() - self.frameSize().height()) / 2
self.move(x, y)
if __name__ == '__main__':
app = QApplication(sys.argv) # 创建应用
win = MainWindow() # 创建主窗口
win.canva.qApp = app # 将自己创建的应用与canva中的 Qapp连接起来
win.show() # 主窗口显示
win.canva.InitDriver() # 社区反馈,添加此语句,以下的bug不会出现。
win.raise_() # 窗口置顶
sys.exit(app.exec_())
如果没什么意外的话,会出现如下界面:
这里是有一个小bug的,就是显示区是黑的,但是然后你全屏一下就恢复了,后面我再修复这个bug。
理想版本如下
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容