Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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)