def set_document( self, document: Drawing, auditor: Auditor, *, layout: str = "Model", overall_scaling_factor: float = 1.0, ): error_count = len(auditor.errors) if error_count > 0: ret = qw.QMessageBox.question( self, "Found DXF Errors", f'Found {error_count} errors in file "{document.filename}"\n' f"Load file anyway? ", ) if ret == qw.QMessageBox.No: auditor.print_error_report(auditor.errors) return self.doc = document self._render_context = RenderContext(document) self._reset_backend(scale=overall_scaling_factor) self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout(layout) self.setWindowTitle("CAD Viewer - " + str(document.filename))
def convert_dxf2img(self, names, img_format=default_img_format, img_res=default_img_res): for name in names: doc = ezdxf.readfile(name) msp = doc.modelspace() # Recommended: audit & repair DXF document before rendering auditor = doc.audit() # The auditor.errors attribute stores severe errors, # which *may* raise exceptions when rendering. if len(auditor.errors) != 0: raise exception( "The DXF document is damaged and can't be converted!") else: fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) ctx.set_current_layout(msp) ctx.current_layout.set_colors(bg='#FFFFFF') out = MatplotlibBackend(ax) Frontend(ctx, out).draw_layout(msp, finalize=True) img_name = re.findall( "(\S+)\.", name ) # select the image name that is the same as the dxf file name first_param = ''.join( img_name) + img_format #concatenate list and string fig.savefig(first_param, dpi=img_res) #if __name__ == '__main__': # first = DXF2IMG() # first.convert_dxf2img(['slot_star.dxf'],img_format='.png')
def convert_dxf2img(self, names, img_format=default_img_format, img_res=default_img_res, clr=default_bg_color): for name in names: doc = ezdxf.readfile(name) msp = doc.modelspace() # Recommended: audit & repair DXF document before rendering auditor = doc.audit() # The auditor.errors attribute stores severe errors, # which *may* raise exceptions when rendering. if len(auditor.errors) != 0: raise Exception( "This DXF document is damaged and can't be converted! --> ", name) name = name = +1 else: fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) ctx.set_current_layout(msp) ezdxf.addons.drawing.properties.MODEL_SPACE_BG_COLOR = clr out = MatplotlibBackend(ax) Frontend(ctx, out).draw_layout(msp, finalize=True) img_name = re.findall( "(\S+)\.", name ) # select the image name that is the same as the dxf file name first_param = ''.join( img_name) + img_format #concatenate list and string fig.savefig(first_param, dpi=img_res) print(name, " Converted Successfully")
def set_document(self, document: Drawing): self.doc = document self._render_context = RenderContext(document) self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout('Model')
def _get_text_visible_when(doc: Drawing, active_layers: Set[str]) -> List[str]: ctx = RenderContext(doc) # set given layer to ON, others to OFF ctx.set_layers_state(active_layers, state=True) backend = BasicBackend() Frontend(ctx, backend).draw_layout(doc.modelspace()) visible_text = [x[1] for x in backend.collector if x[0] == 'text'] return visible_text
def main(): parser = argparse.ArgumentParser() parser.add_argument('dxf_file') parser.add_argument('output_path') parser.add_argument( "--scale", "-s", type=float, default=1.0, help="text scaling factor", ) args = parser.parse_args() doc = ezdxf.readfile(args.dxf_file) layout = doc.modelspace() fig: plt.Figure = plt.figure() ax: plt.Axes = fig.add_axes((0, 0, 1, 1)) ctx = RenderContext(layout.doc) layout_properties = LayoutProperties.from_layout(layout) out = FixedSizedTextMatplotlibBackend(ax, text_size_scale=args.scale) Frontend(ctx, out).draw_layout( layout, finalize=True, layout_properties=layout_properties, ) fig.savefig( args.output_path, dpi=300, facecolor=ax.get_facecolor(), transparent=True, ) plt.close(fig)
def _main(): parser = argparse.ArgumentParser( description='draw the given CAD file and save it to a file or view it') parser.add_argument('cad_file', nargs='?') parser.add_argument('--supported_formats', action='store_true') parser.add_argument('--layout', default='Model') parser.add_argument('--out', required=False) parser.add_argument('--dpi', type=int, default=300) parser.add_argument('--ltype', default='internal') args = parser.parse_args() if args.supported_formats: fig = plt.figure() for extension, description in fig.canvas.get_supported_filetypes().items(): print(f'{extension}: {description}') sys.exit() if args.cad_file is None: print('no CAD file specified') sys.exit(1) try: doc = ezdxf.readfile(args.cad_file) except IOError: print(f'Not a DXF file or a generic I/O error.') sys.exit(2) except ezdxf.DXFError: try: doc, auditor = recover.readfile(args.cad_file) except ezdxf.DXFStructureError: print(f'Invalid or corrupted DXF file: {args.cad_file}') sys.exit(3) else: auditor = doc.audit() if auditor.has_errors: # But is most likely good enough for rendering. print(f'Found {len(auditor.errors)} unrecoverable errors.') if auditor.has_fixes: print(f'Fixed {len(auditor.fixes)} errors.') try: layout = doc.layouts.get(args.layout) except KeyError: print(f'Could not find layout "{args.layout}". ' f'Valid layouts: {[l.name for l in doc.layouts]}') sys.exit(4) fig: plt.Figure = plt.figure() ax: plt.Axes = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) out = MatplotlibBackend(ax, params={'linetype_renderer': args.ltype}) Frontend(ctx, out).draw_layout(layout, finalize=True) if args.out is not None: print(f'saving to "{args.out}"') fig.savefig(args.out, dpi=args.dpi) plt.close(fig) else: plt.show()
def set_document(self, document: Drawing, auditor: Auditor): error_count = len(auditor.errors) if error_count > 0: ret = qw.QMessageBox.question( self, 'Found DXF Errors', f'Found {error_count} errors in file "{document.filename}"\nLoad file anyway? ' ) if ret == qw.QMessageBox.No: auditor.print_error_report(auditor.errors) return self.doc = document self._render_context = RenderContext(document) self._reset_backend() # clear caches self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout('Model') self.setWindowTitle('CAD Viewer - ' + str(document.filename))
def set_document(self, document: Drawing): self.doc = document self._render_context = RenderContext(document) self._visible_layers = None self._current_layout = None # self._populate_layouts() self._populate_layer_list() self.draw_layout('Model') self.model_space = self.doc.modelspace() self.all_dxf_entities = {} for entity in self.model_space.entity_space: self.all_dxf_entities[str(entity)] = entity if entity.DXFTYPE == "CIRCLE": if hasattr(entity.dxf, 'extrusion'): print(entity.dxf.extrusion) if entity.dxf.extrusion[2] == -1: entity.dxf.extrusion = Vector(0, 0, 1) # entity.dxf.center = Vector(-(entity.dxf.center.x), -(entity.dxf.center.y), 0) self.draw_origin() self.apply_origin_to_scene()
def main(rows: int, cols: int): doc = ezdxf.new() msp = doc.modelspace() create_content(msp) # Detecting the drawing extents by ezdxf: # The bounding box cache can be reused for entity filtering. # This cache is a lightweight object, which is compatible to the pickle # module, DXF entities are referenced by handle strings. (multiprocessing!) cache = bbox.Cache() # The bounding box calculation can take along time for big DXF files! # If flatten=0 the bounding box calculation for curves (SPLINE, ELLIPSE, ...) # is based on the control points of the Path class, this is less precise but # can speed up the calculation and for this task is a precise bounding box # not required. # This has no impact on this example which uses only straight lines extents = bbox.extents(msp, cache=cache, flatten=0) ctx = RenderContext(doc) for tile, render_area in enumerate(render_areas(extents, (rows, cols))): # Setup drawing add-on: fig = plt.figure(dpi=DPI) ax = fig.add_axes([0, 0, 1, 1]) out = MatplotlibBackend(ax) ax.set_xlim(render_area.extmin.x, render_area.extmax.x) ax.set_ylim(render_area.extmin.y, render_area.extmax.y) # Disable rendering of entities outside of the render area: def is_intersecting_render_area(entity): """Returns True if entity should be rendered. """ entity_bbox = bbox.extents([entity], cache=cache, flatten=0) return render_area.has_intersection(entity_bbox) # Finalizing invokes auto-scaling! Frontend(ctx, out).draw_layout(msp, finalize=False, filter_func=is_intersecting_render_area) # Set output size in inches: # width = 6 inch x 300 dpi = 1800 px # height = 3 inch x 300 dpi = 900 px fig.set_size_inches(6, 3, forward=True) filename = f"tile-{tile:02d}.png" print(f'saving tile #{tile} to "{filename}"') fig.savefig(DIR / filename, dpi=DPI) plt.close(fig)
def _main(): parser = argparse.ArgumentParser(description='draw the given CAD file and save it to a file or view it') parser.add_argument('cad_file', nargs='?') parser.add_argument('--supported_formats', action='store_true') parser.add_argument('--layout', default='Model') parser.add_argument('--out', required=False) parser.add_argument('--dpi', type=int, default=300) args = parser.parse_args() if args.supported_formats: fig = plt.figure() for extension, description in fig.canvas.get_supported_filetypes().items(): print(f'{extension}: {description}') sys.exit() if args.cad_file is None: print('no CAD file specified') sys.exit(1) doc = ezdxf.readfile(args.cad_file) try: layout = doc.layouts.get(args.layout) except KeyError: print(f'could not find layout "{args.layout}". Valid layouts: {[l.name for l in doc.layouts]}') sys.exit(1) fig: plt.Figure = plt.figure() ax: plt.Axes = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) out = MatplotlibBackend(ax) Frontend(ctx, out).draw_layout(layout, finalize=True) if args.out is not None: print(f'saving to "{args.out}"') fig.savefig(args.out, dpi=args.dpi) plt.close(fig) else: plt.show()
def run(args): # Import on demand for a quicker startup: try: import matplotlib.pyplot as plt except ImportError: print('Matplotlib package not found.') sys.exit(2) from ezdxf.addons.drawing import RenderContext, Frontend from ezdxf.addons.drawing.matplotlib import MatplotlibBackend # Print all supported export formats: if args.formats: fig = plt.figure() for extension, description in fig.canvas.get_supported_filetypes( ).items(): print(f'{extension}: {description}') sys.exit(0) if args.file: filename = args.file else: print('argument FILE is required') sys.exit(1) doc, _ = load_document(filename) fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) out = MatplotlibBackend(ax, params={'linetype_renderer': args.ltype}) Frontend(ctx, out).draw_layout(doc.modelspace(), finalize=True) if args.out is not None: print(f'exporting to "{args.out}"') fig.savefig(args.out, dpi=args.dpi) plt.close(fig) else: plt.show()
# Copyright (c) 2020, Manfred Moitzi # License: MIT License import ezdxf from pathlib import Path import matplotlib.pyplot as plt from ezdxf.addons.drawing import Frontend, RenderContext from ezdxf.addons.drawing.matplotlib_backend import MatplotlibBackend from ezdxf.math import global_bspline_interpolation wave = [(0.0, 0.0), (0.897597901, 0.78183148), (1.79519580, 0.97492791), (2.69279370, 0.433883739), (3.59039160, -0.43388373), (4.48798950, -0.97492791), (5.38558740, -0.78183148), (6.28318530, 0.0)] DIR = Path('~/Desktop/Outbox').expanduser() FILE = 'wave' doc = ezdxf.new() msp = doc.modelspace() s = global_bspline_interpolation(wave) msp.add_spline(dxfattribs={'color': 2}).apply_construction_tool(s) fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) backend = MatplotlibBackend(ax) Frontend(RenderContext(doc), backend).draw_layout(msp) fig.savefig(DIR / f'{FILE}.png', dpi=300)
class CadViewer(qw.QMainWindow): def __init__(self): super().__init__() self.doc = None self._render_context = None self._visible_layers = None self._current_layout = None self.view = CADGraphicsViewWithOverlay() self.view.setScene(qw.QGraphicsScene()) self.view.scale(1, -1) # so that +y is up self.view.element_selected.connect(self._on_element_selected) self.view.mouse_moved.connect(self._on_mouse_moved) menu = self.menuBar() select_doc_action = qw.QAction('Select Document', self) select_doc_action.triggered.connect(self._select_doc) menu.addAction(select_doc_action) self.select_layout_menu = menu.addMenu('Select Layout') toggle_sidebar_action = qw.QAction('Toggle Sidebar', self) toggle_sidebar_action.triggered.connect(self._toggle_sidebar) menu.addAction(toggle_sidebar_action) self.sidebar = qw.QSplitter(qc.Qt.Vertical) self.layers = qw.QListWidget() self.layers.setStyleSheet( 'QListWidget {font-size: 12pt;} QCheckBox {font-size: 12pt; padding-left: 5px;}' ) self.sidebar.addWidget(self.layers) info_container = qw.QWidget() info_layout = qw.QVBoxLayout() info_layout.setContentsMargins(0, 0, 0, 0) self.selected_info = qw.QPlainTextEdit() self.selected_info.setReadOnly(True) info_layout.addWidget(self.selected_info) self.mouse_pos = qw.QLabel() info_layout.addWidget(self.mouse_pos) info_container.setLayout(info_layout) self.sidebar.addWidget(info_container) container = qw.QSplitter() self.setCentralWidget(container) container.addWidget(self.view) container.addWidget(self.sidebar) container.setCollapsible(0, False) container.setCollapsible(1, True) w = container.width() container.setSizes([int(3 * w / 4), int(w / 4)]) self.setWindowTitle('CAD Viewer') self.resize(1600, 900) self.show() def _select_doc(self): path, _ = qw.QFileDialog.getOpenFileName( self, caption='Select CAD Document', filter='DXF Documents(*.dxf);;DWG Documents(*.dwg)') if path: try: if os.path.splitext(path)[1].lower() == '.dwg': doc = odafc.readfile(path) else: doc = ezdxf.readfile(path) self.set_document(doc) except IOError as e: qw.QMessageBox.critical(self, 'Loading Error', str(e)) except DXFStructureError as e: qw.QMessageBox.critical( self, 'DXF Structure Error', f'Invalid DXF file "{path}": {str(e)}') def set_document(self, document: Drawing): auditor = document.audit() error_count = len(auditor.errors) if error_count > 0: ret = qw.QMessageBox.question( self, 'Found DXF Errors', f'Found {error_count} errors in file "{document.filename}"\nLoad file anyway? ' ) if ret == qw.QMessageBox.No: auditor.print_error_report(auditor.errors) return self.doc = document self._render_context = RenderContext(document) self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout('Model') self.setWindowTitle('CAD Viewer - ' + str(document.filename)) def _populate_layer_list(self): self.layers.blockSignals(True) self.layers.clear() for layer in self._render_context.layers.values(): name = layer.layer item = qw.QListWidgetItem() self.layers.addItem(item) checkbox = qw.QCheckBox(name) checkbox.setCheckState( qc.Qt.Checked if layer.is_visible else qc.Qt.Unchecked) checkbox.stateChanged.connect(self._layers_updated) text_color = '#FFFFFF' if is_dark_color(layer.color, 0.4) else '#000000' checkbox.setStyleSheet( f'color: {text_color}; background-color: {layer.color}') self.layers.setItemWidget(item, checkbox) self.layers.blockSignals(False) def _populate_layouts(self): self.select_layout_menu.clear() for layout_name in self.doc.layout_names_in_taborder(): action = qw.QAction(layout_name, self) action.triggered.connect(partial(self.draw_layout, layout_name)) self.select_layout_menu.addAction(action) def draw_layout(self, layout_name: str, reset_view: bool = True): print(f'drawing {layout_name}') self._current_layout = layout_name self.view.begin_loading() new_scene = qw.QGraphicsScene() renderer = PyQtBackend(new_scene) layout = self.doc.layout(layout_name) self._update_render_context(layout) try: Frontend(self._render_context, renderer).draw_layout(layout) except DXFStructureError as e: qw.QMessageBox.critical( self, 'DXF Structure Error', f'Abort rendering of layout "{layout_name}": {str(e)}') finally: renderer.finalize() self.view.end_loading(new_scene) self.view.buffer_scene_rect() if reset_view: self.view.fit_to_scene() def _update_render_context(self, layout): assert self._render_context self._render_context.set_current_layout(layout) # Direct modification of RenderContext.layers would be more flexible, but would also expose the internals. if self._visible_layers is not None: self._render_context.set_layers_state(self._visible_layers, state=True) def resizeEvent(self, event: qg.QResizeEvent) -> None: self.view.fit_to_scene() def _layer_checkboxes(self) -> Iterable[Tuple[int, qw.QCheckBox]]: for i in range(self.layers.count()): item = self.layers.itemWidget(self.layers.item(i)) yield i, item @qc.pyqtSlot(int) def _layers_updated(self, item_state: qc.Qt.CheckState): shift_held = qw.QApplication.keyboardModifiers() & qc.Qt.ShiftModifier if shift_held: for i, item in self._layer_checkboxes(): item.blockSignals(True) item.setCheckState(item_state) item.blockSignals(False) self._visible_layers = set() for i, layer in self._layer_checkboxes(): if layer.checkState() == qc.Qt.Checked: self._visible_layers.add(layer.text()) self.draw_layout(self._current_layout, reset_view=False) @qc.pyqtSlot() def _toggle_sidebar(self): self.sidebar.setHidden(not self.sidebar.isHidden()) @qc.pyqtSlot(qc.QPointF) def _on_mouse_moved(self, mouse_pos: qc.QPointF): self.mouse_pos.setText( f'mouse position: {mouse_pos.x():.4f}, {mouse_pos.y():.4f}\n') @qc.pyqtSlot(object, int) def _on_element_selected(self, elements: List[qw.QGraphicsItem], index: int): 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.selected_info.setPlainText(text)
class CadViewer(qw.QMainWindow): def __init__(self): super().__init__() self.doc = None self._render_context = None self._visible_layers = None self._current_layout = None self.scene = qw.QGraphicsScene() self.view = CADGraphicsViewWithOverlay() self.view.setScene(self.scene) self.view.scale(1, -1) # so that +y is up self.view.element_selected.connect(self._on_element_selected) self.renderer = PyQtBackend(self.scene) menu = self.menuBar() select_doc_action = qw.QAction('Select Document', self) select_doc_action.triggered.connect(self._select_doc) menu.addAction(select_doc_action) self.select_layout_menu = menu.addMenu('Select Layout') toggle_sidebar_action = qw.QAction('Toggle Sidebar', self) toggle_sidebar_action.triggered.connect(self._toggle_sidebar) menu.addAction(toggle_sidebar_action) toggle_join_polylines_action = qw.QAction('Toggle Join Polylines', self) toggle_join_polylines_action.triggered.connect( self._toggle_join_polylines) menu.addAction(toggle_join_polylines_action) self.sidebar = qw.QSplitter(qc.Qt.Vertical) self.layers = qw.QListWidget() self.layers.setStyleSheet('font-size: 12pt') self.layers.itemChanged.connect(self._layers_updated) self.sidebar.addWidget(self.layers) self.info = qw.QPlainTextEdit() self.info.setReadOnly(True) self.sidebar.addWidget(self.info) container = qw.QSplitter() self.setCentralWidget(container) container.addWidget(self.view) container.addWidget(self.sidebar) container.setCollapsible(0, False) container.setCollapsible(1, True) w = container.width() container.setSizes([int(3 * w / 4), int(w / 4)]) self.setWindowTitle('CAD Viewer') self.resize(1600, 900) self.show() def _select_doc(self): path, _ = qw.QFileDialog.getOpenFileName( self, caption='Select CAD Document', filter='DXF Documents (*.dxf)') if path: self.set_document(ezdxf.readfile(path)) def set_document(self, document: Drawing): self.doc = document self._render_context = RenderContext(document) self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout('Model') def _populate_layer_list(self): self.layers.blockSignals(True) self.layers.clear() for layer in self._render_context.layers.values(): name = layer.layer item = qw.QListWidgetItem(name) item.setCheckState(qc.Qt.Checked) item.setBackground(qg.QColor(layer.color)) self.layers.addItem(item) self.layers.blockSignals(False) def _populate_layouts(self): self.select_layout_menu.clear() for layout_name in self.doc.layout_names_in_taborder(): action = qw.QAction(layout_name, self) action.triggered.connect(partial(self.draw_layout, layout_name)) self.select_layout_menu.addAction(action) def draw_layout(self, layout_name: str): print(f'drawing {layout_name}') self._current_layout = layout_name self.renderer.clear() self.view.clear() layout = self.doc.layout(layout_name) self._update_render_context(layout) Frontend(self._render_context, self.renderer).draw_layout(layout) self.view.fit_to_scene() def _update_render_context(self, layout): assert self._render_context self._render_context.set_current_layout(layout) # Direct modification of RenderContext.layers would be more flexible, but would also expose the internals. if self._visible_layers is not None: self._render_context.set_layers_state(self._visible_layers, state=True) def resizeEvent(self, event: qg.QResizeEvent) -> None: self.view.fit_to_scene() @qc.pyqtSlot(qw.QListWidgetItem) def _layers_updated(self, _item: qw.QListWidgetItem): self._visible_layers = set() for i in range(self.layers.count()): layer = self.layers.item(i) if layer.checkState() == qc.Qt.Checked: self._visible_layers.add(layer.text()) self.draw_layout(self._current_layout) @qc.pyqtSlot() def _toggle_sidebar(self): self.sidebar.setHidden(not self.sidebar.isHidden()) @qc.pyqtSlot() def _toggle_join_polylines(self): self.renderer.draw_individual_polyline_elements = not self.renderer.draw_individual_polyline_elements self.draw_layout(self._current_layout) @qc.pyqtSlot(object, qc.QPointF) def _on_element_selected(self, element: Optional[qw.QGraphicsItem], mouse_pos: qc.QPointF): text = f'mouse position: {mouse_pos.x():.4f}, {mouse_pos.y():.4f}\n' if element is None: text += 'No element selected' else: dxf_entity = element.data(CorrespondingDXFEntity) if dxf_entity is None: text += 'No data' else: text += f'Current Entity: {dxf_entity}\nLayer: {dxf_entity.dxf.layer}\n\nDXF Attributes:\n' for key, value in dxf_entity.dxf.all_existing_dxf_attribs( ).items(): text += f'- {key}: {value}\n' dxf_entity_stack = element.data(CorrespondingDXFEntityStack) if dxf_entity_stack: text += '\nParents:\n' for entity in reversed(dxf_entity_stack): text += f'- {entity}\n' self.info.setPlainText(text)
def run(args): # Import on demand for a quicker startup: try: import matplotlib.pyplot as plt except ImportError: print("Matplotlib package not found.") sys.exit(2) from ezdxf.addons.drawing import RenderContext, Frontend from ezdxf.addons.drawing.matplotlib import MatplotlibBackend from ezdxf.addons.drawing.config import Configuration, LinePolicy # Print all supported export formats: if args.formats: fig = plt.figure() for ( extension, description, ) in fig.canvas.get_supported_filetypes().items(): print(f"{extension}: {description}") sys.exit(0) if args.file: filename = args.file else: print("argument FILE is required") sys.exit(1) doc, _ = load_document(filename) try: layout = doc.layouts.get(args.layout) except KeyError: print( f'Could not find layout "{args.layout}". ' f"Valid layouts: {[l.name for l in doc.layouts]}" ) sys.exit(1) fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) out = MatplotlibBackend(ax) if args.all_layers_visible: for layer_properties in ctx.layers.values(): layer_properties.is_visible = True config = Configuration.defaults() config = config.with_changes( line_policy=LinePolicy.ACCURATE if args.ltype == "accurate" else config.line_policy ) if args.all_entities_visible: class AllVisibleFrontend(Frontend): def override_properties( self, entity: "DXFGraphic", properties: "Properties" ) -> None: properties.is_visible = True frontend = AllVisibleFrontend(ctx, out, config=config) else: frontend = Frontend(ctx, out, config=config) frontend.draw_layout(layout, finalize=True) if args.out is not None: print(f'exporting to "{args.out}"') fig.savefig(args.out, dpi=args.dpi) plt.close(fig) else: plt.show()
def effect(self): #get input file and copy it to some new temporary directory inputfile = self.options.inputfile if not os.path.exists(inputfile): inkex.utils.debug( "The input file does not exist. Please select a *.dxf or *.dwg file and try again." ) exit(1) temp_input_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_input") shutil.rmtree(temp_input_dir, ignore_errors=True ) #remove the input directory before doing new job if not os.path.exists(temp_input_dir): os.mkdir(temp_input_dir) #recreate blank dir shutil.copy2( inputfile, os.path.join( temp_input_dir, Path(inputfile).name)) # complete target filename given #Prepapre output conversion outputfilebase = os.path.splitext(os.path.basename(inputfile))[0] inputfile_ending = os.path.splitext(os.path.basename(inputfile))[1] temp_output_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_output") shutil.rmtree(temp_output_dir, ignore_errors=True ) #remove the output directory before doing new job if not os.path.exists(temp_output_dir): os.mkdir(temp_output_dir) #Prepare some more options for proceeding autocad_version = self.options.oda_outputformat.split("_")[0] autocad_format = self.options.oda_outputformat.split("_")[1] self.options.oda_audit_repair = "1" if self.options.oda_audit_repair else "0" #overwrite string bool with int value entityspace = [] if self.options.allentities or self.options.THREE_DFACE: entityspace.append("3DFACE") if self.options.allentities or self.options.ARC: entityspace.append("ARC") if self.options.allentities or self.options.BLOCK: entityspace.append("BLOCK") if self.options.allentities or self.options.CIRCLE: entityspace.append("CIRCLE") if self.options.allentities or self.options.ELLIPSE: entityspace.append("ELLIPSE") if self.options.allentities or self.options.LINE: entityspace.append("LINE") if self.options.allentities or self.options.LWPOLYLINE: entityspace.append("LWPOLYLINE") if self.options.allentities or self.options.POINT: entityspace.append("POINT") if self.options.allentities or self.options.POLYLINE: entityspace.append("POLYLINE") if self.options.allentities or self.options.POP_TRAFO: entityspace.append("POP_TRAFO") if self.options.allentities or self.options.SEQEND: entityspace.append("SEQEND") if self.options.allentities or self.options.SOLID: entityspace.append("SOLID") if self.options.allentities or self.options.SPLINE: entityspace.append("SPLINE") if self.options.allentities or self.options.TABLE: entityspace.append("TABLE") if self.options.allentities or self.options.VERTEX: entityspace.append("VERTEX") if self.options.allentities or self.options.VIEWPORT: entityspace.append("VIEWPORT") if self.options.allentities or self.options.THREE_DSOLID: entityspace.append("3DSOLID") if self.options.allentities or self.options.ATTRIB: entityspace.append("ATTRIB") if self.options.allentities or self.options.BODY: entityspace.append("BODY") if self.options.allentities or self.options.ARC_DIMENSION: entityspace.append("ARC_DIMENSION") if self.options.allentities or self.options.HATCH: entityspace.append("HATCH") if self.options.allentities or self.options.IMAGE: entityspace.append("IMAGE") if self.options.allentities or self.options.INSERT: entityspace.append("INSERT") if self.options.allentities or self.options.MESH: entityspace.append("MESH") if self.options.allentities or self.options.MTEXT: entityspace.append("MTEXT") if self.options.allentities or self.options.RAY: entityspace.append("RAY") if self.options.allentities or self.options.REGION: entityspace.append("REGION") if self.options.allentities or self.options.SHAPE: entityspace.append("SHAPE") if self.options.allentities or self.options.SURFACE: entityspace.append("SURFACE") if self.options.allentities or self.options.TRACE: entityspace.append("TRACE") if self.options.allentities or self.options.UNDERLAY: entityspace.append("UNDERLAY") if self.options.allentities or self.options.XLINE: entityspace.append("XLINE") #ODA to ezdxf mapping oda_ezdxf_mapping = [] oda_ezdxf_mapping.append( ["ACAD9", "R12", "AC1004"] ) #this mapping is not supported directly. so we use the lowest possible which is R12 oda_ezdxf_mapping.append( ["ACAD10", "R12", "AC1006"] ) #this mapping is not supported directly. so we use the lowest possible which is R12 oda_ezdxf_mapping.append(["ACAD12", "R12", "AC1009"]) oda_ezdxf_mapping.append( ["ACAD13", "R2000", "AC1012"] ) #R13 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation) oda_ezdxf_mapping.append( ["ACAD14", "R2000", "AC1014"] ) #R14 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation) oda_ezdxf_mapping.append(["ACAD2000", "R2000", "AC1015"]) oda_ezdxf_mapping.append(["ACAD2004", "R2004", "AC1018"]) oda_ezdxf_mapping.append(["ACAD2007", "R2007", "AC1021"]) oda_ezdxf_mapping.append(["ACAD2010", "R2010", "AC1024"]) oda_ezdxf_mapping.append(["ACAD2013", "R2013", "AC1027"]) oda_ezdxf_mapping.append(["ACAD2018", "R2018", "AC1032"]) ezdxf_autocad_format = None for oe in oda_ezdxf_mapping: if oe[0] == autocad_version: ezdxf_autocad_format = oe[1] break if ezdxf_autocad_format is None: inkex.errormsg("ezdxf conversion format version unknown") #Prepare DXF and SVG paths dxf_file = os.path.join(temp_output_dir, outputfilebase + ".dxf") svg_file = os.path.join(temp_output_dir, outputfilebase + ".svg") # Run ODA File Converter if self.options.oda_skip_dxf_to_dxf == False or inputfile_ending == ".dwg": # Executable test (check for proper configuration by checking mime type. Should return octet stream for a binary executable) if os.name == "nt" and "application/octet-stream" not in str( MimeTypes().guess_type( urllib.pathname2url(self.options.oda_fileconverter))): inkex.utils.debug( "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it." ) exit(1) elif os.path.isfile(self.options.oda_fileconverter) == False: inkex.utils.debug( "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it." ) exit(1) else: # Build and run ODA File Converter command oda_cmd = [ self.options.oda_fileconverter, temp_input_dir, temp_output_dir, autocad_version, autocad_format, "0", self.options.oda_audit_repair ] if os.name == 'nt' and self.options.oda_hidewindow: info = subprocess.STARTUPINFO( ) #hide the ODA File Converter window because it is annoying (does not work for Linux :-() info.dwFlags = 1 info.wShowWindow = 0 proc = subprocess.Popen(oda_cmd, startupinfo=info, shell=False, stdout=PIPE, stderr=PIPE) else: proc = subprocess.Popen(oda_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0 or (len(stderr) > 0 and stderr != b"Quit (core dumped)\n"): inkex.utils.debug("ODAFileConverter failed: %d %s %s" % (proc.returncode, stdout, stderr)) if os.name != 'nt': inkex.utils.debug( "If the error message above contains a warning about wrong/missing Qt version please install the required version. You can get the installer from 'https://download.qt.io/archive/qt/'. Sadly you will need to create a free account to install. After installation please configure the shell script '/usr/bin/ODAFileConverter' to add a preceding line with content similar to 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/Qt5.14.2/5.14.2/gcc_64/lib/'." ) exit(1) # check if ODA converted successfully. This is the case if no error file was created oda_errorfile = os.path.join(temp_output_dir, Path(inputfile).name + ".err") if os.path.exists(oda_errorfile): inkex.utils.debug( "ODA File Converter failed to process the file. Cannot continue DXF/DWG import. The error message is:" ) errormessage = open(oda_errorfile, 'r') errorlines = errormessage.readlines() for errorline in errorlines: inkex.utils.debug(errorline.strip()) errormessage.close() exit(1) # Do some movings/copies of skipped or processed DXF if self.options.oda_skip_dxf_to_dxf: #if true we need to move the file to simulate "processed" shutil.move(os.path.join(temp_input_dir, Path(inputfile).name), os.path.join(temp_output_dir, Path(inputfile).name)) if self.options.oda_keepconverted_dxf: shutil.copy2( dxf_file, os.path.join(os.path.dirname(inputfile), outputfilebase + "_oda.dxf")) # complete target filename given # Preprocessing DXF to DXF (entity filter) by using ezdxf the first time if self.options.ezdxf_preprocessing: # uniconverter does not handle all entities. we parse the file to exlude stuff which lets uniconverter fail dxf = ezdxf.readfile(dxf_file) modelspace = dxf.modelspace() allowed_entities = [] # supported entities by UniConverter- impossible: MTEXT TEXT INSERT and a lot of others query_string = str(entityspace)[1:-1].replace("'", "").replace(",", "") if query_string != "": for e in modelspace.query(query_string): allowed_entities.append(e) #inkex.utils.debug(ezdxf_autocad_format) #inkex.utils.debug(self.options.ezdxf_output_version) if self.options.ezdxf_output_version == "SAME": doc = ezdxf.new(ezdxf_autocad_format) else: doc = ezdxf.new( self.options.ezdxf_output_version ) #use the string values from inx file. Required to match the values from ezdxf library. See Python reference msp = doc.modelspace() for e in allowed_entities: msp.add_foreign_entity(e) doc.saveas(dxf_file) if self.options.ezdfx_keep_preprocessed: shutil.copy2( dxf_file, os.path.join( os.path.dirname(inputfile), outputfilebase + "_ezdxf.dxf")) # complete target filename given # Make SVG from DXF if self.options.dxf_to_svg_parser == "sk1": if os.name != "nt": inkex.utils.debug( "You selected sk1 UniConvertor but you are not running on a Windows platform. On Linux uniconverter 1.1.X can be installed using the now obsolete Python 2.7, but it will not run correctly because you finally will fail at installing liblcms1-dev library on newer systems. That leads to uncompilable sk1libs package. Unfortunately sk1 UniConvertor 2.X does not support dxf format. So please use another DXF to SVG converter." ) exit(1) sk1_command_ending = os.path.splitext( os.path.splitext( os.path.basename(self.options.sk1_uniconverter))[1])[0] if sk1_command_ending != ".cmd": inkex.utils.debug( "You selected sk1 UniConverter but it was not configured properly. Check the path to the executable." ) exit(1) uniconverter_cmd = [ self.options.sk1_uniconverter, dxf_file, svg_file ] #inkex.utils.debug(uniconverter_cmd) proc = subprocess.Popen(uniconverter_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg("UniConverter failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "bjnortier": if which("node") is None: inkex.utils.debug( "NodeJS executable not found on path. Please check your installation." ) exit(1) else: bjnortier_cmd = [ "node", os.path.join("node_modules", "dxf", "lib", "cli.js"), dxf_file, svg_file ] #inkex.utils.debug(bjnortier_cmd) proc = subprocess.Popen(bjnortier_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg( "node.js DXF to SVG conversion failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "kabeja": wd = os.path.join(os.getcwd(), "kabeja") #inkex.utils.debug(wd) proc = subprocess.Popen( "java -jar launcher.jar -nogui -pipeline svg " + dxf_file + " " + svg_file, cwd=wd, shell=True, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg("kabeja failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "vpype_dxf": try: from inkex.command import inkscape import vpype from vpype_cli import execute except Exception as e: inkex.errormsg( "Error importing vpype. Did you properly install the vpype and vpype-dxf python modules?" ) exit(1) doc = vpype.Document() #create new vpype document command = "dread --quantization " + str( self.options.vpype_quantization) if self.options.vpype_simplify is True: command += " --simplify" if self.options.vpype_parallel is True: command += " --parallel" #command += " '" + inputfile + "'" command += " '" + dxf_file + "'" #inkex.errormsg(command) doc = execute(command, doc) if doc.length() == 0: inkex.errormsg( 'No lines left after vpype conversion. Conversion result is empty. Cannot continue' ) exit(1) # save the vpype document to new svg file and close it afterwards output_fileIO = open(svg_file, "w", encoding="utf-8") vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=False, color_mode='layer') output_fileIO.close() # convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb." cli_output = inkscape( svg_file, "--with-gui", actions= "EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit" ) if len(cli_output) > 0: self.debug( _("Inkscape returned the following output when trying to run the vpype object to path back-conversion:" )) self.debug(cli_output) elif self.options.dxf_to_svg_parser == "ezdxf": try: doc = ezdxf.readfile(dxf_file) msp = doc.modelspace() #for e in msp: #loop through entities # inkex.errormsg(e) #doc.header['$DIMSCALE'] = 0.2 does not apply to the plot :-( #inkex.utils.debug(doc.header['$DIMSCALE']) #inkex.utils.debug(doc.header['$MEASUREMENT']) auditor = doc.audit( ) #audit & repair DXF document before rendering # The auditor.errors attribute stores severe errors, which *may* raise exceptions when rendering. if len(auditor.errors) == 0: fig = plt.figure() ax = plt.axes([0., 0., 1., 1.], xticks=[], yticks=[]) #ax = plt.axes([0., 0., 1., 1.], frameon=False, xticks=[], yticks=[]) ax.patches = [] #plt.axis('off') plt.margins(0, 0) plt.gca().xaxis.set_major_locator(plt.NullLocator()) plt.gca().yaxis.set_major_locator(plt.NullLocator()) plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) out = MatplotlibBackend(fig.add_axes(ax)) Frontend(RenderContext(doc), out).draw_layout(msp, finalize=True) #plt.show() #fig.savefig(os.path.join(temp_output_dir, outputfilebase + ".png"), dpi=300) fig.savefig( svg_file ) #see https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.savefig.html except IOError: inkex.errormsg("Not a DXF file or a generic I/O error.") exit(1) except ezdxf.DXFStructureError: inkex.errormsg("Invalid or corrupted DXF file.") exit(1) elif self.options.dxf_to_svg_parser == "legacy": inkex.utils.debug( "The selected legacy DXF to SVG parser is not supported by this extension yet. Use File > Import > *.dxf. This calls the \"dxf_input.inx\" extension." ) exit(1) else: inkex.utils.debug("undefined parser") exit(1) # Write the generated SVG into InkScape's canvas try: stream = open(svg_file, 'r') except FileNotFoundError as e: inkex.utils.debug( "There was no SVG output generated. Cannot continue") exit(1) p = etree.XMLParser(huge_tree=True) doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)).getroot() stream.close() doc.set( 'id', self.svg.get_unique_id("dxf_dwg_import-" + self.options.dxf_to_svg_parser + "-")) self.document.getroot().append(doc) #get children of the doc and move them one group above - we don't do this for bjnortier tool because this has different structure which we don't want to disturb if self.options.dxf_to_svg_parser == "sk1": elements = [] emptyGroup = None for firstGroup in doc.getchildren(): emptyGroup = firstGroup for element in firstGroup.getchildren(): elements.append(element) #break #only one cycle - could be bad idea or not for element in elements: doc.set('id', self.svg.get_unique_id('dxf_dwg_import')) doc.insert(doc.index(firstGroup), element) if emptyGroup is not None: emptyGroup.getparent().remove(emptyGroup) #empty the following vals because they destroy the size aspects of the import / make viewbox looking wrong if self.options.dxf_to_svg_parser == "bjnortier" or self.options.dxf_to_svg_parser == "kabeja": doc.set('width', '') doc.set('height', '') doc.set('viewBox', '') doc.getchildren()[0].set('transform', '') #adjust viewport and width/height to have the import at the center of the canvas if self.options.resizetoimport: bbox = inkex.elements._selected.ElementList.bounding_box( doc.getchildren()[0]) if bbox is not None: root = self.svg.getElement('//svg:svg') offset = self.svg.unittouu( str(self.options.extraborder) + self.options.extraborder_units) root.set( 'viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset)) root.set('width', bbox.width + 2 * offset) root.set('height', bbox.height + 2 * offset)
class CadViewer(qw.QMainWindow): def __init__(self, config: Configuration = Configuration.defaults()): super().__init__() self._config = config self.doc = None self._render_context = None self._visible_layers = None self._current_layout = None self._reset_backend() self.view = CADGraphicsViewWithOverlay() self.view.setScene(qw.QGraphicsScene()) self.view.scale(1, -1) # so that +y is up self.view.element_selected.connect(self._on_element_selected) self.view.mouse_moved.connect(self._on_mouse_moved) menu = self.menuBar() select_doc_action = QAction("Select Document", self) select_doc_action.triggered.connect(self._select_doc) menu.addAction(select_doc_action) self.select_layout_menu = menu.addMenu("Select Layout") toggle_sidebar_action = QAction("Toggle Sidebar", self) toggle_sidebar_action.triggered.connect(self._toggle_sidebar) menu.addAction(toggle_sidebar_action) self.sidebar = qw.QSplitter(qc.Qt.Vertical) self.layers = qw.QListWidget() self.layers.setStyleSheet( "QListWidget {font-size: 12pt;} " "QCheckBox {font-size: 12pt; padding-left: 5px;}") self.sidebar.addWidget(self.layers) info_container = qw.QWidget() info_layout = qw.QVBoxLayout() info_layout.setContentsMargins(0, 0, 0, 0) self.selected_info = qw.QPlainTextEdit() self.selected_info.setReadOnly(True) info_layout.addWidget(self.selected_info) self.mouse_pos = qw.QLabel() info_layout.addWidget(self.mouse_pos) info_container.setLayout(info_layout) self.sidebar.addWidget(info_container) container = qw.QSplitter() self.setCentralWidget(container) container.addWidget(self.view) container.addWidget(self.sidebar) container.setCollapsible(0, False) container.setCollapsible(1, True) w = container.width() container.setSizes([int(3 * w / 4), int(w / 4)]) self.setWindowTitle("CAD Viewer") self.resize(1600, 900) self.show() def _reset_backend(self, scale: float = 1.0): backend = PyQtBackend(use_text_cache=True) if scale != 1.0: backend = BackendScaler(backend, factor=scale) # clear caches self._backend = backend def _select_doc(self): path, _ = qw.QFileDialog.getOpenFileName( self, caption="Select CAD Document", filter="CAD Documents (*.dxf *.DXF *.dwg *.DWG)", ) if path: try: if os.path.splitext(path)[1].lower() == ".dwg": doc = odafc.readfile(path) auditor = doc.audit() else: try: doc = ezdxf.readfile(path) except ezdxf.DXFError: doc, auditor = recover.readfile(path) else: auditor = doc.audit() self.set_document(doc, auditor) except IOError as e: qw.QMessageBox.critical(self, "Loading Error", str(e)) except DXFStructureError as e: qw.QMessageBox.critical( self, "DXF Structure Error", f'Invalid DXF file "{path}": {str(e)}', ) def set_document( self, document: Drawing, auditor: Auditor, *, layout: str = "Model", overall_scaling_factor: float = 1.0, ): error_count = len(auditor.errors) if error_count > 0: ret = qw.QMessageBox.question( self, "Found DXF Errors", f'Found {error_count} errors in file "{document.filename}"\n' f"Load file anyway? ", ) if ret == qw.QMessageBox.No: auditor.print_error_report(auditor.errors) return self.doc = document self._render_context = RenderContext(document) self._reset_backend(scale=overall_scaling_factor) self._visible_layers = None self._current_layout = None self._populate_layouts() self._populate_layer_list() self.draw_layout(layout) self.setWindowTitle("CAD Viewer - " + str(document.filename)) def _populate_layer_list(self): self.layers.blockSignals(True) self.layers.clear() for layer in self._render_context.layers.values(): name = layer.layer item = qw.QListWidgetItem() self.layers.addItem(item) checkbox = qw.QCheckBox(name) checkbox.setCheckState( qc.Qt.Checked if layer.is_visible else qc.Qt.Unchecked) checkbox.stateChanged.connect(self._layers_updated) text_color = ("#FFFFFF" if is_dark_color(layer.color, 0.4) else "#000000") checkbox.setStyleSheet( f"color: {text_color}; background-color: {layer.color}") self.layers.setItemWidget(item, checkbox) self.layers.blockSignals(False) def _populate_layouts(self): self.select_layout_menu.clear() for layout_name in self.doc.layout_names_in_taborder(): action = QAction(layout_name, self) action.triggered.connect( lambda: self.draw_layout(layout_name, reset_view=True)) self.select_layout_menu.addAction(action) def draw_layout( self, layout_name: str, reset_view: bool = True, ): print(f"drawing {layout_name}") self._current_layout = layout_name self.view.begin_loading() new_scene = qw.QGraphicsScene() self._backend.set_scene(new_scene) layout = self.doc.layout(layout_name) self._update_render_context(layout) try: start = time.perf_counter() self.create_frontend().draw_layout(layout) duration = time.perf_counter() - start print(f"took {duration:.4f} seconds") except DXFStructureError as e: qw.QMessageBox.critical( self, "DXF Structure Error", f'Abort rendering of layout "{layout_name}": {str(e)}', ) finally: self._backend.finalize() self.view.end_loading(new_scene) self.view.buffer_scene_rect() if reset_view: self.view.fit_to_scene() def create_frontend(self): return Frontend( self._render_context, self._backend, self._config, ) def _update_render_context(self, layout): assert self._render_context self._render_context.set_current_layout(layout) # Direct modification of RenderContext.layers would be more flexible, # but would also expose the internals. if self._visible_layers is not None: self._render_context.set_layers_state(self._visible_layers, state=True) def resizeEvent(self, event: qg.QResizeEvent) -> None: self.view.fit_to_scene() def _layer_checkboxes(self) -> Iterable[Tuple[int, qw.QCheckBox]]: for i in range(self.layers.count()): item = self.layers.itemWidget(self.layers.item(i)) yield i, item # type: ignore @Slot(int) # type: ignore def _layers_updated(self, item_state: qc.Qt.CheckState): shift_held = qw.QApplication.keyboardModifiers() & qc.Qt.ShiftModifier if shift_held: for i, item in self._layer_checkboxes(): item.blockSignals(True) item.setCheckState(item_state) item.blockSignals(False) self._visible_layers = set() for i, layer in self._layer_checkboxes(): if layer.checkState() == qc.Qt.Checked: self._visible_layers.add(layer.text()) self.draw_layout(self._current_layout, reset_view=False) @Slot() def _toggle_sidebar(self): self.sidebar.setHidden(not self.sidebar.isHidden()) @Slot(qc.QPointF) def _on_mouse_moved(self, mouse_pos: qc.QPointF): self.mouse_pos.setText( f"mouse position: {mouse_pos.x():.4f}, {mouse_pos.y():.4f}\n") @Slot(object, int) def _on_element_selected(self, elements: List[qw.QGraphicsItem], index: int): 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}\n" f"Layer: {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.selected_info.setPlainText(text)
def _main(): parser = argparse.ArgumentParser( description="draw the given CAD file and save it to a file or view it" ) parser.add_argument("cad_file", nargs="?") parser.add_argument("--supported_formats", action="store_true") parser.add_argument("--layout", default="Model") parser.add_argument("--out", required=False) parser.add_argument("--dpi", type=int, default=300) parser.add_argument("--ltype", default="internal") args = parser.parse_args() if args.supported_formats: fig = plt.figure() for ( extension, description, ) in fig.canvas.get_supported_filetypes().items(): print(f"{extension}: {description}") sys.exit() if args.cad_file is None: print("no CAD file specified") sys.exit(1) try: doc = ezdxf.readfile(args.cad_file) except IOError: print(f"Not a DXF file or a generic I/O error.") sys.exit(2) except ezdxf.DXFError: try: doc, auditor = recover.readfile(args.cad_file) except ezdxf.DXFStructureError: print(f"Invalid or corrupted DXF file: {args.cad_file}") sys.exit(3) else: auditor = doc.audit() if auditor.has_errors: # But is most likely good enough for rendering. print(f"Found {len(auditor.errors)} unrecoverable errors.") if auditor.has_fixes: print(f"Fixed {len(auditor.fixes)} errors.") try: layout = doc.layouts.get(args.layout) except KeyError: print( f'Could not find layout "{args.layout}". ' f"Valid layouts: {[l.name for l in doc.layouts]}" ) sys.exit(4) # setup drawing add-on configuration config = Configuration.defaults() config = config.with_changes( line_policy=LinePolicy.ACCURATE if args.ltype == "ezdxf" else config.line_policy ) fig: plt.Figure = plt.figure(dpi=args.dpi) ax: plt.Axes = fig.add_axes([0, 0, 1, 1]) ctx = RenderContext(doc) out = MatplotlibBackend(ax) Frontend(ctx, out, config=config).draw_layout(layout, finalize=True) if args.out is not None: print(f'saving to "{args.out}"') fig.savefig(args.out, dpi=args.dpi) plt.close(fig) else: plt.show()
def ctx(doc): return RenderContext(doc)
class MainWindow(qw.QMainWindow, Ui_MainWindow): def __init__(self, controller): super().__init__() self.controller = controller self.setupUi(self) self.scene = qw.QGraphicsScene() items = self.scene.items() for item in items: item.transformations() self.view = self.graphicsView self.view.setScene(self.scene) self.view.scale(1, -1) # so that +y is up self.view.element_selected.connect(self._on_element_selected) self.actionImport_DXF.triggered.connect(self.import_dxf) self.actionNew_Origin.triggered.connect(self.show_origin_dialog) self.actionNew_Setup.triggered.connect(self.show_new_setup_dialog) self.new_face.triggered.connect(self.show_new_face_feature_dialog) self.new_drill.triggered.connect(self.show_new_drill_feature_dialog) self.new_slot.triggered.connect(self.show_new_slot_feature_dialog) self.treeView.setContextMenuPolicy(qc.Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect( self.show_r_click_tree_menu) self.position_widget = PositionWidget() self.statusbar.addWidget(self.position_widget) self.position_widget.change_current_origin.connect( self.change_current_origin) self.position_widget.change_active_setup.connect( self.change_active_setup) self.save_nc_button.clicked.connect(self.save_nc_file) self.actionPost.triggered.connect(self.post_file) self.actionSet_DB.triggered.connect( self.controller.show_connect_dialog) self.actionGeometry_Palette.triggered.connect( self.show_geometry_palette) self.geometry_palette_dialog = None self.origin_dialog = None self.setup_dialog = None self.feature_dialog = None self.tree_r_click_menu = None self.picker_axis = None self.all_dxf_entities = {} self.selected_dxf_entities = [] self.model_space = None self.right_click_point = None self.picked_item_vector = None self.scene_origin = None self.image = qg.QPixmap('img/splash.png') self.editor_setup = None self.editor_origin = None self.editor_feature = None self.renderer = PyQtBackend(self.scene) self.doc = None self._render_context = None self._visible_layers = None self._current_layout = None self.scene_shift = (0, 0, 0) self.hidden_dialogs = [] def post_file(self): self.get_all_dxf_entities() self.controller.update_machine() self.controller.populate_operation_list() if hasattr(self.controller.current_camo_file, 'operations'): if len(self.controller.current_camo_file.operations) > 0: code_builder = CodeBuilder(self.controller) self.controller.nc_output = code_builder.post() self.nc_output_edit.setText(self.controller.nc_output) self.tabWidget.setCurrentWidget(self.code_tab) else: show_error_message( 'No features', 'please have active setup with features to post NC code') def save_nc_file(self): default_filename = self.controller.current_camo_file.filename if default_filename: default_filename = default_filename.replace('.camo', '.NC') filename, _ = qw.QFileDialog.getSaveFileName(self, 'Save .NC File', default_filename, filter='*.NC') if filename: with open(filename, 'w') as file: file.writelines(self.nc_output_edit.toPlainText()) def get_all_dxf_entities(self): msp = self.controller.current_camo_file.dxf_doc.modelspace() self.all_dxf_entities = {} for entity in msp: self.all_dxf_entities[str(entity)] = entity def show_geometry_palette(self): self.geometry_palette_dialog = GeoPaletteDialog() self.geometry_palette_dialog.show() def show_r_click_tree_menu(self, point): self.right_click_point = point self.tree_r_click_menu = qw.QMenu() edit_tree_item_action = qw.QAction('Edit', self) edit_tree_item_action.triggered.connect(self.edit_tree_item) delete_tree_item_action = qw.QAction('Delete', self) delete_tree_item_action.triggered.connect(self.delete_tree_item) self.tree_r_click_menu.addAction(edit_tree_item_action) self.tree_r_click_menu.addAction(delete_tree_item_action) self.tree_r_click_menu.exec(self.treeView.mapToGlobal(point)) print('rclick') def get_right_clicked_tree_item(self): index = self.treeView.indexAt(self.right_click_point) node = index.internalPointer() return index, node def edit_tree_item(self, ): _, node = self.get_right_clicked_tree_item() if node.type == ct.OSetup: self.show_edit_setup_dialog(node._data[1]) elif node.type == ct.OOrigin: self.show_edit_origin_dialog(node._data[1]) elif node.type == ct.OFeature: self.show_edit_feature_dialog(node._data[1]) print(node) def delete_tree_item(self): selected_node = self.treeView.indexAt(self.right_click_point) item = selected_node.internalPointer() print(item) item = item._data[1] if isinstance(item, Setup): self.controller.current_camo_file.setups.remove(item) elif isinstance(item, Origin): self.controller.current_camo_file.origins.remove(item) elif isinstance(item, PartFeature): self.controller.current_camo_file.features.remove(item) elif hasattr(item, 'DXFTYPE'): self.controller.current_camo_file.dxf_doc.modelspace( ).delete_entity(item) else: return self.set_document(self.controller.current_camo_file.dxf_doc) self.controller.build_file_tree_model() def populate_origin_comboBox(self): self.position_widget.origin_combo.clear() for origin in self.controller.current_camo_file.origins: self.position_widget.origin_combo.addItem(origin.name, origin) def populate_active_setup_combo(self): self.position_widget.active_setup_combo.clear() for setup in self.controller.current_camo_file.setups: self.position_widget.active_setup_combo.addItem(setup.name, setup) def change_current_origin(self, origin): if origin: self.controller.current_camo_file.active_origin = origin self.apply_origin_to_scene() def apply_origin_to_scene(self): self.reset_scene_shift() x = self.controller.current_camo_file.active_origin.x y = self.controller.current_camo_file.active_origin.y a = self.controller.current_camo_file.active_origin.angle self.scene_shift = (x, y, a) self.graphicsView.rotate(a) self.scene_origin.setX(x) self.scene_origin.setY(y) self.scene_origin.setRotation(-a) def reset_scene_shift(self): x, y, a = self.scene_shift self.graphicsView.rotate(360 - a) self.scene_origin.setX(-x) self.scene_origin.setY(-y) self.scene_origin.setRotation(a) self.scene_shift = (0, 0, 0) def change_active_setup(self, setup): if setup: self.controller.current_camo_file.active_setup = setup self.controller.populate_operation_list() def show_origin_dialog(self): self.origin_dialog = OriginDialog() self.origin_dialog.find_x.clicked.connect(self.find_x) self.origin_dialog.find_y.clicked.connect(self.find_y) self.origin_dialog.buttonBox.accepted.connect(self.add_new_origin) self.origin_dialog.show() def find_x(self): self.origin_dialog.hide() self.picker_axis = 'x' self.graphicsView.graphics_view_clicked.connect( self.dxf_entity_clicked_origin) def find_y(self): self.origin_dialog.hide() self.picker_axis = 'y' self.graphicsView.graphics_view_clicked.connect( self.dxf_entity_clicked_origin) def dxf_entity_clicked_origin(self, item): item_data = item.data(0) picked_item_vector = None if item_data.DXFTYPE == 'LINE': picked_item_vector = item_data.dxf.start elif item_data.DXFTYPE == 'CIRCLE': picked_item_vector = item_data.dxf.center elif item_data.DXFTYPE == 'ARC': picked_item_vector = item_data.dxf.center if picked_item_vector: if self.picker_axis == 'x': self.origin_dialog.origin_x.setText( str(round(picked_item_vector.x, 4))) elif self.picker_axis == 'y': self.origin_dialog.origin_y.setText( str(round(picked_item_vector.y, 4))) self.origin_dialog.showNormal() self.graphicsView.graphics_view_clicked.disconnect( self.dxf_entity_clicked_origin) def show_edit_origin_dialog(self, origin): self.show_origin_dialog() self.editor_origin = origin self.origin_dialog.buttonBox.accepted.disconnect(self.add_new_origin) self.origin_dialog.buttonBox.accepted.connect(self.edit_origin) self.origin_dialog.origin_name_input.setText(origin.name) self.origin_dialog.origin_x.setText(str(origin.x)) self.origin_dialog.origin_y.setText(str(origin.y)) self.origin_dialog.angle_input.setText(str(origin.angle)) self.origin_dialog.wfo_spinBox.setValue(origin.wfo_num) def add_new_origin(self): name, wfo, x, y, angle = self.clean_origin_values_from_dialog() origin = Origin(name, wfo, x, y, angle) self.controller.current_camo_file.origins.append(origin) self.controller.build_file_tree_model() self.populate_origin_comboBox() def edit_origin(self): name, wfo, x, y, angle = self.clean_origin_values_from_dialog() self.editor_origin.name = name self.editor_origin.x = x self.editor_origin.y = y self.editor_origin.angle = angle self.editor_origin.wfo_num = wfo self.editor_origin.angle = angle self.controller.build_file_tree_model() def clean_origin_values_from_dialog(self): name = self.origin_dialog.origin_name_input.text() x = self.origin_dialog.origin_x.text() y = self.origin_dialog.origin_y.text() angle = self.origin_dialog.angle_input.text() wfo = self.origin_dialog.wfo_spinBox.value() if x == '': x = 0 if y == '': y = 0 if angle == '': angle = 0 return name, wfo, float(x), float(y), float(angle) def show_new_setup_dialog(self): if len(self.controller.current_camo_file.origins) < 1: show_error_message('Error', 'Please add origin') return self.setup_dialog = SetupDialog(self.controller) self.setup_dialog.buttonBox.accepted.connect(self.add_new_setup) self.setup_dialog.show() def show_edit_feature_dialog(self, feature): self.feature_dialog = FeatureDialog(self.controller) self.editor_feature = feature base_feature = self.controller.session.query(Feature).filter( Feature.id == self.editor_feature.base_feature_id).one() print(base_feature) def show_edit_setup_dialog(self, setup): self.setup_dialog = SetupDialog(self.controller) self.editor_setup = setup self.setup_dialog.buttonBox.accepted.connect(self.edit_setup) self.setup_dialog.machine_combo.setCurrentIndex( self.setup_dialog.machine_combo.findText( setup.get_machine(self.controller.session).name)) self.setup_dialog.origin_combo.setCurrentIndex( self.setup_dialog.origin_combo.findText(setup.origin.name)) self.setup_dialog.clearance_spinbox.setValue(setup.clearance_plane) self.setup_dialog.setup_name_input.setText(setup.name) self.setup_dialog.program_number_spinbox.setValue(setup.program_number) # setup.qb_setup_id = '' self.setup_dialog.setup_id_edit.setText(setup.qb_setup_id) self.setup_dialog.show() def add_new_setup(self): name = self.setup_dialog.setup_name_input.text() machine = get_combo_data(self.setup_dialog.machine_combo) origin = get_combo_data(self.setup_dialog.origin_combo) clearance = self.setup_dialog.clearance_spinbox.value() program_number = self.setup_dialog.program_number_spinbox.value() setup_id = self.setup_dialog.setup_id_edit.text() setup = Setup(name, machine.id, origin, clearance, program_number, setup_id) self.controller.current_camo_file.setups.append(setup) self.controller.build_file_tree_model() self.populate_active_setup_combo() def edit_setup(self): self.editor_setup.name = self.setup_dialog.setup_name_input.text() self.editor_setup.machine = get_combo_data( self.setup_dialog.machine_combo) self.editor_setup.machine_id = self.editor_setup.machine.id self.editor_setup.origin = self.setup_dialog.origin_combo.itemData( self.setup_dialog.origin_combo.currentIndex()) self.editor_setup.clearance_plane = self.setup_dialog.clearance_spinbox.value( ) self.editor_setup.program_number = self.setup_dialog.program_number_spinbox.value( ) self.editor_setup.qb_setup_id = self.setup_dialog.setup_id_edit.text() self.controller.build_file_tree_model() def show_feature_dialog(self): if len(self.controller.current_camo_file.setups) < 1: show_error_message('Error', 'Please add setup') return self.feature_dialog = FeatureDialog(self.controller) for setup in self.controller.current_camo_file.setups: self.feature_dialog.setup_combo.addItem(setup.name, setup) self.feature_dialog.buttonBox.rejected.connect( self.feature_dialog.reject) self.feature_dialog.base_feature_combo.currentIndexChanged.connect( self.populate_depth_inputs) self.feature_dialog.show() def populate_filtered_base_feature_list(self, features): for feature in features: self.feature_dialog.base_feature_combo.addItem( feature.name, feature) def show_face_feature_dialog(self): self.show_feature_dialog() if self.feature_dialog: filtered_list = [ feature for feature in self.controller.feature_list.features if feature.feature_type.feature_type == 'Facing' ] self.populate_filtered_base_feature_list(filtered_list) face_widget = FaceWidget() self.feature_dialog.frame_layout.addWidget(face_widget) def show_drill_feature_dialog(self): self.show_feature_dialog() if self.feature_dialog: filtered_list = [ feature for feature in self.controller.feature_list.features if feature.feature_type.feature_type == 'Drilling' ] self.populate_filtered_base_feature_list(filtered_list) self.feature_dialog.drill_widget = DrillWidget() self.feature_dialog.frame_layout.addWidget( self.feature_dialog.drill_widget) self.feature_dialog.drill_widget.select_sample_circle.clicked.connect( self.find_circle_diameter) self.feature_dialog.drill_widget.find_cirlces_button.clicked.connect( self.find_circles) def show_slot_feature_dialog(self): filtered_list = [ feature for feature in self.controller.feature_list.features if feature.feature_type.feature_type == 'Slotting' ] if len(filtered_list) < 1: show_error_message('Error', 'Please add Slot to database Features') return self.show_feature_dialog() if self.feature_dialog: self.populate_filtered_base_feature_list(filtered_list) self.feature_dialog.slot_widget = SlotWidget() self.feature_dialog.frame_layout.addWidget( self.feature_dialog.slot_widget) self.feature_dialog.slot_widget.add_line_button.clicked.connect( self.add_slot_line) def show_new_face_feature_dialog(self): self.show_face_feature_dialog() self.feature_dialog.buttonBox.accepted.connect(self.add_face_feature) def show_new_drill_feature_dialog(self): self.show_drill_feature_dialog() if self.feature_dialog: self.feature_dialog.buttonBox.accepted.connect( self.add_drill_feature) def show_new_slot_feature_dialog(self): self.show_slot_feature_dialog() if self.feature_dialog: self.feature_dialog.selected_slot_lines = [] self.feature_dialog.buttonBox.accepted.connect( self.add_slot_feature) def populate_depth_inputs(self, index): feature = get_combo_data_index(self.feature_dialog.base_feature_combo, index) # for i in reversed(range(self.feature_dialog.depth_groupBox.layout().count())): # self.feature_dialog.depth_groupBox.layout().itemAt(i).widget().setParent(None) clearLayout(self.feature_dialog.depth_groupBox.layout()) for op in feature.operations: print(str(op.camo_op.op_type)) depth_widget = DepthWidget(op.camo_op.op_type) self.feature_dialog.depth_groupBox.layout().addWidget(depth_widget) def add_face_feature(self): base_feature = get_combo_data(self.feature_dialog.base_feature_combo) print(base_feature) setup = get_combo_data(self.feature_dialog.setup_combo) print(setup) depths = get_depths_from_layout( self.feature_dialog.depth_groupBox.layout()) feature = PartFeature(base_feature.id, setup, depths=depths) self.controller.current_camo_file.features.append(feature) self.controller.build_file_tree_model() def add_slot_feature(self): base_feature = get_combo_data(self.feature_dialog.base_feature_combo) setup = get_combo_data(self.feature_dialog.setup_combo) geo = self.feature_dialog.selected_slot_lines if len(geo) > 0: depths = get_depths_from_layout( self.feature_dialog.depth_groupBox.layout()) feature = PartFeature(base_feature.id, setup, geometry=geo, depths=depths) self.controller.current_camo_file.features.append(feature) self.controller.build_file_tree_model() def add_drill_feature(self): base_feature = get_combo_data(self.feature_dialog.base_feature_combo) setup = get_combo_data(self.feature_dialog.setup_combo) geo = self.feature_dialog.drill_widget.selected_circles depths = get_depths_from_layout( self.feature_dialog.depth_groupBox.layout()) feature = PartFeature(base_feature.id, setup, geometry=geo, depths=depths) self.controller.current_camo_file.features.append(feature) self.controller.build_file_tree_model() def add_slot_line(self): self.feature_dialog.hide() self.graphicsView.graphics_view_clicked.connect( self.dxf_entity_clicked_slot_line) def dxf_entity_clicked_slot_line(self, item): item_data = item.data(0) line = None if item_data.DXFTYPE == 'LINE': line = item_data if line: self.feature_dialog.selected_slot_lines.append(str(line)) self.feature_dialog.slot_widget.line_list.addItem(str(line)) self.feature_dialog.show() self.graphicsView.graphics_view_clicked.disconnect( self.dxf_entity_clicked_slot_line) def find_circle_diameter(self): self.feature_dialog.hide() self.graphicsView.graphics_view_clicked.connect( self.dxf_entity_clicked_circle_diameter) def find_circles(self): diameter = self.feature_dialog.drill_widget.diameter_edit.text() try: diameter = float(diameter) assert diameter > 0 except: show_error_message('Error', 'No diameter given') return circles = [ entity for entity in self.controller.current_camo_file.dxf_doc.modelspace( ).entity_space.entities if entity.DXFTYPE == 'CIRCLE' ] filtered_circles = [ circle for circle in circles if math.isclose(circle.dxf.radius * 2, diameter, rel_tol=.0002) ] self.feature_dialog.drill_widget.geometry_list.clear() self.feature_dialog.drill_widget.selected_circles = [ str(x) for x in filtered_circles ] for circle in filtered_circles: item = qw.QListWidgetItem(str(circle)) item.circle = circle self.feature_dialog.drill_widget.geometry_list.addItem(item) self.feature_dialog.drill_widget.lcdNumber.display( len(filtered_circles)) def dxf_entity_clicked_circle_diameter(self, item): item_data = item.data(0) diameter = None if item_data.DXFTYPE == 'CIRCLE': diameter = item_data.dxf.radius * 2 elif item_data.DXFTYPE == 'ARC': diameter = item_data.dxf.radius * 2 if diameter: self.feature_dialog.drill_widget.diameter_edit.setText( str(round(diameter, 4))) self.feature_dialog.show() self.graphicsView.graphics_view_clicked.disconnect( self.dxf_entity_clicked_circle_diameter) print(diameter) # def fill_dxf_select_list(self, element: Optional[qw.QGraphicsItem]): # self.selected_dxf_entities.append(element.data(0)) # print(element) def import_dxf(self): path, _ = qw.QFileDialog.getOpenFileName( self, caption='Select CAD Document', filter='DXF Documents (*.dxf)') if path: incoming_dxf = ezdxf.readfile(path) importer = Importer(incoming_dxf, self.controller.current_camo_file.dxf_doc) # import all entities from source modelspace into modelspace of the target drawing importer.import_modelspace() importer.finalize() self.set_document(self.controller.current_camo_file.dxf_doc) self.controller.build_file_tree_model() def set_document(self, document: Drawing): self.doc = document self._render_context = RenderContext(document) self._visible_layers = None self._current_layout = None # self._populate_layouts() self._populate_layer_list() self.draw_layout('Model') self.model_space = self.doc.modelspace() self.all_dxf_entities = {} for entity in self.model_space.entity_space: self.all_dxf_entities[str(entity)] = entity if entity.DXFTYPE == "CIRCLE": if hasattr(entity.dxf, 'extrusion'): print(entity.dxf.extrusion) if entity.dxf.extrusion[2] == -1: entity.dxf.extrusion = Vector(0, 0, 1) # entity.dxf.center = Vector(-(entity.dxf.center.x), -(entity.dxf.center.y), 0) self.draw_origin() self.apply_origin_to_scene() def _populate_layer_list(self): self.layers.blockSignals(True) self.layers.clear() for layer in self._render_context.layers.values(): name = layer.layer item = qw.QListWidgetItem(name) item.setCheckState(qc.Qt.Checked) item.setBackground(qg.QColor(layer.color)) self.layers.addItem(item) self.layers.blockSignals(False) def draw_origin(self): pen = qg.QPen(qg.QColor('#e533e5'), 2) pen.setCosmetic(True) # changes width depending on zoom pen.setJoinStyle(qc.Qt.RoundJoin) length = self.scene.itemsBoundingRect().width() / 16 diameter = length / 10 origin_item = qw.QGraphicsItemGroup() circle = qw.QGraphicsEllipseItem( qc.QRectF(-diameter / 2, -diameter / 2, diameter, diameter)) circle.setPen(pen) circle.setEnabled(False) origin_item.addToGroup(circle) x_line = qw.QGraphicsLineItem( 0, 0, length, 0, ) x_line.setPen(pen) origin_item.addToGroup(x_line) y_line = qw.QGraphicsLineItem(0, 0, 0, length) y_line.setPen(pen) origin_item.addToGroup(y_line) origin_item.setEnabled(False) origin_item.setZValue(-1) self.scene_origin = origin_item self.scene.addItem(self.scene_origin) def draw_layout(self, layout_name: str): print(f'drawing {layout_name}') self._current_layout = layout_name self.renderer.clear() self.view.clear() layout = self.doc.layout(layout_name) self._update_render_context(layout) Frontend(self._render_context, self.renderer).draw_layout(layout) self.view.fit_to_scene() def _update_render_context(self, layout): assert self._render_context self._render_context.set_current_layout(layout) # Direct modification of RenderContext.layers would be more flexible, but would also expose the internals. if self._visible_layers is not None: self._render_context.set_layers_state(self._visible_layers, state=True) def resizeEvent(self, event: qg.QResizeEvent) -> None: self.view.fit_to_scene() @qc.pyqtSlot(qw.QListWidgetItem) def _layers_updated(self, _item: qw.QListWidgetItem): self._visible_layers = set() for i in range(self.layers.count()): layer = self.layers.item(i) if layer.checkState() == qc.Qt.Checked: self._visible_layers.add(layer.text()) self.draw_layout(self._current_layout) @qc.pyqtSlot() def _toggle_sidebar(self): self.sidebar.setHidden(not self.sidebar.isHidden()) @qc.pyqtSlot() def _toggle_join_polylines(self): self.renderer.draw_individual_polyline_elements = not self.renderer.draw_individual_polyline_elements self.draw_layout(self._current_layout) @qc.pyqtSlot(object, qc.QPointF) def _on_element_selected(self, element: Optional[qw.QGraphicsItem], mouse_pos: qc.QPointF): shifted_position = (mouse_pos.x() - self.scene_shift[0], mouse_pos.y() - self.scene_shift[1]) x, y = shifted_position x, y = rotate_point((x, y), -self.scene_shift[2]) # rotate also here text = f'mouse position: {shifted_position[0]:.4f}, {shifted_position[1]:.4f}\n' self.position_widget.xlabel.setText(f'X: {x:.4f}') self.position_widget.ylabel.setText(f'Y: {y:.4f}') if element is None: text += 'No element selected' else: dxf_entity = element.data(CorrespondingDXFEntity) if dxf_entity is None: text += 'No data' else: text += f'Current Entity: {dxf_entity}\nLayer: {dxf_entity.dxf.layer}\n\nDXF Attributes:\n' attribs = dxf_entity.dxf.all_existing_dxf_attribs() for key, value in attribs.items(): text += f'- {key}: {value}\n' dxf_entity_stack = element.data(CorrespondingDXFEntityStack) # model = self.controller.main_window.treeView.model() # view = self.treeView # if model and model.indexes: this doe the tree select based on hover # view.setCurrentIndex(model.indexes[str(dxf_entity)]) if dxf_entity_stack: text += '\nParents:\n' for entity in reversed(dxf_entity_stack): text += f'- {entity}\n' self.info.setPlainText(text)