From 779a8ccc9fb7a482464a84cf6d5cc41baf89c923 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 26 Apr 2022 14:29:23 +0300 Subject: [PATCH] - activated the 'View Source' option in the project menu context menu for the Geometry objects - in Image Import plugin now the created objects have the 'source_file' attribute with content - working in the Excellon Editor - wip --- CHANGELOG.md | 6 + appEditors/AppExcEditor.py | 344 ++++++++++++++++++++++++++++----- appEditors/AppGeoEditor.py | 3 +- appEditors/AppTextEditor.py | 21 +- appGUI/MainGUI.py | 80 ++++++-- appMain.py | 3 +- appObjects/ObjectCollection.py | 3 +- appPlugins/ToolImage.py | 11 +- 8 files changed, 392 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b13f52..e168d5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM Evo beta ================================================= +26.04.2022 + +- activated the 'View Source' option in the project menu context menu for the Geometry objects +- in Image Import plugin now the created objects have the 'source_file' attribute with content +- working in the Excellon Editor - wip + 25.04.2022 - deleted the Calibration Plugin (made redundant by changes in other Plugins) diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 91bf4cf9..a21eb997 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -5,15 +5,15 @@ # MIT Licence # # ########################################################## -from PyQt6 import QtGui, QtCore, QtWidgets -from PyQt6.QtCore import Qt - from camlib import distance, arc, AppRTreeStorage + +from appEditors.exc_plugins.ExcGenPlugin import * + from appGUI.GUIElements import FCEntry, FCTable, FCDoubleSpinner, RadioSet, FCSpinner, FCButton, FCLabel, GLay from appEditors.AppGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, AppGeoEditor from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point -import shapely.affinity as affinity +from shapely.affinity import scale, rotate, translate # from appCommon.Common import LoudDict import numpy as np @@ -61,6 +61,17 @@ class SelectEditorExc(FCShapeTool): self.draw_app.ui.slot_frame.hide() self.draw_app.ui.slot_array_frame.hide() + # make sure that the cursor text from the DrillAdd is deleted + if self.draw_app.app.plotcanvas.text_cursor.parent and self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + + # make sure that the Tools tab is removed + try: + self.draw_app.app.ui.notebook.removeTab(2) + except Exception: + pass + def click(self, point): key_modifier = QtWidgets.QApplication.keyboardModifiers() @@ -229,6 +240,7 @@ class DrillAdd(FCShapeTool): DrawTool.__init__(self, draw_app) self.name = 'drill_add' self.draw_app = draw_app + self.app = self.draw_app.app self.selected_dia = None try: @@ -258,17 +270,27 @@ class DrillAdd(FCShapeTool): self.draw_app.app.inform.emit(_("Click to place ...")) + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) - # Switch notebook to Properties page - self.draw_app.app.ui.notebook.setCurrentWidget(self.draw_app.app.ui.properties_tab) + self.drill_tool = ExcGenEditorTool(self.app, self.draw_app, plugin_name=_("Drill")) + self.drill_tool.run() + + self.app.ui.notebook.setTabText(2, _("Drill")) + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + self.points = deepcopy(self.draw_app.pos) if \ + self.draw_app.pos and self.draw_app.pos[0] and self.draw_app.pos[1] else (0.0, 0.0) + self.drill_point = None def click(self, point): + self.drill_point = point self.make() return "Done." def utility_geometry(self, data=None): - self.points = data return DrawToolUtilityShape(self.util_shape(data)) def util_shape(self, point): @@ -289,6 +311,9 @@ class DrillAdd(FCShapeTool): return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)]) def make(self): + if self.drill_point is None: + self.drill_point = (self.draw_app.snap_x, self.draw_app.snap_y) + self.selected_dia = self.draw_app.tool2tooldia[self.draw_app.last_tool_selected] try: QtGui.QGuiApplication.restoreOverrideCursor() @@ -298,22 +323,130 @@ class DrillAdd(FCShapeTool): # add the point to drills if the diameter is a key in the dict, if not, create it add the drill location # to the value, as a list of itself if self.selected_dia in self.draw_app.points_edit: - self.draw_app.points_edit[self.selected_dia].append(self.points) + self.draw_app.points_edit[self.selected_dia].append(self.drill_point) else: - self.draw_app.points_edit[self.selected_dia] = [self.points] + self.draw_app.points_edit[self.selected_dia] = [self.drill_point] self.draw_app.current_storage = self.draw_app.storage_dict[self.selected_dia] - self.geometry = DrawToolShape(self.util_shape(self.points)) + self.geometry = DrawToolShape(self.util_shape(self.drill_point)) self.draw_app.in_action = False self.complete = True self.draw_app.app.inform.emit('[success] %s' % _("Done.")) - self.draw_app.app.jump_signal.disconnect() + try: + self.draw_app.app.jump_signal.disconnect() + except (TypeError, AttributeError): + pass + + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if not self.points: + self.points = (0, 0) + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + x = pos[0] + y = pos[1] + try: + length = abs(np.sqrt((pos[0] - self.points[0]) ** 2 + (pos[1] - self.points[1]) ** 2)) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + except TypeError: + print(self.points) + units = self.draw_app.app.app_units.lower() + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + + def on_key(self, key): + # Jump to coords + if key == QtCore.Qt.Key.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + + if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \ + key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2, + QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6, + QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus, + QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, + QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: + try: + # VisPy keys + if self.drill_tool.length == 0: + self.drill_tool.length = str(key.name) + else: + self.drill_tool.length = str(self.drill_tool.length) + str(key.name) + except AttributeError: + # Qt keys + if self.drill_tool.length == 0: + self.drill_tool.length = chr(key) + else: + self.drill_tool.length = str(self.drill_tool.length) + chr(key) + + if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: + if self.drill_tool.length != 0: + target_length = self.drill_tool.length + if target_length is None: + self.drill_tool.length = 0.0 + return _("Failed.") + + first_pt = self.points + last_pt = self.draw_app.app.mouse + + seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) + if seg_length == 0.0: + return + try: + new_x = first_pt[0] + (last_pt[0] - first_pt[0]) / seg_length * target_length + new_y = first_pt[1] + (last_pt[1] - first_pt[1]) / seg_length * target_length + except ZeroDivisionError as err: + self.clean_up() + return '[ERROR_NOTCL] %s %s' % (_("Failed."), str(err).capitalize()) + + if self.points != (new_x, new_y): + self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) + self.make() + self.drill_point = (new_x, new_y) def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.tools_table_exc.clearSelection() self.draw_app.plot_all() + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + try: self.draw_app.app.jump_signal.disconnect() except (TypeError, AttributeError): @@ -449,7 +582,7 @@ class DrillArray(FCShapeTool): ) if static is None or static is False: - geo_list.append(affinity.translate(geo, xoff=(dx - self.last_dx), yoff=(dy - self.last_dy))) + geo_list.append(translate(geo, xoff=(dx - self.last_dx), yoff=(dy - self.last_dy))) else: geo_list.append(geo) # self.origin = data @@ -649,6 +782,7 @@ class SlotAdd(FCShapeTool): DrawTool.__init__(self, draw_app) self.name = 'slot_add' self.draw_app = draw_app + self.app = self.draw_app.app self.draw_app.ui.slot_frame.show() @@ -685,10 +819,16 @@ class SlotAdd(FCShapeTool): self.draw_app.app.inform.emit(_("Click on target location ...")) + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) - # Switch notebook to Properties page - self.draw_app.app.ui.notebook.setCurrentWidget(self.draw_app.app.ui.properties_tab) + self.slot_tool = ExcGenEditorTool(self.app, self.draw_app, plugin_name=_("Slot")) + self.slot_tool.run() + + self.app.ui.notebook.setTabText(2, _("Slot")) + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) def click(self, point): self.make() @@ -774,7 +914,7 @@ class SlotAdd(FCShapeTool): geo.append(p4) if self.draw_app.ui.slot_axis_radio.get_value() == 'A': - return affinity.rotate(geom=Polygon(geo), angle=-slot_angle) + return rotate(geom=Polygon(geo), angle=-slot_angle) else: return Polygon(geo) else: @@ -831,7 +971,58 @@ class SlotAdd(FCShapeTool): self.draw_app.ui.slot_frame.hide() self.draw_app.app.jump_signal.disconnect() + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + x = pos[0] + y = pos[1] + try: + length = abs(np.sqrt((pos[0] - self.points[-1][0]) ** 2 + (pos[1] - self.points[-1][1]) ** 2)) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + units = self.draw_app.app.app_units.lower() + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + def on_key(self, key): + # Jump to coords + if key == QtCore.Qt.Key.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + # Toggle Pad Direction if key == QtCore.Qt.Key.Key_Space: if self.draw_app.ui.slot_axis_radio.get_value() == 'X': @@ -843,6 +1034,57 @@ class SlotAdd(FCShapeTool): # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) + if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \ + key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2, + QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6, + QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus, + QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, + QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: + try: + # VisPy keys + if self.slot_tool.length == 0: + self.slot_tool.length = str(key.name) + else: + self.slot_tool.length = str(self.slot_tool.length) + str(key.name) + except AttributeError: + # Qt keys + if self.slot_tool.length == 0: + self.slot_tool.length = chr(key) + else: + self.slot_tool.length = str(self.slot_tool.length) + chr(key) + + if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: + if self.slot_tool.length != 0: + target_length = self.slot_tool.length + if target_length is None: + self.slot_tool.length = 0.0 + return _("Failed.") + + first_pt = self.points[-1] + last_pt = self.draw_app.app.mouse + + seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) + if seg_length == 0.0: + return + try: + new_x = first_pt[0] + (last_pt[0] - first_pt[0]) / seg_length * target_length + new_y = first_pt[1] + (last_pt[1] - first_pt[1]) / seg_length * target_length + except ZeroDivisionError as err: + self.points = [] + self.clean_up() + return '[ERROR_NOTCL] %s %s' % (_("Failed."), str(err).capitalize()) + + if self.points[-1] != (new_x, new_y): + self.points.append((new_x, new_y)) + self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) + if len(self.points) > 0: + msg = '%s: %s. %s' % ( + _("Projected"), str(self.slot_tool.length), + _("Click on next Point or click right mouse button to complete ...")) + self.draw_app.app.inform.emit(msg) + # self.interpolate_length = '' + # return "Click on next point or hit ENTER to complete ..." + def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.tools_table_exc.clearSelection() @@ -988,7 +1230,7 @@ class SlotArray(FCShapeTool): ) if static is None or static is False: - geo_el = affinity.translate(geo_el, xoff=(dx - self.last_dx), yoff=(dy - self.last_dy)) + geo_el = translate(geo_el, xoff=(dx - self.last_dx), yoff=(dy - self.last_dy)) geo_el_list.append(geo_el) self.last_dx = dx @@ -1053,7 +1295,7 @@ class SlotArray(FCShapeTool): y = self.origin[1] + radius * math.sin(-angle_radians + angle) geo_sol = self.util_shape((x, y)) - geo_sol = affinity.rotate(geo_sol, angle=(math.pi - angle_radians + angle), use_radians=True) + geo_sol = rotate(geo_sol, angle=(math.pi - angle_radians + angle), use_radians=True) circular_geo.append(DrawToolShape(geo_sol)) else: @@ -1063,7 +1305,7 @@ class SlotArray(FCShapeTool): y = self.origin[1] + radius * math.sin(angle_radians + angle) geo_sol = self.util_shape((x, y)) - geo_sol = affinity.rotate(geo_sol, angle=(angle_radians + angle - math.pi), use_radians=True) + geo_sol = rotate(geo_sol, angle=(angle_radians + angle - math.pi), use_radians=True) circular_geo.append(DrawToolShape(geo_sol)) @@ -1162,7 +1404,7 @@ class SlotArray(FCShapeTool): # this function return one slot in the slot array and the following will rotate that one slot around it's # center if the radio value is "A". if self.draw_app.ui.slot_axis_radio.get_value() == 'A': - return affinity.rotate(Polygon(geo), -slot_angle) + return rotate(Polygon(geo), -slot_angle) else: return Polygon(geo) @@ -1360,10 +1602,8 @@ class ResizeEditorExc(FCShapeTool): # add new geometry according to the new size if isinstance(select_shape.geo, MultiLineString): factor = new_dia / sel_dia - self.geometry.append(DrawToolShape(affinity.scale(select_shape.geo, - xfact=factor, - yfact=factor, - origin='center'))) + self.geometry.append(DrawToolShape(scale(select_shape.geo, xfact=factor, yfact=factor, + origin='center'))) elif isinstance(select_shape.geo, Polygon): # I don't have any info regarding the angle of the slot geometry, nor how thick it is or # how long it is given the angle. So I will have to make an approximation because @@ -1384,7 +1624,7 @@ class ResizeEditorExc(FCShapeTool): # make a list of intersections with the rotated line list_of_cuttings = [] for angle in range(0, 359, 1): - rot_poly_diagonal = affinity.rotate(poly_diagonal, angle=angle, origin=poly_center) + rot_poly_diagonal = rotate(poly_diagonal, angle=angle, origin=poly_center) cut_line = rot_poly_diagonal.intersection(poly) cut_line_len = cut_line.length list_of_cuttings.append( @@ -1594,7 +1834,7 @@ class MoveEditorExc(FCShapeTool): for select_shape in self.draw_app.get_selected(): if select_shape in self.current_storage.get_objects(): - self.geometry.append(DrawToolShape(affinity.translate(select_shape.geo, xoff=dx, yoff=dy))) + self.geometry.append(DrawToolShape(translate(select_shape.geo, xoff=dx, yoff=dy))) self.current_storage.remove(select_shape) sel_shapes_to_be_deleted.append(select_shape) self.draw_app.on_exc_shape_complete(self.current_storage) @@ -1655,7 +1895,7 @@ class MoveEditorExc(FCShapeTool): if len(self.draw_app.get_selected()) <= self.sel_limit: try: for geom in self.draw_app.get_selected(): - geo_list.append(affinity.translate(geom.geo, xoff=dx, yoff=dy)) + geo_list.append(translate(geom.geo, xoff=dx, yoff=dy)) except AttributeError: self.draw_app.select_tool('drill_select') self.draw_app.selected = [] @@ -1663,7 +1903,7 @@ class MoveEditorExc(FCShapeTool): return DrawToolUtilityShape(geo_list) else: try: - ss_el = affinity.translate(self.selection_shape, xoff=dx, yoff=dy) + ss_el = translate(self.selection_shape, xoff=dx, yoff=dy) except ValueError: ss_el = None return DrawToolUtilityShape(ss_el) @@ -1694,7 +1934,7 @@ class CopyEditorExc(MoveEditorExc): self.current_storage = self.draw_app.storage_dict[sel_dia] for select_shape in self.draw_app.get_selected(): if select_shape in self.current_storage.get_objects(): - self.geometry.append(DrawToolShape(affinity.translate(select_shape.geo, xoff=dx, yoff=dy))) + self.geometry.append(DrawToolShape(translate(select_shape.geo, xoff=dx, yoff=dy))) # Add some fake drills into the self.draw_app.points_edit to update the drill count in tool table # This may fail if we copy slots. @@ -2438,7 +2678,7 @@ class AppExcEditor(QtCore.QObject): geo_list = [] if isinstance(shape_exc.geo, MultiLineString): for subgeo in shape_exc.geo.geoms: - geo_list.append(affinity.scale(subgeo, xfact=factor, yfact=factor, origin='center')) + geo_list.append(scale(subgeo, xfact=factor, yfact=factor, origin='center')) new_geo = MultiLineString(geo_list) elif isinstance(shape_exc.geo, Polygon): # I don't have any info regarding the angle of the slot geometry, nor how thick it is or @@ -2460,7 +2700,7 @@ class AppExcEditor(QtCore.QObject): # make a list of intersections with the rotated line list_of_cuttings = [] for angle in range(0, 359, 1): - rot_poly_diagonal = affinity.rotate(poly_diagonal, angle=angle, origin=poly_center) + rot_poly_diagonal = rotate(poly_diagonal, angle=angle, origin=poly_center) cut_line = rot_poly_diagonal.intersection(poly) cut_line_len = cut_line.length list_of_cuttings.append( @@ -3460,7 +3700,7 @@ class AppExcEditor(QtCore.QObject): # "%.4f    " % (self.app.dx, self.app.dy)) self.app.ui.update_location_labels(self.app.dx, self.app.dy, x, y) - units = self.app.app_units.lower() + # units = self.app.app_units.lower() # self.app.plotcanvas.text_hud.text = \ # 'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format( # self.app.dx, units, self.app.dy, units, x, units, y, units) @@ -3469,6 +3709,14 @@ class AppExcEditor(QtCore.QObject): # ## Utility geometry (animated) self.update_utility_geometry(data=(x, y)) + self.update_utility_geometry(data=(x, y)) + if self.active_tool.name in ['drill_add', 'drill_array', 'slot_add', 'slot_array', 'copy']: + try: + self.active_tool.draw_cursor_data(pos=(x, y)) + except AttributeError: + # this can happen if the method is not implemented yet for the active_tool + pass + # ## Selection area on canvas section # ## if event_is_dragging == 1 and event.button == 1: # I make an exception for FCDrillAdd and DrillArray because clicking and dragging while making regions @@ -3495,34 +3743,34 @@ class AppExcEditor(QtCore.QObject): edge_width=self.app.options["global_cursor_width"], size=self.app.options["global_cursor_size"]) - def add_exc_shape(self, shape, storage): + def add_exc_shape(self, shp, storage): """ Adds a shape to a specified shape storage. - :param shape: Shape to be added. - :type shape: DrawToolShape + :param shp: Shape to be added. + :type shp: DrawToolShape :param storage: object where to store the shapes :return: None """ # List of DrawToolShape? - if isinstance(shape, list): - for subshape in shape: + if isinstance(shp, list): + for subshape in shp: self.add_exc_shape(subshape, storage) return - assert isinstance(shape, DrawToolShape), \ - "Expected a DrawToolShape, got %s" % str(type(shape)) + assert isinstance(shp, DrawToolShape), \ + "Expected a DrawToolShape, got %s" % str(type(shp)) - assert shape.geo is not None, \ + assert shp.geo is not None, \ "Shape object has empty geometry (None)" - assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or not isinstance(shape.geo, list), \ + assert (isinstance(shp.geo, list) and len(shp.geo) > 0) or not isinstance(shp.geo, list), \ "Shape objects has empty geometry ([])" - if isinstance(shape, DrawToolUtilityShape): - self.utility.append(shape) + if isinstance(shp, DrawToolUtilityShape): + self.utility.append(shp) else: - storage.insert(shape) # TODO: Check performance + storage.insert(shp) # TODO: Check performance def add_shape(self, shape): """ @@ -3970,13 +4218,13 @@ class AppExcEditor(QtCore.QObject): for dict_dia, geo_dict in self.storage_dict.items(): if self.dec_format(float(dict_dia)) == table_tooldia: storage_elem = AppGeoEditor.make_storage() - for shape in geo_dict.get_objects(): - if isinstance(shape.geo, MultiLineString): + for shp in geo_dict.get_objects(): + if isinstance(shp.geo, MultiLineString): # it's a drill just add it as it is to storage - self.add_exc_shape(shape, storage_elem) - if isinstance(shape.geo, Polygon): + self.add_exc_shape(shp, storage_elem) + if isinstance(shp.geo, Polygon): # it's a slot, convert drill to slot and then add it to storage - new_shape = convert_slot2drill(shape.geo, table_tooldia) + new_shape = convert_slot2drill(shp.geo, table_tooldia) self.add_exc_shape(DrawToolShape(new_shape), storage_elem) new_storage_dict[table_tooldia] = storage_elem diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index cc6839f4..cc033165 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -1604,8 +1604,9 @@ class FCSelect(DrawTool): # self.selected = self.draw_app.selected # make sure that the cursor text from the FCPath is deleted - if self.draw_app.app.plotcanvas.text_cursor.parent: + if self.draw_app.app.plotcanvas.text_cursor.parent and self.draw_app.app.use_3d_engine: self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None # make sure that the Tools tab is removed try: diff --git a/appEditors/AppTextEditor.py b/appEditors/AppTextEditor.py index f0aa2f9b..00e91a77 100644 --- a/appEditors/AppTextEditor.py +++ b/appEditors/AppTextEditor.py @@ -198,16 +198,27 @@ class AppTextEditor(QtWidgets.QWidget): self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as_red.png')) def load_text(self, text, move_to_start=False, move_to_end=False, clear_text=True, as_html=False): - self.code_editor.textChanged.disconnect() + try: + self.code_editor.textChanged.disconnect() + except (AttributeError, TypeError): + pass if clear_text: # first clear previous text in text editor (if any) self.code_editor.clear() self.code_editor.setReadOnly(False) - if as_html is False: - self.code_editor.setPlainText(text) - else: - self.code_editor.setHtml(text) + try: + if as_html is False: + self.code_editor.setPlainText(text) + else: + if isinstance(self.code_editor, QtWidgets.QTextEdit): + self.code_editor.setHtml(text) + else: + self.code_editor.setPlainText(text) + except Exception as err: + self.app.log.error("AppTextEditor.load_text() --> %s." % str(err)) + self.code_editor.textChanged.connect(self.handleTextChanged) + return if move_to_start: self.code_editor.moveCursor(QtGui.QTextCursor.MoveOperation.Start) elif move_to_end: diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 95fb90e6..a7371e50 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -3515,7 +3515,7 @@ class MainGUI(QtWidgets.QMainWindow): # Finish the current action. Use with tools that do not # complete automatically, like a polygon or path. - if key == QtCore.Qt.Key.Key_Enter or key == 'Enter': + if key == QtCore.Qt.Key.Key_Enter or key == 'Enter' or key == QtCore.Qt.Key.Key_Return: if isinstance(self.app.geo_editor.active_tool, FCShapeTool): if self.app.geo_editor.active_tool.name == 'rotate': self.app.geo_editor.active_tool.make() @@ -3791,7 +3791,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.grb_editor.on_transform() return # NO MODIFIER - elif modifiers == QtCore.Qt.KeyboardModifier.NoModifier: + elif modifiers == QtCore.Qt.KeyboardModifier.NoModifier or \ + modifiers == QtCore.Qt.KeyboardModifier.KeypadModifier: # Abort the current action if key == QtCore.Qt.Key.Key_Escape or key == 'Escape': # self.on_tool_select("select") @@ -4024,7 +4025,8 @@ class MainGUI(QtWidgets.QMainWindow): elif modifiers == QtCore.Qt.KeyboardModifier.AltModifier: pass # NO MODIFIER - elif modifiers == QtCore.Qt.KeyboardModifier.NoModifier: + elif modifiers == QtCore.Qt.KeyboardModifier.NoModifier or \ + modifiers == QtCore.Qt.KeyboardModifier.KeypadModifier: # Abort the current action if key == QtCore.Qt.Key.Key_Escape or key == 'Escape': self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) @@ -4034,6 +4036,44 @@ class MainGUI(QtWidgets.QMainWindow): self.app.exc_editor.select_tool('drill_select') return + # Finish the current action. Use with tools that do not + # complete automatically. + if key == QtCore.Qt.Key.Key_Enter or key == 'Enter' or key == QtCore.Qt.Key.Key_Return: + if isinstance(self.app.exc_editor.active_tool, FCShapeTool): + if self.app.exc_editor.active_tool.name == 'drill_add' \ + and self.app.exc_editor.active_tool.drill_tool.length != 0.0: + pass + elif self.app.exc_editor.active_tool.name == 'drill_array' \ + and self.app.exc_editor.active_tool.drill_array_tool.length != 0.0: + pass + elif self.app.exc_editor.active_tool.name == 'slot_add' \ + and self.app.exc_editor.active_tool.slot_tool.x != 0.0 \ + and self.app.exc_editor.active_tool.slot_tool.y != 0.0: + pass + elif self.app.exc_editor.active_tool.name == 'slot_array' \ + and self.app.exc_editor.active_tool.slot_array_tool.length != 0.0 \ + and self.app.exc_editor.active_tool.slot_array_tool.width != 0.0: + pass + elif self.app.exc_editor.active_tool.name == 'move' \ + and self.app.exc_editor.active_tool.move_tool.length != 0.0 \ + and self.app.exc_editor.active_tool.move_tool.width != 0.0: + pass + elif self.app.exc_editor.active_tool.name == 'copy' \ + and self.app.exc_editor.active_tool.copy_tool.length != 0.0 \ + and self.app.exc_editor.active_tool.copy_tool.width != 0.0: + pass + else: + self.app.exc_editor.active_tool.click( + self.app.geo_editor.snap(self.app.exc_editor.x, self.app.exc_editor.y)) + + self.app.exc_editor.active_tool.make() + + if self.app.exc_editor.active_tool.complete: + self.app.exc_editor.on_shape_complete() + self.app.inform.emit('[success] %s' % _("Done.")) + # automatically make the selection tool active after completing current action + self.app.exc_editor.select_tool('select') + # Delete selected object if delete key event comes out of canvas if key == 'Delete': self.app.exc_editor.launched_from_shortcuts = True @@ -4068,23 +4108,23 @@ class MainGUI(QtWidgets.QMainWindow): self.on_toggle_notebook() return - # Switch to Project Tab - if key == QtCore.Qt.Key.Key_1 or key == '1': - self.app.exc_editor.launched_from_shortcuts = True - self.on_select_tab('project') - return - - # Switch to Selected Tab - if key == QtCore.Qt.Key.Key_2 or key == '2': - self.app.exc_editor.launched_from_shortcuts = True - self.on_select_tab('selected') - return - - # Switch to Tool Tab - if key == QtCore.Qt.Key.Key_3 or key == '3': - self.app.exc_editor.launched_from_shortcuts = True - self.on_select_tab('tool') - return + # # Switch to Project Tab + # if key == QtCore.Qt.Key.Key_1 or key == '1': + # self.app.exc_editor.launched_from_shortcuts = True + # self.on_select_tab('project') + # return + # + # # Switch to Selected Tab + # if key == QtCore.Qt.Key.Key_2 or key == '2': + # self.app.exc_editor.launched_from_shortcuts = True + # self.on_select_tab('selected') + # return + # + # # Switch to Tool Tab + # if key == QtCore.Qt.Key.Key_3 or key == '3': + # self.app.exc_editor.launched_from_shortcuts = True + # self.on_select_tab('tool') + # return # Grid Snap if key == QtCore.Qt.Key.Key_G or key == 'G': diff --git a/appMain.py b/appMain.py index bf90d7c1..2607d934 100644 --- a/appMain.py +++ b/appMain.py @@ -8298,7 +8298,8 @@ class App(QtCore.QObject): self.source_editor_tab.t_frame.hide() try: - self.source_editor_tab.load_text(file.getvalue(), clear_text=True, move_to_start=True) + source_text = file.getvalue() + self.source_editor_tab.load_text(source_text, clear_text=True, move_to_start=True) except Exception as e: self.log.error('App.on_view_source() -->%s' % str(e)) self.inform.emit('[ERROR] %s: %s' % (_('Failed to load the source code for the selected object'), str(e))) diff --git a/appObjects/ObjectCollection.py b/appObjects/ObjectCollection.py index 742aefab..f98b19ed 100644 --- a/appObjects/ObjectCollection.py +++ b/appObjects/ObjectCollection.py @@ -428,9 +428,8 @@ class ObjectCollection(QtCore.QAbstractItemModel): if obj.kind == 'gerber' or obj.kind == 'excellon': self.app.ui.menuprojectcolor.setEnabled(True) - if obj.kind != 'gerber' and obj.kind != 'excellon' and obj.kind != 'cncjob': + if obj.kind not in ['geometry', 'gerber', 'excellon', 'cncjob']: self.app.ui.menuprojectviewsource.setVisible(False) - if obj.kind != 'gerber' and obj.kind != 'geometry' and obj.kind != 'excellon' and obj.kind != 'cncjob': # meaning for Scripts and for Document type of FlatCAM object self.app.ui.menuprojectenable.setVisible(False) self.app.ui.menuprojectdisable.setVisible(False) diff --git a/appPlugins/ToolImage.py b/appPlugins/ToolImage.py index bf71957d..c4319618 100644 --- a/appPlugins/ToolImage.py +++ b/appPlugins/ToolImage.py @@ -179,8 +179,8 @@ class ToolImage(AppTool): if response == bt_no: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) return - self.app.inform.emit(_("Please be patient. Chromium is being downloaded in the background.\n" - "The app will resume after it is installed.")) + self.app.inform.emit(_("Please be patient. Chromium is being downloaded in the background.\n" + "The app will resume after it is installed.")) _filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \ "Bitmap File (*.BMP);;" \ @@ -296,6 +296,11 @@ class ToolImage(AppTool): }) geo_obj.tools[1]['data']['name'] = name + if svg_text is not None: + geo_obj.source_file = svg_text + else: + geo_obj.source_file = app_obj.f_handlers.export_dxf( + obj_name=None, filename=None, local_use=geo_obj, use_thread=False) else: # 'gerber' if 0 not in geo_obj.tools: geo_obj.tools[0] = { @@ -317,6 +322,8 @@ class ToolImage(AppTool): isinstance(geo_obj.solid_geometry, Polygon) else geo_obj.solid_geometry } geo_obj.tools[0]['geometry'].append(new_el) + geo_obj.source_file = app_obj.f_handlers.export_gerber( + obj_name=None, filename=None, local_use=geo_obj, use_thread=False) with self.app.proc_container.new('%s ...' % _("Importing")):