예제 #1
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)
예제 #2
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)

        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:
            try:
                self.set_document(ezdxf.readfile(path))
            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 - ' + 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(name)
            item.setCheckState(qc.Qt.Checked)
            item.setBackground(qg.QColor(layer.color))
            if is_dark_color(layer.color, 0.4):
                item.setForeground(qg.QColor('#FFFFFF'))
            else:
                item.setForeground(qg.QColor(qg.QColor('#000000')))
            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)
        try:
            Frontend(self._render_context, self.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:
            self.renderer.finalize()
        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(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)