Pythonのezdxfを使ってDXFファイルをPyQt5に表示してみる
2021/09/19 categories:Python| tags:Python|ezdxf|
Pythonのezdxfを使ってDXFデータをPyQt5のGUI上に表示して見ました。
テスト用のDXFデータ
Solid Edge 2D Drafting 2021を使用して簡単な絵を書いて、DXFデータとして保存したデータを使用しました。
DXFファイルを読み込む
readfileでファイル名を指定するとDXFデータを読み込み、ezdxf.document.Drawingのオブジェクトとして読み込めます。DXFを読み込むだけのコードは下記の通りです。
import ezdxf
dxf = ezdxf.readfile('Draft1.dxf')
ezdxf.document.DrawingをPyQt5に表示するビュークラス
ezdxf.addons.drawing.qtviewerに定義されているCADGraphicsViewWithOverlayを使うことで、PyQt5にezdxfで読み込んだDXFデータを表示することが出来ます。表示の切り替え用にウィジェットを用意して、レイヤの表示やマウスオーバーしたアイテムの情報の表示、表示に関するログの表示などを表示できるようにしました。動作は下記の通りです。
ソースコード
# -*- coding: utf-8 -*-
import ezdxf
import sys
from ezdxf.addons.drawing import Frontend, RenderContext
from ezdxf.addons.drawing.pyqt import PyQtBackend, CorrespondingDXFEntity, CorrespondingDXFParentStack
from ezdxf.addons.drawing.properties import is_dark_color
from ezdxf.lldxf.const import DXFStructureError
from ezdxf.addons.drawing.qtviewer import CADGraphicsViewWithOverlay
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(800, 800)
self.render_params = {'linetype_renderer': 'ezdxf'}
self.selectedInfo = SelectedInfo(self)
self.layers = Layers(self)
self.logView = LogView(self)
self.statusLabel = QtWidgets.QLabel()
self.view = CADGraphicsViewWithOverlay()
self.view.setScene(QtWidgets.QGraphicsScene())
self.view.scale(1, -1)
self.setCentralWidget(self.view)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.layers)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.selectedInfo)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.logView)
self.open_file_action = QtWidgets.QAction('Open files')
self.open_file_action.triggered.connect(self.open_file)
self.menuBar().addAction(self.open_file_action)
self.select_layout_menu = self.menuBar().addMenu('Select Layout')
self.statusBar().addPermanentWidget(self.statusLabel)
self.view.element_selected.connect(self.selectedInfo.set_elements)
self.view.mouse_moved.connect(self._on_mouse_moved)
self.layers.updated_signal.connect( lambda : self.draw_layout(self.current_layout) )
def open_file(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Open file', '', 'CAD files (*.dxf *.DXF)')
if filename == '':
return
self.dxf = ezdxf.readfile(filename)
self.render_context = RenderContext(self.dxf)
self.backend = PyQtBackend(use_text_cache=True, params=self.render_params)
self.layers.visible_names = None
self.current_layout = None
self.select_layout_menu.clear()
for layout_name in self.dxf.layout_names_in_taborder():
action = self.select_layout_menu.addAction(layout_name)
action.triggered.connect(self.change_layout)
self.layers.populate_layer_list( self.render_context.layers.values() )
self.draw_layout('Model')
self.setWindowTitle('CAD Viewer - ' + filename)
def change_layout(self):
layout_name = self.sender().text()
self.draw_layout(layout_name)
def draw_layout(self, layout_name):
self.current_layout = layout_name
self.view.begin_loading()
new_scene = QtWidgets.QGraphicsScene()
self.backend.set_scene(new_scene)
layout = self.dxf.layout(layout_name)
self.render_context.set_current_layout(layout)
if self.layers.visible_names is not None:
self.render_context.set_layers_state(self.layers.visible_names, state=True)
try:
frontend = MyFrontend(self.render_context, self.backend)
frontend.log_view = self.logView
frontend.draw_layout(layout)
except DXFStructureError as e:
self.logView.append('DXF Structure Error')
self.logView.append(f'Abort rendering of layout "{layout_name}": {str(e)}')
finally:
self.backend.finalize()
self.view.end_loading(new_scene)
self.view.buffer_scene_rect()
self.view.fit_to_scene()
self.view.setScene(new_scene)
def _on_mouse_moved(self, mouse_pos: QtCore.QPointF):
self.statusLabel.setText( f'mouse position: {mouse_pos.x():.4f}, {mouse_pos.y():.4f}\n' )
class SelectedInfo(QtWidgets.QDockWidget):
def __init__(self, parent=None):
super(SelectedInfo, self).__init__(parent)
self.text = QtWidgets.QPlainTextEdit()
self.text.setReadOnly(True)
self.setWidget( QtWidgets.QWidget() )
self.widget().setLayout( QtWidgets.QVBoxLayout() )
self.widget().layout().addWidget(self.text)
self.setWindowTitle('Selected Info')
def set_elements(self, elements, index):
def _entity_attribs_string(dxf_entity, indent=''):
text = ''
for key, value in dxf_entity.dxf.all_existing_dxf_attribs().items():
text += f'{indent}- {key}: {value}\n'
return text
if not elements:
text = 'No element selected'
else:
text = f'Selected: {index + 1} / {len(elements)} (click to cycle)\n'
element = elements[index]
dxf_entity = element.data(CorrespondingDXFEntity)
if dxf_entity is None:
text += 'No data'
else:
text += f'Selected Entity: {dxf_entity}\nLayer: {dxf_entity.dxf.layer}\n\nDXF Attributes:\n'
text += _entity_attribs_string(dxf_entity)
dxf_parent_stack = element.data(CorrespondingDXFParentStack)
if dxf_parent_stack:
text += '\nParents:\n'
for entity in reversed(dxf_parent_stack):
text += f'- {entity}\n'
text += _entity_attribs_string(entity, indent=' ')
self.text.setPlainText(text)
class Layers(QtWidgets.QDockWidget):
updated_signal = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super(Layers, self).__init__(parent)
self.visible_names = None
self.model = QtGui.QStandardItemModel()
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
self.view.setStyleSheet( 'QListWidget {font-size: 12pt;} QCheckBox {font-size: 12pt; padding-left: 5px;}' )
self.setWidget( QtWidgets.QWidget() )
self.widget().setLayout( QtWidgets.QVBoxLayout() )
self.widget().layout().addWidget(self.view)
self.setWindowTitle('Layers')
self.model.dataChanged.connect(self.layers_updated)
def populate_layer_list(self, layers):
self.model.clear()
for layer in layers:
item = QtGui.QStandardItem(layer.layer)
item.setData(layer)
item.setCheckable(True)
item.setCheckState( QtCore.Qt.Checked if layer.is_visible else QtCore.Qt.Unchecked )
text_color = '#FFFFFF' if is_dark_color(layer.color, 0.4) else '#000000'
item.setForeground( QtGui.QBrush(QtGui.QColor(text_color)) )
item.setBackground( QtGui.QBrush(QtGui.QColor(layer.color)) )
self.model.appendRow(item)
def layers_updated(self):
self.visible_names = []
for row in range( self.model.rowCount() ):
item = self.model.item(row, 0)
if item.checkState() == QtCore.Qt.Checked:
self.visible_names.append( item.text() )
self.updated_signal.emit(self.visible_names)
class LogView(QtWidgets.QDockWidget):
def __init__(self, parent=None):
super(LogView, self).__init__(parent)
self.text = QtWidgets.QTextBrowser()
self.setWidget( QtWidgets.QWidget() )
self.widget().setLayout( QtWidgets.QVBoxLayout() )
self.widget().layout().addWidget(self.text)
self.setWindowTitle('Log')
def append(self, text):
self.text.append(text)
class MyFrontend(Frontend):
log_view = None
def log_message(self, message):
self.log_view.append(message)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()