From 84f3166e2f759a51e454fe39a70378c0b8752d7f Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sat, 13 Apr 2019 22:56:46 +0300
Subject: [PATCH 01/42] - Gerber Editor: Remade the processing of
'clear_geometry' (geometry generated by polygons made with Gerber LPC
command) to work if more than one such polygon exists
---
FlatCAMApp.py | 4 ++--
README.md | 4 ++++
camlib.py | 16 +++++++++-------
3 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 123dc481..2787a485 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -94,8 +94,8 @@ class App(QtCore.QObject):
log.addHandler(handler)
# Version
- version = 8.913
- version_date = "2019/04/13"
+ version = 8.914
+ version_date = "2019/04/20"
beta = True
# current date now
diff --git a/README.md b/README.md
index f99f17da..0787fea9 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+14.04.2019
+
+- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
+
13.04.2019
- updating the German translation
diff --git a/camlib.py b/camlib.py
index 16eddd60..c732b8a1 100644
--- a/camlib.py
+++ b/camlib.py
@@ -3183,16 +3183,18 @@ class Gerber (Geometry):
temp_geo = []
for apid in self.apertures:
if 'clear_geometry' in self.apertures[apid]:
- for clear_geo in self.apertures[apid]['clear_geometry']:
- for solid_geo in self.apertures[apid]['solid_geometry']:
- if solid_geo.intersects(clear_geo):
- res_geo = clear_geo.symmetric_difference(solid_geo)
- temp_geo.append(res_geo)
- else:
- temp_geo.append(solid_geo)
+ clear_geo = cascaded_union(self.apertures[apid]['clear_geometry'])
+ for solid_geo in self.apertures[apid]['solid_geometry']:
+ if clear_geo.intersects(solid_geo):
+ res_geo = clear_geo.symmetric_difference(solid_geo)
+ temp_geo.append(res_geo)
+ else:
+ temp_geo.append(solid_geo)
self.apertures[apid]['solid_geometry'] = deepcopy(temp_geo)
self.apertures[apid].pop('clear_geometry', None)
+
+
# --- Apply buffer ---
# this treats the case when we are storing geometry as paths
self.follow_geometry = follow_buffer
From 0fdd2e4f7c9bd156c5381026717e155c88be7251 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sat, 13 Apr 2019 23:47:02 +0300
Subject: [PATCH 02/42] - Gerber Editor: a disabled/enabled sequence for the
VisPy cursor on Gerber edit make the graphics better
---
README.md | 1 +
flatcamEditors/FlatCAMGrbEditor.py | 9 +++++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 0787fea9..778459f5 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
14.04.2019
- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
+- Gerber Editor: a disabled/enabled sequence for the VisPy cursor on Gerber edit make the graphics better
13.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 7ec59756..6e384223 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -2572,10 +2572,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.plot_thread.stop()
self.set_ui()
- # now that we hava data, create the GUI interface and add it to the Tool Tab
+ # now that we have data, create the GUI interface and add it to the Tool Tab
self.build_ui()
-
self.plot_all()
+
+ # HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
+ # - perhaps is a bug in VisPy implementation
+ self.app.app_cursor.enabled = False
+ self.app.app_cursor.enabled = True
+
log.debug("FlatCAMGrbEditor --> delayed_plot finished")
except Exception:
traceback.print_exc()
From 4100e98ebef835b6d61e484efdd85134c8f11136 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 01:03:42 +0300
Subject: [PATCH 03/42] - Gerber Editor: some more changes in processing LPC
polygons
---
camlib.py | 4 ++--
flatcamEditors/FlatCAMGrbEditor.py | 34 ++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/camlib.py b/camlib.py
index c732b8a1..b3dffb83 100644
--- a/camlib.py
+++ b/camlib.py
@@ -3183,10 +3183,10 @@ class Gerber (Geometry):
temp_geo = []
for apid in self.apertures:
if 'clear_geometry' in self.apertures[apid]:
- clear_geo = cascaded_union(self.apertures[apid]['clear_geometry'])
+ clear_geo = MultiPolygon(self.apertures[apid]['clear_geometry'])
for solid_geo in self.apertures[apid]['solid_geometry']:
if clear_geo.intersects(solid_geo):
- res_geo = clear_geo.symmetric_difference(solid_geo)
+ res_geo = solid_geo.difference(clear_geo)
temp_geo.append(res_geo)
else:
temp_geo.append(solid_geo)
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 6e384223..63dcc31d 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -1292,6 +1292,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
+ # holds flattened geometry
+ self.flat_geometry = []
+
# Init GUI
self.apdim_lbl.hide()
self.apdim_entry.hide()
@@ -1927,6 +1930,37 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.clear(update=True)
self.tool_shape.clear(update=True)
+ def flatten(self, geometry=None, reset=True, pathonly=False):
+ """
+ Creates a list of non-iterable linear geometry objects.
+ Polygons are expanded into its exterior pathonly param if specified.
+
+ Results are placed in flat_geometry
+
+ :param geometry: Shapely type or list or list of list of such.
+ :param reset: Clears the contents of self.flat_geometry.
+ :param pathonly: Expands polygons into linear elements from the exterior attribute.
+ """
+
+ if reset:
+ self.flat_geometry = []
+ ## If iterable, expand recursively.
+ try:
+ for geo in geometry:
+ if geo is not None:
+ self.flatten(geometry=geo, reset=False, pathonly=pathonly)
+
+ ## Not iterable, do the actual indexing and add.
+ except TypeError:
+ if pathonly and type(geometry) == Polygon:
+ self.flat_geometry.append(geometry.exterior)
+ self.flatten(geometry=geometry.interiors,
+ reset=False,
+ pathonly=True)
+ else:
+ self.flat_geometry.append(geometry)
+ return self.flat_geometry
+
def edit_fcgerber(self, orig_grb_obj):
"""
Imports the geometry found in self.apertures from the given FlatCAM Gerber object
From 081231aca47f7464a4d3678ac3ac4f4fb5acc05a Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 02:10:31 +0300
Subject: [PATCH 04/42] - Editors: activated an old function that was no longer
active: each tool can have it's own set of shortcut keys, the Editor general
shortcut keys that are letters are overridden - Gerber and Geometry editors,
when using the Backspace keys for certain tools, they will backtrack one
point but now the utility geometry is immediately updated
---
README.md | 2 +
flatcamEditors/FlatCAMGeoEditor.py | 22 +-
flatcamEditors/FlatCAMGrbEditor.py | 14 +-
flatcamGUI/FlatCAMGUI.py | 466 +++++++++++++++--------------
4 files changed, 264 insertions(+), 240 deletions(-)
diff --git a/README.md b/README.md
index 778459f5..4bba0199 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
- Gerber Editor: a disabled/enabled sequence for the VisPy cursor on Gerber edit make the graphics better
+- Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden
+- Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated
13.04.2019
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index a3d26f0d..987c22a9 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -2003,18 +2003,18 @@ class FCArc(FCShapeTool):
return ""
def on_key(self, key):
- if key == 'o':
+ if key == 'D' or key == QtCore.Qt.Key_D:
self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
- return 'Direction: ' + self.direction.upper()
+ return _('Direction: %s') % self.direction.upper()
- if key == 'p':
+ if key == 'M' or key == QtCore.Qt.Key_M:
if self.mode == 'c12':
self.mode = '12c'
elif self.mode == '12c':
self.mode = '132'
else:
self.mode = 'c12'
- return 'Mode: ' + self.mode
+ return _('Mode: %s') % self.mode
def utility_geometry(self, data=None):
if len(self.points) == 1: # Show the radius
@@ -2233,9 +2233,14 @@ class FCPolygon(FCShapeTool):
self.draw_app.app.inform.emit(_("[success] Done. Polygon completed."))
def on_key(self, key):
- if key == 'backspace':
+ if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
self.points = self.points[0:-1]
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+ return _("Backtracked one point ...")
class FCPath(FCPolygon):
@@ -2260,9 +2265,14 @@ class FCPath(FCPolygon):
return None
def on_key(self, key):
- if key == 'backspace':
+ if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
self.points = self.points[0:-1]
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+ return _("Backtracked one point ...")
class FCSelect(DrawTool):
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 63dcc31d..58d78d3a 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -527,9 +527,14 @@ class FCRegion(FCShapeTool):
self.draw_app.plot_all()
def on_key(self, key):
- if key == 'backspace':
+ if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
self.points = self.points[0:-1]
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+ return _("Backtracked one point ...")
class FCTrack(FCRegion):
@@ -560,9 +565,14 @@ class FCTrack(FCRegion):
return None
def on_key(self, key):
- if key == 'backspace':
+ if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
self.points = self.points[0:-1]
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+ return _("Backtracked one point ...")
class FCScale(FCShapeTool):
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index 9ad13b1b..8e4c038d 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -1952,6 +1952,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# events from Vispy are of type KeyEvent
else:
key = event.key
+
+ # Propagate to tool
+ response = None
+
if self.app.call_source == 'app':
if modifiers == QtCore.Qt.ControlModifier:
if key == QtCore.Qt.Key_A:
@@ -2380,132 +2384,130 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_3 or key == '3':
self.app.on_select_tab('tool')
- # Arc Tool
- if key == QtCore.Qt.Key_A or key == 'A':
- self.app.geo_editor.select_tool('arc')
-
- # Buffer
- if key == QtCore.Qt.Key_B or key == 'B':
- self.app.geo_editor.select_tool('buffer')
-
- # Copy
- if key == QtCore.Qt.Key_C or key == 'C':
- self.app.geo_editor.on_copy_click()
-
- # Substract Tool
- if key == QtCore.Qt.Key_E or key == 'E':
- if self.app.geo_editor.get_selected() is not None:
- self.app.geo_editor.intersection()
- else:
- msg = _("Please select geometry items \n" \
- "on which to perform Intersection Tool.")
-
- messagebox = QtWidgets.QMessageBox()
- messagebox.setText(msg)
- messagebox.setWindowTitle(_("Warning"))
- messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
- messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
- messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
- messagebox.exec_()
-
- # Grid Snap
- if key == QtCore.Qt.Key_G or key == 'G':
- self.app.ui.grid_snap_btn.trigger()
-
- # make sure that the cursor shape is enabled/disabled, too
- if self.app.geo_editor.options['grid_snap'] is True:
- self.app.app_cursor.enabled = True
- else:
- self.app.app_cursor.enabled = False
-
- # Paint
- if key == QtCore.Qt.Key_I or key == 'I':
- self.app.geo_editor.select_tool('paint')
-
- # Jump to coords
- if key == QtCore.Qt.Key_J or key == 'J':
- self.app.on_jump_to()
-
- # Corner Snap
- if key == QtCore.Qt.Key_K or key == 'K':
- self.app.geo_editor.on_corner_snap()
-
- # Move
- if key == QtCore.Qt.Key_M or key == 'M':
- self.app.geo_editor.on_move_click()
-
- # Polygon Tool
- if key == QtCore.Qt.Key_N or key == 'N':
- self.app.geo_editor.select_tool('polygon')
-
- # Circle Tool
- if key == QtCore.Qt.Key_O or key == 'O':
- self.app.geo_editor.select_tool('circle')
-
- # Path Tool
- if key == QtCore.Qt.Key_P or key == 'P':
- self.app.geo_editor.select_tool('path')
-
- # Rectangle Tool
- if key == QtCore.Qt.Key_R or key == 'R':
- self.app.geo_editor.select_tool('rectangle')
-
- # Substract Tool
- if key == QtCore.Qt.Key_S or key == 'S':
- if self.app.geo_editor.get_selected() is not None:
- self.app.geo_editor.subtract()
- else:
- msg = _(
- "Please select geometry items \n"
- "on which to perform Substraction Tool.")
-
- messagebox = QtWidgets.QMessageBox()
- messagebox.setText(msg)
- messagebox.setWindowTitle(_("Warning"))
- messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
- messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
- messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
- messagebox.exec_()
-
- # Add Text Tool
- if key == QtCore.Qt.Key_T or key == 'T':
- self.app.geo_editor.select_tool('text')
-
- # Substract Tool
- if key == QtCore.Qt.Key_U or key == 'U':
- if self.app.geo_editor.get_selected() is not None:
- self.app.geo_editor.union()
- else:
- msg = _("Please select geometry items \n"
- "on which to perform union.")
-
- messagebox = QtWidgets.QMessageBox()
- messagebox.setText(msg)
- messagebox.setWindowTitle(_("Warning"))
- messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
- messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
- messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
- messagebox.exec_()
-
- if key == QtCore.Qt.Key_V or key == 'V':
- self.app.on_zoom_fit(None)
-
- # Flip on X axis
- if key == QtCore.Qt.Key_X or key == 'X':
- self.app.geo_editor.transform_tool.on_flipx()
- return
-
- # Flip on Y axis
- if key == QtCore.Qt.Key_Y or key == 'Y':
- self.app.geo_editor.transform_tool.on_flipy()
- return
-
- # Propagate to tool
- response = None
- if self.app.geo_editor.active_tool is not None:
+ if self.app.geo_editor.active_tool is not None and self.geo_select_btn.isChecked() == False:
response = self.app.geo_editor.active_tool.on_key(key=key)
- if response is not None:
- self.app.inform.emit(response)
+ if response is not None:
+ self.app.inform.emit(response)
+ else:
+ # Arc Tool
+ if key == QtCore.Qt.Key_A or key == 'A':
+ self.app.geo_editor.select_tool('arc')
+
+ # Buffer
+ if key == QtCore.Qt.Key_B or key == 'B':
+ self.app.geo_editor.select_tool('buffer')
+
+ # Copy
+ if key == QtCore.Qt.Key_C or key == 'C':
+ self.app.geo_editor.on_copy_click()
+
+ # Substract Tool
+ if key == QtCore.Qt.Key_E or key == 'E':
+ if self.app.geo_editor.get_selected() is not None:
+ self.app.geo_editor.intersection()
+ else:
+ msg = _("Please select geometry items \n" \
+ "on which to perform Intersection Tool.")
+
+ messagebox = QtWidgets.QMessageBox()
+ messagebox.setText(msg)
+ messagebox.setWindowTitle(_("Warning"))
+ messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+ messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
+ messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
+ messagebox.exec_()
+
+ # Grid Snap
+ if key == QtCore.Qt.Key_G or key == 'G':
+ self.app.ui.grid_snap_btn.trigger()
+
+ # make sure that the cursor shape is enabled/disabled, too
+ if self.app.geo_editor.options['grid_snap'] is True:
+ self.app.app_cursor.enabled = True
+ else:
+ self.app.app_cursor.enabled = False
+
+ # Paint
+ if key == QtCore.Qt.Key_I or key == 'I':
+ self.app.geo_editor.select_tool('paint')
+
+ # Jump to coords
+ if key == QtCore.Qt.Key_J or key == 'J':
+ self.app.on_jump_to()
+
+ # Corner Snap
+ if key == QtCore.Qt.Key_K or key == 'K':
+ self.app.geo_editor.on_corner_snap()
+
+ # Move
+ if key == QtCore.Qt.Key_M or key == 'M':
+ self.app.geo_editor.on_move_click()
+
+ # Polygon Tool
+ if key == QtCore.Qt.Key_N or key == 'N':
+ self.app.geo_editor.select_tool('polygon')
+
+ # Circle Tool
+ if key == QtCore.Qt.Key_O or key == 'O':
+ self.app.geo_editor.select_tool('circle')
+
+ # Path Tool
+ if key == QtCore.Qt.Key_P or key == 'P':
+ self.app.geo_editor.select_tool('path')
+
+ # Rectangle Tool
+ if key == QtCore.Qt.Key_R or key == 'R':
+ self.app.geo_editor.select_tool('rectangle')
+
+ # Substract Tool
+ if key == QtCore.Qt.Key_S or key == 'S':
+ if self.app.geo_editor.get_selected() is not None:
+ self.app.geo_editor.subtract()
+ else:
+ msg = _(
+ "Please select geometry items \n"
+ "on which to perform Substraction Tool.")
+
+ messagebox = QtWidgets.QMessageBox()
+ messagebox.setText(msg)
+ messagebox.setWindowTitle(_("Warning"))
+ messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+ messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
+ messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
+ messagebox.exec_()
+
+ # Add Text Tool
+ if key == QtCore.Qt.Key_T or key == 'T':
+ self.app.geo_editor.select_tool('text')
+
+ # Substract Tool
+ if key == QtCore.Qt.Key_U or key == 'U':
+ if self.app.geo_editor.get_selected() is not None:
+ self.app.geo_editor.union()
+ else:
+ msg = _("Please select geometry items \n"
+ "on which to perform union.")
+
+ messagebox = QtWidgets.QMessageBox()
+ messagebox.setText(msg)
+ messagebox.setWindowTitle(_("Warning"))
+ messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+ messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
+ messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
+ messagebox.exec_()
+
+ if key == QtCore.Qt.Key_V or key == 'V':
+ self.app.on_zoom_fit(None)
+
+ # Flip on X axis
+ if key == QtCore.Qt.Key_X or key == 'X':
+ self.app.geo_editor.transform_tool.on_flipx()
+ return
+
+ # Flip on Y axis
+ if key == QtCore.Qt.Key_Y or key == 'Y':
+ self.app.geo_editor.transform_tool.on_flipy()
+ return
# Show Shortcut list
if key == 'F3':
@@ -2599,114 +2601,114 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.on_select_tab('tool')
return
- # Add Array of pads
- if key == QtCore.Qt.Key_A or key == 'A':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.inform.emit("Click on target point.")
- self.app.ui.add_pad_ar_btn.setChecked(True)
-
- self.app.grb_editor.x = self.app.mouse[0]
- self.app.grb_editor.y = self.app.mouse[1]
-
- self.app.grb_editor.select_tool('array')
- return
-
- # Scale Tool
- if key == QtCore.Qt.Key_B or key == 'B':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.grb_editor.select_tool('buffer')
- return
-
- # Copy
- if key == QtCore.Qt.Key_C or key == 'C':
- self.app.grb_editor.launched_from_shortcuts = True
- if self.app.grb_editor.selected:
- self.app.inform.emit(_("Click on target point."))
- self.app.ui.aperture_copy_btn.setChecked(True)
- self.app.grb_editor.on_tool_select('copy')
- self.app.grb_editor.active_tool.set_origin(
- (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
- else:
- self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy."))
- return
-
- # Grid Snap
- if key == QtCore.Qt.Key_G or key == 'G':
- self.app.grb_editor.launched_from_shortcuts = True
- # make sure that the cursor shape is enabled/disabled, too
- if self.app.grb_editor.options['grid_snap'] is True:
- self.app.app_cursor.enabled = False
- else:
- self.app.app_cursor.enabled = True
- self.app.ui.grid_snap_btn.trigger()
- return
-
- # Jump to coords
- if key == QtCore.Qt.Key_J or key == 'J':
- self.app.on_jump_to()
-
- # Corner Snap
- if key == QtCore.Qt.Key_K or key == 'K':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.ui.corner_snap_btn.trigger()
- return
-
- # Move
- if key == QtCore.Qt.Key_M or key == 'M':
- self.app.grb_editor.launched_from_shortcuts = True
- if self.app.grb_editor.selected:
- self.app.inform.emit(_("Click on target point."))
- self.app.ui.aperture_move_btn.setChecked(True)
- self.app.grb_editor.on_tool_select('move')
- self.app.grb_editor.active_tool.set_origin(
- (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
- else:
- self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move."))
- return
-
- # Add Region Tool
- if key == QtCore.Qt.Key_N or key == 'N':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.grb_editor.select_tool('region')
- return
-
- # Add Pad Tool
- if key == QtCore.Qt.Key_P or key == 'P':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.inform.emit(_("Click on target point."))
- self.app.ui.add_pad_ar_btn.setChecked(True)
-
- self.app.grb_editor.x = self.app.mouse[0]
- self.app.grb_editor.y = self.app.mouse[1]
-
- self.app.grb_editor.select_tool('pad')
- return
-
- # Scale Tool
- if key == QtCore.Qt.Key_S or key == 'S':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.grb_editor.select_tool('scale')
- return
-
- # Add Track
- if key == QtCore.Qt.Key_T or key == 'T':
- self.app.grb_editor.launched_from_shortcuts = True
- ## Current application units in Upper Case
- self.app.grb_editor.select_tool('track')
- return
-
- # Zoom Fit
- if key == QtCore.Qt.Key_V or key == 'V':
- self.app.grb_editor.launched_from_shortcuts = True
- self.app.on_zoom_fit(None)
- return
-
- # Propagate to tool
- response = None
- if self.app.grb_editor.active_tool is not None:
+ # we do this so we can reuse the following keys while inside a Tool
+ # the above keys are general enough so were left outside
+ if self.app.grb_editor.active_tool is not None and self.grb_select_btn.isChecked() == False:
response = self.app.grb_editor.active_tool.on_key(key=key)
- if response is not None:
- self.app.inform.emit(response)
+ if response is not None:
+ self.app.inform.emit(response)
+ else:
+ # Add Array of pads
+ if key == QtCore.Qt.Key_A or key == 'A':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.inform.emit("Click on target point.")
+ self.app.ui.add_pad_ar_btn.setChecked(True)
+
+ self.app.grb_editor.x = self.app.mouse[0]
+ self.app.grb_editor.y = self.app.mouse[1]
+
+ self.app.grb_editor.select_tool('array')
+ return
+
+ # Scale Tool
+ if key == QtCore.Qt.Key_B or key == 'B':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.grb_editor.select_tool('buffer')
+ return
+
+ # Copy
+ if key == QtCore.Qt.Key_C or key == 'C':
+ self.app.grb_editor.launched_from_shortcuts = True
+ if self.app.grb_editor.selected:
+ self.app.inform.emit(_("Click on target point."))
+ self.app.ui.aperture_copy_btn.setChecked(True)
+ self.app.grb_editor.on_tool_select('copy')
+ self.app.grb_editor.active_tool.set_origin(
+ (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
+ else:
+ self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy."))
+ return
+
+ # Grid Snap
+ if key == QtCore.Qt.Key_G or key == 'G':
+ self.app.grb_editor.launched_from_shortcuts = True
+ # make sure that the cursor shape is enabled/disabled, too
+ if self.app.grb_editor.options['grid_snap'] is True:
+ self.app.app_cursor.enabled = False
+ else:
+ self.app.app_cursor.enabled = True
+ self.app.ui.grid_snap_btn.trigger()
+ return
+
+ # Jump to coords
+ if key == QtCore.Qt.Key_J or key == 'J':
+ self.app.on_jump_to()
+
+ # Corner Snap
+ if key == QtCore.Qt.Key_K or key == 'K':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.ui.corner_snap_btn.trigger()
+ return
+
+ # Move
+ if key == QtCore.Qt.Key_M or key == 'M':
+ self.app.grb_editor.launched_from_shortcuts = True
+ if self.app.grb_editor.selected:
+ self.app.inform.emit(_("Click on target point."))
+ self.app.ui.aperture_move_btn.setChecked(True)
+ self.app.grb_editor.on_tool_select('move')
+ self.app.grb_editor.active_tool.set_origin(
+ (self.app.grb_editor.snap_x, self.app.grb_editor.snap_y))
+ else:
+ self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move."))
+ return
+
+ # Add Region Tool
+ if key == QtCore.Qt.Key_N or key == 'N':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.grb_editor.select_tool('region')
+ return
+
+ # Add Pad Tool
+ if key == QtCore.Qt.Key_P or key == 'P':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.inform.emit(_("Click on target point."))
+ self.app.ui.add_pad_ar_btn.setChecked(True)
+
+ self.app.grb_editor.x = self.app.mouse[0]
+ self.app.grb_editor.y = self.app.mouse[1]
+
+ self.app.grb_editor.select_tool('pad')
+ return
+
+ # Scale Tool
+ if key == QtCore.Qt.Key_S or key == 'S':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.grb_editor.select_tool('scale')
+ return
+
+ # Add Track
+ if key == QtCore.Qt.Key_T or key == 'T':
+ self.app.grb_editor.launched_from_shortcuts = True
+ ## Current application units in Upper Case
+ self.app.grb_editor.select_tool('track')
+ return
+
+ # Zoom Fit
+ if key == QtCore.Qt.Key_V or key == 'V':
+ self.app.grb_editor.launched_from_shortcuts = True
+ self.app.on_zoom_fit(None)
+ return
# Show Shortcut list
if key == QtCore.Qt.Key_F3 or key == 'F3':
From e92cab2e96030a5465cb9905aa689e15c8563908 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 02:46:55 +0300
Subject: [PATCH 05/42] - In Geometry Editor I fixed bug in Arc modes. Arc mode
shortcut key is now key 'M' and arc direction change shortcut key is 'D'
---
README.md | 1 +
camlib.py | 2 +-
flatcamEditors/FlatCAMGeoEditor.py | 19 ++++++++++++++++---
3 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 4bba0199..d16f0015 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: a disabled/enabled sequence for the VisPy cursor on Gerber edit make the graphics better
- Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden
- Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated
+- In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D'
13.04.2019
diff --git a/camlib.py b/camlib.py
index b3dffb83..20f88c89 100644
--- a/camlib.py
+++ b/camlib.py
@@ -7589,7 +7589,7 @@ def three_point_circle(p1, p2, p3):
center = a1 + b1 * T[0]
# Radius
- radius = norm(center - p1)
+ radius = np.linalg.norm(center - p1)
return center, radius, T[0]
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index 987c22a9..46ffc961 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -22,6 +22,7 @@ from shapely.ops import cascaded_union
import shapely.affinity as affinity
from numpy import arctan2, Inf, array, sqrt, sign, dot
+from numpy.linalg import norm
from rtree import index as rtindex
from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
@@ -1989,11 +1990,21 @@ class FCArc(FCShapeTool):
self.points.append(point)
if len(self.points) == 1:
- self.draw_app.app.inform.emit(_("Click on Start arc point ..."))
+ if self.mode == 'c12':
+ self.draw_app.app.inform.emit(_("Click on Start point ..."))
+ elif self.mode == '132':
+ self.draw_app.app.inform.emit(_("Click on Point3 ..."))
+ else:
+ self.draw_app.app.inform.emit(_("Click on Stop point to complete ..."))
return "Click on 1st point ..."
if len(self.points) == 2:
- self.draw_app.app.inform.emit(_("Click on End arc point to complete ..."))
+ if self.mode == 'c12':
+ self.draw_app.app.inform.emit(_("Click on Stop point to complete ..."))
+ elif self.mode == '132':
+ self.draw_app.app.inform.emit(_("Click on Point2 ..."))
+ else:
+ self.draw_app.app.inform.emit(_("Click on Center point to complete ..."))
return "Click on 2nd point to complete ..."
if len(self.points) == 3:
@@ -2010,11 +2021,13 @@ class FCArc(FCShapeTool):
if key == 'M' or key == QtCore.Qt.Key_M:
if self.mode == 'c12':
self.mode = '12c'
+ return _('Mode: Start -> Stop -> Center. Click on Start point ...')
elif self.mode == '12c':
self.mode = '132'
+ return _('Mode: Point1 -> Point3 -> Point2. Click on 1st point ...')
else:
self.mode = 'c12'
- return _('Mode: %s') % self.mode
+ return _('Mode: Center -> Start -> Stop. Click on Center ...')
def utility_geometry(self, data=None):
if len(self.points) == 1: # Show the radius
From 1332601624055d80f6e823ebe35840e86e686cf6 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 15:16:37 +0300
Subject: [PATCH 06/42] - moved the key handler out of the Measurement tool to
flatcamGUI.FlatCAMGui.keyPressEvent() - Gerber Editor: started to add new
function of poligonize which should make a filled polygon out of a shape
---
README.md | 2 +
camlib.py | 61 +++++++++++++++++++++++-
flatcamEditors/FlatCAMExcEditor.py | 6 +--
flatcamEditors/FlatCAMGeoEditor.py | 6 +--
flatcamEditors/FlatCAMGrbEditor.py | 75 ++++++++++++++++++++++++++++--
flatcamGUI/FlatCAMGUI.py | 20 ++++++++
flatcamTools/ToolMeasurement.py | 68 ++++++++++-----------------
7 files changed, 185 insertions(+), 53 deletions(-)
diff --git a/README.md b/README.md
index d16f0015..e70c1538 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ CAD program, and create G-Code for Isolation routing.
- Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden
- Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated
- In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D'
+- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent()
+- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape
13.04.2019
diff --git a/camlib.py b/camlib.py
index 20f88c89..ec598154 100644
--- a/camlib.py
+++ b/camlib.py
@@ -26,10 +26,11 @@ from rtree import index as rtindex
from lxml import etree as ET
# See: http://toblerity.org/shapely/manual.html
+
from shapely.geometry import Polygon, LineString, Point, LinearRing, MultiLineString
from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
-from shapely.ops import cascaded_union, unary_union
+from shapely.ops import cascaded_union, unary_union, polygonize
import shapely.affinity as affinity
from shapely.wkt import loads as sloads
from shapely.wkt import dumps as sdumps
@@ -45,6 +46,7 @@ import ezdxf
# TODO: Commented for FlatCAM packaging with cx_freeze
# from scipy.spatial import KDTree, Delaunay
+from scipy.spatial import Delaunay
from flatcamParsers.ParseSVG import *
from flatcamParsers.ParseDXF import *
@@ -7348,6 +7350,63 @@ def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
return ret_val
+def alpha_shape(points, alpha):
+ """
+ Compute the alpha shape (concave hull) of a set of points.
+
+ @param points: Iterable container of points.
+ @param alpha: alpha value to influence the gooeyness of the border. Smaller
+ numbers don't fall inward as much as larger numbers. Too large,
+ and you lose everything!
+ """
+ if len(points) < 4:
+ # When you have a triangle, there is no sense in computing an alpha
+ # shape.
+ return MultiPoint(list(points)).convex_hull
+
+ def add_edge(edges, edge_points, coords, i, j):
+ """Add a line between the i-th and j-th points, if not in the list already"""
+ if (i, j) in edges or (j, i) in edges:
+ # already added
+ return
+ edges.add( (i, j) )
+ edge_points.append(coords[ [i, j] ])
+
+ coords = np.array([point.coords[0] for point in points])
+
+ tri = Delaunay(coords)
+ edges = set()
+ edge_points = []
+ # loop over triangles:
+ # ia, ib, ic = indices of corner points of the triangle
+ for ia, ib, ic in tri.vertices:
+ pa = coords[ia]
+ pb = coords[ib]
+ pc = coords[ic]
+
+ # Lengths of sides of triangle
+ a = math.sqrt((pa[0]-pb[0])**2 + (pa[1]-pb[1])**2)
+ b = math.sqrt((pb[0]-pc[0])**2 + (pb[1]-pc[1])**2)
+ c = math.sqrt((pc[0]-pa[0])**2 + (pc[1]-pa[1])**2)
+
+ # Semiperimeter of triangle
+ s = (a + b + c)/2.0
+
+ # Area of triangle by Heron's formula
+ area = math.sqrt(s*(s-a)*(s-b)*(s-c))
+ circum_r = a*b*c/(4.0*area)
+
+ # Here's the radius filter.
+ #print circum_r
+ if circum_r < 1.0/alpha:
+ add_edge(edges, edge_points, coords, ia, ib)
+ add_edge(edges, edge_points, coords, ib, ic)
+ add_edge(edges, edge_points, coords, ic, ia)
+
+ m = MultiLineString(edge_points)
+ triangles = list(polygonize(m))
+ return cascaded_union(triangles), edge_points
+
# def voronoi(P):
# """
# Returns a list of all edges of the voronoi diagram for the given input points.
diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py
index 5dc5c731..2f02b7c6 100644
--- a/flatcamEditors/FlatCAMExcEditor.py
+++ b/flatcamEditors/FlatCAMExcEditor.py
@@ -1683,12 +1683,12 @@ class FlatCAMExcEditor(QtCore.QObject):
self.canvas.vis_connect('mouse_press', self.on_canvas_click)
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.on_exc_click_release)
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
# we restore the key and mouse control to FlatCAMApp method
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
@@ -2136,7 +2136,7 @@ class FlatCAMExcEditor(QtCore.QObject):
else:
self.storage.insert(shape) # TODO: Check performance
- def on_canvas_click_release(self, event):
+ def on_exc_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index 46ffc961..add13229 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -2973,13 +2973,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.canvas.vis_connect('mouse_press', self.on_canvas_click)
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.on_geo_click_release)
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
# we restore the key and mouse control to FlatCAMApp method
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
@@ -3310,7 +3310,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
- def on_canvas_click_release(self, event):
+ def on_geo_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 58d78d3a..16d7b5e2 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -476,6 +476,73 @@ class FCPadArray(FCShapeTool):
self.draw_app.plot_all()
+class FCPoligonize(FCShapeTool):
+ """
+ Resulting type: Polygon
+ """
+
+ def __init__(self, draw_app):
+ DrawTool.__init__(self, draw_app)
+ self.name = 'poligonize'
+ self.draw_app = draw_app
+
+ self.start_msg = _("Select shape(s) and then click ...")
+ self.draw_app.in_action = True
+ self.make()
+
+ def click(self, point):
+ # self.draw_app.in_action = True
+ # if self.draw_app.selected:
+ # self.make()
+ # else:
+ # self.draw_app.app.inform.emit(_("[WARNING_NOTCL] No shapes are selected. Select shapes and try again ..."))
+
+ return ""
+
+ def make(self):
+ geo = []
+
+ for shape in self.draw_app.selected:
+ current_storage = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
+ print(self.draw_app.active_tool)
+ aha = []
+ if shape.geo:
+ shape_points = list(shape.geo.exterior.coords)
+ for pt in shape_points:
+ aha.append(Point(pt))
+ concave_hull, bla_bla = alpha_shape(points=aha, alpha=0.5)
+ geo.append(concave_hull)
+ print(geo)
+ self.geometry = DrawToolShape(geo)
+ self.draw_app.on_grb_shape_complete(current_storage)
+
+ self.draw_app.in_action = False
+ self.complete = True
+ self.draw_app.app.inform.emit(_("[success] Done. Poligonize completed."))
+
+ self.draw_app.build_ui()
+ # MS: always return to the Select Tool if modifier key is not pressed
+ # else return to the current tool
+
+ key_modifier = QtWidgets.QApplication.keyboardModifiers()
+ if self.draw_app.app.defaults["global_mselect_key"] == 'Control':
+ modifier_to_use = Qt.ControlModifier
+ else:
+ modifier_to_use = Qt.ShiftModifier
+ # if modifier key is pressed then we add to the selected list the current shape but if it's already
+ # in the selected list, we removed it. Therefore first click selects, second deselects.
+ if key_modifier == modifier_to_use:
+ self.draw_app.select_tool(self.draw_app.active_tool.name)
+ else:
+ self.draw_app.select_tool("select")
+ return
+
+ def clean_up(self):
+ self.draw_app.selected = []
+ self.draw_app.apertures_table.clearSelection()
+ self.draw_app.plot_all()
+
+
class FCRegion(FCShapeTool):
"""
Resulting type: Polygon
@@ -1259,6 +1326,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
"constructor": FCTrack},
"region": {"button": self.app.ui.grb_add_region_btn,
"constructor": FCRegion},
+ "poligonize": {"button": self.app.ui.grb_convert_poly_btn,
+ "constructor": FCPoligonize},
"buffer": {"button": self.app.ui.aperture_buffer_btn,
"constructor": FCBuffer},
"scale": {"button": self.app.ui.aperture_scale_btn,
@@ -1918,12 +1987,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.canvas.vis_connect('mouse_press', self.on_canvas_click)
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.on_grb_click_release)
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
+ self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
# we restore the key and mouse control to FlatCAMApp method
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
@@ -2338,7 +2407,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
else:
self.app.log.debug("No active tool to respond to click!")
- def on_canvas_click_release(self, event):
+ def on_grb_click_release(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index 8e4c038d..583aa31a 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -690,6 +690,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.add_pad_ar_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/padarray32.png'), _('Add Pad Array'))
self.grb_add_track_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/track32.png'), _("Add Track"))
self.grb_add_region_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Add Region"))
+ self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Poligonize"))
+
self.grb_edit_toolbar.addSeparator()
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
@@ -2911,6 +2913,24 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_F3 or key == 'F3':
self.app.on_shortcut_list()
return
+ elif self.app.call_source == 'measurement':
+ if modifiers == QtCore.Qt.ControlModifier:
+ pass
+ elif modifiers == QtCore.Qt.AltModifier:
+ pass
+ elif modifiers == QtCore.Qt.ShiftModifier:
+ pass
+ elif modifiers == QtCore.Qt.NoModifier:
+ if key == QtCore.Qt.Key_Escape or key == 'Escape':
+ # abort the measurement action
+ self.app.measurement_tool.on_measure(activate=False)
+ self.app.measurement_tool.deactivate_measure_tool()
+ self.app.inform.emit(_("Measurement Tool exit..."))
+ return
+
+ if key == QtCore.Qt.Key_G or key == 'G':
+ self.app.ui.grid_snap_btn.trigger()
+ return
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
diff --git a/flatcamTools/ToolMeasurement.py b/flatcamTools/ToolMeasurement.py
index 6cf0fded..12f68bdd 100644
--- a/flatcamTools/ToolMeasurement.py
+++ b/flatcamTools/ToolMeasurement.py
@@ -112,6 +112,8 @@ class Measurement(FlatCAMTool):
# self.setVisible(False)
self.active = 0
+ self.original_call_source = 'app'
+
# VisPy visuals
self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
@@ -125,7 +127,7 @@ class Measurement(FlatCAMTool):
self.app.ui.notebook.setTabText(2, _("Meas. Tool"))
- # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+ # if the splitter is hidden, display it
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
@@ -155,29 +157,22 @@ class Measurement(FlatCAMTool):
self.distance_y_entry.set_value('0')
self.total_distance_entry.set_value('0')
- def activate(self):
+ def activate_measure_tool(self):
# we disconnect the mouse/key handlers from wherever the measurement tool was called
- self.canvas.vis_disconnect('key_press')
self.canvas.vis_disconnect('mouse_move')
self.canvas.vis_disconnect('mouse_press')
self.canvas.vis_disconnect('mouse_release')
- self.canvas.vis_disconnect('key_release')
# we can safely connect the app mouse events to the measurement tool
self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas)
- self.canvas.vis_connect('mouse_release', self.on_mouse_click)
- self.canvas.vis_connect('key_release', self.on_key_release_meas)
+ self.canvas.vis_connect('mouse_release', self.on_mouse_click_release)
self.set_tool_ui()
- def deactivate(self):
+ def deactivate_measure_tool(self):
# disconnect the mouse/key events from functions of measurement tool
self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
- self.canvas.vis_disconnect('mouse_release', self.on_mouse_click)
- self.canvas.vis_disconnect('key_release', self.on_key_release_meas)
-
- # reconnect the mouse/key events to the functions from where the tool was called
- self.canvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+ self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
if self.app.call_source == 'app':
self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
@@ -186,57 +181,44 @@ class Measurement(FlatCAMTool):
elif self.app.call_source == 'geo_editor':
self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
- # self.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
- self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release)
elif self.app.call_source == 'exc_editor':
self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
- # self.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
- self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release)
elif self.app.call_source == 'grb_editor':
self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click)
- # self.canvas.vis_connect('key_press', self.app.grb_editor.on_canvas_key)
- self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_canvas_click_release)
+ self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_grb_click_release)
self.app.ui.notebook.setTabText(2, _("Tools"))
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
def on_measure(self, signal=None, activate=None):
log.debug("Measurement.on_measure()")
- if activate is False or activate is None:
- # DISABLE the Measuring TOOL
- self.deactivate()
+ if activate is True:
+ # ENABLE the Measuring TOOL
+ self.clicked_meas = 0
+ self.original_call_source = copy(self.app.call_source)
+ self.app.call_source = 'measurement'
+ self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
+ self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
+
+ self.activate_measure_tool()
+ log.debug("Measurement Tool --> tool initialized")
+ else:
+ # DISABLE the Measuring TOOL
+ self.deactivate_measure_tool()
+ self.app.call_source = copy(self.original_call_source)
self.app.command_active = None
# delete the measuring line
self.delete_shape()
log.debug("Measurement Tool --> exit tool")
- elif activate is True:
- # ENABLE the Measuring TOOL
- self.clicked_meas = 0
- self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
- self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
-
- self.activate()
- log.debug("Measurement Tool --> tool initialized")
-
- def on_key_release_meas(self, event):
- if event.key == 'escape':
- # abort the measurement action
- self.on_measure(activate=False)
- self.app.inform.emit(_("Measurement Tool exit..."))
- return
-
- if event.key == 'G':
- # toggle grid status
- self.app.ui.grid_snap_btn.trigger()
- return
-
- def on_mouse_click(self, event):
+ def on_mouse_click_release(self, event):
# mouse click releases will be accepted only if the left button is clicked
# this is necessary because right mouse click or middle mouse click
# are used for panning on the canvas
From fc1dfb855035328f46982736adf505513609b681 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 16:59:20 +0300
Subject: [PATCH 07/42] - cleaned up Measuring Tool
---
FlatCAMApp.py | 6 +-
README.md | 1 +
flatcamGUI/FlatCAMGUI.py | 1 -
flatcamTools/ToolMeasurement.py | 168 ++++++++++++++++----------------
4 files changed, 85 insertions(+), 91 deletions(-)
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 2787a485..0a4bdf55 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -4472,7 +4472,6 @@ class App(QtCore.QObject):
self.report_usage("on_set_origin()")
self.inform.emit(_('Click to set the origin ...'))
-
self.plotcanvas.vis_connect('mouse_press', self.on_set_zero_click)
def on_jump_to(self, custom_location=None, fit_center=True):
@@ -5117,9 +5116,7 @@ class App(QtCore.QObject):
def on_mouse_move_over_plot(self, event, origin_click=None):
"""
- Callback for the mouse motion event over the plot. This event is generated
- by the Matplotlib backend and has been registered in ``self.__init__()``.
- For details, see: http://matplotlib.org/users/event_handling.html
+ Callback for the mouse motion event over the plot.
:param event: Contains information about the event.
:param origin_click
@@ -5314,7 +5311,6 @@ class App(QtCore.QObject):
def select_objects(self, key=None):
# list where we store the overlapped objects under our mouse left click position
objects_under_the_click_list = []
-
# Populate the list with the overlapped objects on the click position
curr_x, curr_y = self.pos
for obj in self.all_objects_list:
diff --git a/README.md b/README.md
index e70c1538..adf9c96a 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ CAD program, and create G-Code for Isolation routing.
- In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D'
- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent()
- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape
+- cleaned up Measuring Tool
13.04.2019
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index 583aa31a..37914892 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -2923,7 +2923,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
elif modifiers == QtCore.Qt.NoModifier:
if key == QtCore.Qt.Key_Escape or key == 'Escape':
# abort the measurement action
- self.app.measurement_tool.on_measure(activate=False)
self.app.measurement_tool.deactivate_measure_tool()
self.app.inform.emit(_("Measurement Tool exit..."))
return
diff --git a/flatcamTools/ToolMeasurement.py b/flatcamTools/ToolMeasurement.py
index 12f68bdd..e8a4f96f 100644
--- a/flatcamTools/ToolMeasurement.py
+++ b/flatcamTools/ToolMeasurement.py
@@ -103,25 +103,23 @@ class Measurement(FlatCAMTool):
self.layout.addStretch()
- self.clicked_meas = 0
+ # store here the first click and second click of the measurement process
+ self.points = []
- self.point1 = None
- self.point2 = None
-
- # the default state is disabled for the Move command
- # self.setVisible(False)
- self.active = 0
+ self.active = False
self.original_call_source = 'app'
# VisPy visuals
self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
- self.measure_btn.clicked.connect(lambda: self.on_measure(activate=True))
+ self.measure_btn.clicked.connect(self.activate_measure_tool)
def run(self, toggle=False):
self.app.report_usage("ToolMeasurement()")
+ self.points[:] = []
+
if self.app.tool_tab_locked is True:
return
@@ -130,8 +128,13 @@ class Measurement(FlatCAMTool):
# if the splitter is hidden, display it
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
+ if toggle:
+ pass
- self.on_measure(activate=True)
+ if self.active is False:
+ self.activate_measure_tool()
+ else:
+ self.deactivate_measure_tool()
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
@@ -156,101 +159,104 @@ class Measurement(FlatCAMTool):
self.distance_x_entry.set_value('0')
self.distance_y_entry.set_value('0')
self.total_distance_entry.set_value('0')
+ log.debug("Measurement Tool --> tool initialized")
def activate_measure_tool(self):
- # we disconnect the mouse/key handlers from wherever the measurement tool was called
- self.canvas.vis_disconnect('mouse_move')
- self.canvas.vis_disconnect('mouse_press')
- self.canvas.vis_disconnect('mouse_release')
+ # ENABLE the Measuring TOOL
+ self.active = True
- # we can safely connect the app mouse events to the measurement tool
+ self.clicked_meas = 0
+ self.original_call_source = copy(self.app.call_source)
+
+ self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
+ self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
+
+ # we can connect the app mouse events to the measurement tool
+ # NEVER DISCONNECT THOSE before connecting some other handlers; it breaks something in VisPy
self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas)
self.canvas.vis_connect('mouse_release', self.on_mouse_click_release)
+ # we disconnect the mouse/key handlers from wherever the measurement tool was called
+ if self.app.call_source == 'app':
+ self.canvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+ self.canvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+ self.canvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+ elif self.app.call_source == 'geo_editor':
+ self.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
+ elif self.app.call_source == 'exc_editor':
+ self.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
+ elif self.app.call_source == 'grb_editor':
+ self.canvas.vis_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
+
+ self.app.call_source = 'measurement'
+
self.set_tool_ui()
def deactivate_measure_tool(self):
- # disconnect the mouse/key events from functions of measurement tool
- self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
- self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
+ # DISABLE the Measuring TOOL
+ self.active = False
+ self.points = []
- if self.app.call_source == 'app':
+ self.app.call_source = copy(self.original_call_source)
+ if self.original_call_source == 'app':
self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
- elif self.app.call_source == 'geo_editor':
+ elif self.original_call_source == 'geo_editor':
self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release)
- elif self.app.call_source == 'exc_editor':
+ elif self.original_call_source == 'exc_editor':
self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release)
- elif self.app.call_source == 'grb_editor':
+ elif self.original_call_source == 'grb_editor':
self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move)
self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click)
self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_grb_click_release)
- self.app.ui.notebook.setTabText(2, _("Tools"))
- self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+ # disconnect the mouse/key events from functions of measurement tool
+ self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
+ self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
- def on_measure(self, signal=None, activate=None):
- log.debug("Measurement.on_measure()")
- if activate is True:
- # ENABLE the Measuring TOOL
- self.clicked_meas = 0
- self.original_call_source = copy(self.app.call_source)
- self.app.call_source = 'measurement'
+ # self.app.ui.notebook.setTabText(2, _("Tools"))
+ # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
- self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
- self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
+ self.app.command_active = None
- self.activate_measure_tool()
- log.debug("Measurement Tool --> tool initialized")
- else:
- # DISABLE the Measuring TOOL
- self.deactivate_measure_tool()
- self.app.call_source = copy(self.original_call_source)
- self.app.command_active = None
+ # delete the measuring line
+ self.delete_shape()
- # delete the measuring line
- self.delete_shape()
-
- log.debug("Measurement Tool --> exit tool")
+ log.debug("Measurement Tool --> exit tool")
def on_mouse_click_release(self, event):
# mouse click releases will be accepted only if the left button is clicked
# this is necessary because right mouse click or middle mouse click
# are used for panning on the canvas
+ log.debug("Measuring Tool --> mouse click release")
if event.button == 1:
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+ # if GRID is active we need to get the snapped positions
+ if self.app.grid_status() == True:
+ pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
+ else:
+ pos = pos_canvas[0], pos_canvas[1]
+ self.points.append(pos)
- if self.clicked_meas == 0:
- self.clicked_meas = 1
-
- # if GRID is active we need to get the snapped positions
- if self.app.grid_status() == True:
- pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- else:
- pos = pos_canvas[0], pos_canvas[1]
-
- self.point1 = pos
+ if len(self.points) == 1:
self.start_entry.set_value("(%.4f, %.4f)" % pos)
self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
- else:
- # delete the selection bounding box
- self.delete_shape()
-
- # if GRID is active we need to get the snapped positions
- if self.app.grid_status() is True:
- pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- else:
- pos = pos_canvas[0], pos_canvas[1]
-
- dx = pos[0] - self.point1[0]
- dy = pos[1] - self.point1[1]
+ if len(self.points) == 2:
+ dx = self.points[1][0] - self.points[0][0]
+ dy = self.points[1][1] - self.points[0][1]
d = sqrt(dx ** 2 + dy ** 2)
self.stop_entry.set_value("(%.4f, %.4f)" % pos)
@@ -262,33 +268,25 @@ class Measurement(FlatCAMTool):
self.distance_y_entry.set_value('%.4f' % abs(dy))
self.total_distance_entry.set_value('%.4f' % abs(d))
- # TODO: I don't understand why I have to do it twice ... but without it the mouse handlers are
- # TODO: are left disconnected ...
- self.on_measure(activate=False)
- self.deactivate()
+ self.deactivate_measure_tool()
def on_mouse_move_meas(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
-
- # if GRID is active we need to get the snapped positions
+ # Update cursor
+ pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
if self.app.grid_status() == True:
- pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
- self.app.app_cursor.enabled = True
- # Update cursor
self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]), symbol='++', edge_color='black', size=20)
- else:
- pos = pos_canvas
- self.app.app_enabled = False
-
- self.point2 = (pos[0], pos[1])
# update utility geometry
- if self.clicked_meas == 1:
- # first delete old shape
- self.delete_shape()
- # second draw the new shape of the utility geometry
- self.meas_line = LineString([self.point2, self.point1])
- self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
+ if len(self.points) == 1:
+ self.utility_geometry(pos=pos)
+
+ def utility_geometry(self, pos):
+ # first delete old shape
+ self.delete_shape()
+ # second draw the new shape of the utility geometry
+ self.meas_line = LineString([pos, self.points[0]])
+ self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
def delete_shape(self):
self.sel_shapes.clear()
From 2e7d9f953f306f40e0c8d8ee32583b6a55ae5866 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Sun, 14 Apr 2019 22:46:08 +0300
Subject: [PATCH 08/42] - solved bug in Gerber apertures size and dimensions
values conversion when file units are different than app units
---
README.md | 1 +
camlib.py | 14 +++++++++++++-
flatcamEditors/FlatCAMGrbEditor.py | 11 +++++------
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index adf9c96a..80f9c49d 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ CAD program, and create G-Code for Isolation routing.
- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent()
- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape
- cleaned up Measuring Tool
+- solved bug in Gerber apertures size and dimensions values conversion when file units are different than app units
13.04.2019
diff --git a/camlib.py b/camlib.py
index ec598154..6f46b161 100644
--- a/camlib.py
+++ b/camlib.py
@@ -2162,6 +2162,9 @@ class Gerber (Geometry):
# Coordinates of the current path, each is [x, y]
path = []
+ # store the file units here:
+ gerber_units = 'IN'
+
# this is for temporary storage of geometry until it is added to poly_buffer
geo = None
@@ -3180,6 +3183,13 @@ class Gerber (Geometry):
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
+ # TODO: make sure to keep track of units changes because right now it seems to happen in a weird way
+ # find out the conversion factor used to convert inside the self.apertures keys: size, width, height
+ file_units = gerber_units if gerber_units else 'IN'
+ app_units = self.app.defaults['units']
+
+ conversion_factor = 25.4 if file_units == 'IN' else (1/25.4) if file_units != app_units else 1
+
# first check if we have any clear_geometry (LPC) and if yes then we need to substract it
# from the apertures solid_geometry
temp_geo = []
@@ -3195,7 +3205,9 @@ class Gerber (Geometry):
self.apertures[apid]['solid_geometry'] = deepcopy(temp_geo)
self.apertures[apid].pop('clear_geometry', None)
-
+ for k, v in self.apertures[apid].items():
+ if k == 'size' or k == 'width' or k == 'height':
+ self.apertures[apid][k] = v * conversion_factor
# --- Apply buffer ---
# this treats the case when we are storing geometry as paths
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 16d7b5e2..9770f25b 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -1570,15 +1570,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
if str(self.storage_dict[ap_code]['type']) == 'R' or str(self.storage_dict[ap_code]['type']) == 'O':
ap_dim_item = QtWidgets.QTableWidgetItem(
- '%.4f, %.4f' % (self.storage_dict[ap_code]['width'] * self.gerber_obj.file_units_factor,
- self.storage_dict[ap_code]['height'] * self.gerber_obj.file_units_factor
+ '%.4f, %.4f' % (self.storage_dict[ap_code]['width'],
+ self.storage_dict[ap_code]['height']
)
)
ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
elif str(self.storage_dict[ap_code]['type']) == 'P':
ap_dim_item = QtWidgets.QTableWidgetItem(
- '%.4f, %.4f' % (self.storage_dict[ap_code]['diam'] * self.gerber_obj.file_units_factor,
- self.storage_dict[ap_code]['nVertices'] * self.gerber_obj.file_units_factor)
+ '%.4f, %.4f' % (self.storage_dict[ap_code]['diam'],
+ self.storage_dict[ap_code]['nVertices'])
)
ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
else:
@@ -1588,8 +1588,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
try:
if self.storage_dict[ap_code]['size'] is not None:
ap_size_item = QtWidgets.QTableWidgetItem('%.4f' %
- float(self.storage_dict[ap_code]['size'] *
- self.gerber_obj.file_units_factor))
+ float(self.storage_dict[ap_code]['size']))
else:
ap_size_item = QtWidgets.QTableWidgetItem('')
except KeyError:
From db26895b5bde2d351ad5b315a23cb6c6b581ff51 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Mon, 15 Apr 2019 03:29:43 +0300
Subject: [PATCH 09/42] - working on a new tool to process automatically
PcbWizard Excellon files which are generated in 2 files
---
FlatCAMApp.py | 5 +-
README.md | 4 +
camlib.py | 14 +-
flatcamGUI/GUIElements.py | 5 +-
flatcamTools/ToolPcbWizard.py | 415 ++++++++++++++++++++++++++++++++++
flatcamTools/__init__.py | 1 +
6 files changed, 437 insertions(+), 7 deletions(-)
create mode 100644 flatcamTools/ToolPcbWizard.py
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 0a4bdf55..f567029b 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -2006,6 +2006,9 @@ class App(QtCore.QObject):
self.image_tool = ToolImage(self)
self.image_tool.install(icon=QtGui.QIcon('share/image32.png'), pos=self.ui.menufileimport,
separator=True)
+ self.pcb_wizard_tool = PcbWizard(self)
+ self.pcb_wizard_tool.install(icon=QtGui.QIcon('share/drill32.png'), pos=self.ui.menufileimport,
+ separator=True)
self.log.debug("Tools are installed.")
@@ -7081,7 +7084,7 @@ class App(QtCore.QObject):
# self.progress.emit(20)
try:
- ret = excellon_obj.parse_file(filename)
+ ret = excellon_obj.parse_file(filename=filename)
if ret == "fail":
log.debug("Excellon parsing failed.")
self.inform.emit(_("[ERROR_NOTCL] This is not Excellon file."))
diff --git a/README.md b/README.md
index 80f9c49d..08e0ee9d 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+15.04.2019
+
+- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
+
14.04.2019
- Gerber Editor: Remade the processing of 'clear_geometry' (geometry generated by polygons made with Gerber LPC command) to work if more than one such polygon exists
diff --git a/camlib.py b/camlib.py
index 6f46b161..7676bee1 100644
--- a/camlib.py
+++ b/camlib.py
@@ -3837,7 +3837,7 @@ class Excellon(Geometry):
# Repeating command
self.repeat_re = re.compile(r'R(\d+)')
- def parse_file(self, filename):
+ def parse_file(self, filename=None, file_obj=None):
"""
Reads the specified file as array of lines as
passes it to ``parse_lines()``.
@@ -3846,9 +3846,15 @@ class Excellon(Geometry):
:type filename: str
:return: None
"""
- efile = open(filename, 'r')
- estr = efile.readlines()
- efile.close()
+ if file_obj:
+ estr = file_obj
+ else:
+ if filename is None:
+ return "fail"
+ efile = open(filename, 'r')
+ estr = efile.readlines()
+ efile.close()
+
try:
self.parse_lines(estr)
except:
diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py
index e30a2f9b..c7b724f5 100644
--- a/flatcamGUI/GUIElements.py
+++ b/flatcamGUI/GUIElements.py
@@ -27,7 +27,7 @@ EDIT_SIZE_HINT = 70
class RadioSet(QtWidgets.QWidget):
- activated_custom = QtCore.pyqtSignal()
+ activated_custom = QtCore.pyqtSignal(str)
def __init__(self, choices, orientation='horizontal', parent=None, stretch=None):
"""
@@ -72,7 +72,8 @@ class RadioSet(QtWidgets.QWidget):
radio = self.sender()
if radio.isChecked():
self.group_toggle_fn()
- self.activated_custom.emit()
+ ret_val = str(self.get_value())
+ self.activated_custom.emit(ret_val)
return
def get_value(self):
diff --git a/flatcamTools/ToolPcbWizard.py b/flatcamTools/ToolPcbWizard.py
new file mode 100644
index 00000000..d990140d
--- /dev/null
+++ b/flatcamTools/ToolPcbWizard.py
@@ -0,0 +1,415 @@
+############################################################
+# FlatCAM: 2D Post-processing for Manufacturing #
+# http://flatcam.org #
+# File Author: Marius Adrian Stanciu (c) #
+# Date: 4/15/2019 #
+# MIT Licence #
+############################################################
+
+from FlatCAMTool import FlatCAMTool
+
+from flatcamGUI.GUIElements import RadioSet, FCComboBox, FCSpinner, FCButton, FCTable
+from PyQt5 import QtGui, QtWidgets, QtCore
+from PyQt5.QtCore import pyqtSignal
+import re
+import os
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+
+fcTranslate.apply_language('strings')
+import builtins
+if '_' not in builtins.__dict__:
+ _ = gettext.gettext
+
+
+class PcbWizard(FlatCAMTool):
+
+ file_loaded = pyqtSignal(str, str)
+
+ toolName = _("PcbWizard Import Tool")
+
+ def __init__(self, app):
+ FlatCAMTool.__init__(self, app)
+
+ self.app = app
+
+ # Title
+ title_label = QtWidgets.QLabel("%s" % _('Import 2-file Excellon'))
+ title_label.setStyleSheet("""
+ QLabel
+ {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ """)
+ self.layout.addWidget(title_label)
+
+ self.layout.addWidget(QtWidgets.QLabel(""))
+ self.layout.addWidget(QtWidgets.QLabel("Load files:"))
+
+ # Form Layout
+ form_layout = QtWidgets.QFormLayout()
+ self.layout.addLayout(form_layout)
+
+ self.excellon_label = QtWidgets.QLabel(_("Excellon file:"))
+ self.excellon_label.setToolTip(
+ _( "Load the Excellon file.\n"
+ "Usually it has a .DRL extension")
+
+ )
+ self.excellon_brn = FCButton(_("Open"))
+ form_layout.addRow(self.excellon_label, self.excellon_brn)
+
+ self.inf_label = QtWidgets.QLabel(_("INF file:"))
+ self.inf_label.setToolTip(
+ _("Load the INF file.")
+
+ )
+ self.inf_btn = FCButton(_("Open"))
+ form_layout.addRow(self.inf_label, self.inf_btn)
+
+ self.tools_table = FCTable()
+ self.layout.addWidget(self.tools_table)
+
+ self.tools_table.setColumnCount(2)
+ self.tools_table.setHorizontalHeaderLabels(['#Tool', _('Diameter')])
+
+ self.tools_table.horizontalHeaderItem(0).setToolTip(
+ _("Tool Number"))
+ self.tools_table.horizontalHeaderItem(1).setToolTip(
+ _("Tool diameter in file units."))
+
+ # start with apertures table hidden
+ self.tools_table.setVisible(False)
+
+ self.layout.addWidget(QtWidgets.QLabel(""))
+ self.layout.addWidget(QtWidgets.QLabel("Excellon format:"))
+ # Form Layout
+ form_layout1 = QtWidgets.QFormLayout()
+ self.layout.addLayout(form_layout1)
+
+ # Integral part of the coordinates
+ self.int_entry = FCSpinner()
+ self.int_entry.set_range(1, 10)
+ self.int_label = QtWidgets.QLabel(_("Int. digits:"))
+ self.int_label.setToolTip(
+ _( "The number of digits for the integral part of the coordinates.")
+ )
+ form_layout1.addRow(self.int_label, self.int_entry)
+
+ # Fractional part of the coordinates
+ self.frac_entry = FCSpinner()
+ self.frac_entry.set_range(1, 10)
+ self.frac_label = QtWidgets.QLabel(_("Frac. digits:"))
+ self.frac_label.setToolTip(
+ _("The number of digits for the fractional part of the coordinates.")
+ )
+ form_layout1.addRow(self.frac_label, self.frac_entry)
+
+ # Zeros suppression for coordinates
+ self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'},
+ {'label': 'TZ', 'value': 'T'},
+ {'label': 'No Suppression', 'value': 'D'}])
+ self.zeros_label = QtWidgets.QLabel(_("Zeros supp.:"))
+ self.zeros_label.setToolTip(
+ _("The type of zeros suppression used.\n"
+ "Can be of type:\n"
+ "- LZ = leading zeros are kept\n"
+ "- TZ = trailing zeros are kept\n"
+ "- No Suppression = no zero suppression")
+ )
+ form_layout1.addRow(self.zeros_label, self.zeros_radio)
+
+ # Units type
+ self.units_radio = RadioSet([{'label': 'INCH', 'value': 'INCH'},
+ {'label': 'MM', 'value': 'METRIC'}])
+ self.units_label = QtWidgets.QLabel("%s:" % _('Units'))
+ self.units_label.setToolTip(
+ _("The type of units that the coordinates and tool\n"
+ "diameters are using. Can be INCH or MM.")
+ )
+ form_layout1.addRow(self.units_label, self.units_radio)
+
+ # Buttons
+
+ self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
+ self.import_button.setToolTip(
+ _("Import in FlatCAM an Excellon file\n"
+ "that store it's information's in 2 files.\n"
+ "One usually has .DRL extension while\n"
+ "the other has .INF extension.")
+ )
+ self.layout.addWidget(self.import_button)
+
+ self.layout.addStretch()
+
+ self.excellon_loaded = False
+ self.inf_loaded = False
+ self.process_finished = False
+
+ ## Signals
+ self.excellon_brn.clicked.connect(self.on_load_excellon_click)
+ self.inf_btn.clicked.connect(self.on_load_inf_click)
+ self.import_button.clicked.connect(self.on_import_excellon)
+ self.file_loaded.connect(self.on_file_loaded)
+ self.units_radio.activated_custom.connect(self.on_units_change)
+
+ self.units = 'INCH'
+ self.zeros = 'L'
+ self.integral = 2
+ self.fractional = 4
+
+ self.outname = 'file'
+
+ self.exc_file_content = None
+ self.tools_from_inf = {}
+
+ def run(self, toggle=False):
+ self.app.report_usage("PcbWizard Tool()")
+
+ if toggle:
+ # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+ if self.app.ui.splitter.sizes()[0] == 0:
+ self.app.ui.splitter.setSizes([1, 1])
+ else:
+ try:
+ if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+ self.app.ui.splitter.setSizes([0, 1])
+ except AttributeError:
+ pass
+ else:
+ if self.app.ui.splitter.sizes()[0] == 0:
+ self.app.ui.splitter.setSizes([1, 1])
+
+ FlatCAMTool.run(self)
+ self.set_tool_ui()
+
+ self.app.ui.notebook.setTabText(2, _("PCBWizard Tool"))
+
+ def install(self, icon=None, separator=None, **kwargs):
+ FlatCAMTool.install(self, icon, separator, **kwargs)
+
+ def set_tool_ui(self):
+ ## Initialize form
+ self.int_entry.set_value(self.integral)
+ self.frac_entry.set_value(self.fractional)
+ self.zeros_radio.set_value(self.zeros)
+ self.units_radio.set_value(self.units)
+
+ self.excellon_loaded = False
+ self.inf_loaded = False
+ self.process_finished = False
+
+ self.build_ui()
+
+ def build_ui(self):
+ sorted_tools = []
+
+ if not self.tools_from_inf:
+ self.tools_table.setRowCount(1)
+ else:
+ sort = []
+ for k, v in list(self.tools_from_inf.items()):
+ sort.append(int(k))
+ sorted_tools = sorted(sort)
+ n = len(sorted_tools)
+ self.tools_table.setRowCount(n)
+
+ tool_row = 0
+ for tool in sorted_tools:
+ tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool))
+ tool_id_item.setFlags(QtCore.Qt.ItemIsEnabled)
+ self.tools_table.setItem(tool_row, 0, tool_id_item) # Tool name/id
+
+ tool_dia_item = QtWidgets.QTableWidgetItem(str(self.tools_from_inf[tool]))
+ tool_dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
+ self.tools_table.setItem(tool_row, 1, tool_dia_item)
+ tool_row += 1
+
+ self.tools_table.resizeColumnsToContents()
+ self.tools_table.resizeRowsToContents()
+
+ vertical_header = self.tools_table.verticalHeader()
+ vertical_header.hide()
+ self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+ horizontal_header = self.tools_table.horizontalHeader()
+ # horizontal_header.setMinimumSectionSize(10)
+ # horizontal_header.setDefaultSectionSize(70)
+ horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
+ horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+
+ self.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.tools_table.setSortingEnabled(False)
+ self.tools_table.setMinimumHeight(self.tools_table.getHeight())
+ self.tools_table.setMaximumHeight(self.tools_table.getHeight())
+
+ def update_params(self):
+ self.units = self.units_radio.get_value()
+ self.zeros = self.zeros_radio.get_value()
+ self.integral = self.int_entry.get_value()
+ self.fractional = self.frac_entry.get_value()
+
+ def on_units_change(self, val):
+ if val == 'INCH':
+ self.int_entry.set_value(2)
+ self.frac_entry.set_value(4)
+ else:
+ self.int_entry.set_value(3)
+ self.frac_entry.set_value(3)
+
+ def on_load_excellon_click(self):
+ """
+
+ :return: None
+ """
+ self.app.log.debug("on_load_excellon_click()")
+
+ filter = "Excellon Files(*.DRL *.DRD *.TXT);;All Files (*.*)"
+ try:
+ filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Load PcbWizard Excellon file"),
+ directory=self.app.get_last_folder(),
+ filter=filter)
+ except TypeError:
+ filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Load PcbWizard Excellon file"),
+ filter=filter)
+
+ filename = str(filename)
+
+
+ if filename == "":
+ self.app.inform.emit(_("Open cancelled."))
+ else:
+ self.app.worker_task.emit({'fcn': self.load_excellon,
+ 'params': [self, filename]})
+
+ def on_load_inf_click(self):
+ """
+
+ :return: None
+ """
+ self.app.log.debug("on_load_inf_click()")
+
+ filter = "INF Files(*.INF);;All Files (*.*)"
+ try:
+ filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Load PcbWizard INF file"),
+ directory=self.app.get_last_folder(),
+ filter=filter)
+ except TypeError:
+ filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Load PcbWizard INF file"),
+ filter=filter)
+
+ filename = str(filename)
+
+ if filename == "":
+ self.app.inform.emit(_("Open cancelled."))
+ else:
+ self.app.worker_task.emit({'fcn': self.load_inf, 'params': [filename]})
+
+ def load_inf(self, filename):
+ self.app.log.debug("ToolPcbWizard.load_inf()")
+
+ with open(filename, 'r') as inf_f:
+ inf_file_content = inf_f.readlines()
+
+ tool_re = re.compile(r'^T(\d+)\s+(\d*\.?\d+)$')
+
+ for eline in inf_file_content:
+ # Cleanup lines
+ eline = eline.strip(' \r\n')
+
+ match = tool_re.search(eline)
+ if match:
+ tool =int( match.group(1))
+ dia = float(match.group(2))
+ if dia < 0.1:
+ # most likely the file is in INCH
+ self.units_radio.set_value('INCH')
+
+ self.tools_from_inf[tool] = dia
+
+ if not self.tools_from_inf:
+ self.app.inform.emit(_("[ERROR] The INF file does not contain the tool table.\n"
+ "Try to open the Excellon file from File -> Open -> Excellon\n"
+ "and edit the drill diameters manually."))
+ return "fail"
+
+ self.tools_table.setVisible(True)
+ self.file_loaded.emit('inf', filename)
+
+ def load_excellon(self, filename):
+ with open(filename, 'r') as exc_f:
+ self.exc_file_content = exc_f.readlines()
+
+ self.file_loaded.emit("excellon", filename)
+
+ def on_file_loaded(self, signal, filename):
+ self.build_ui()
+
+ if signal == 'inf':
+ self.inf_loaded = True
+ elif signal == 'excellon':
+ self.excellon_loaded = True
+
+ if self.excellon_loaded and self.inf_loaded:
+ pass
+
+
+ # Register recent file
+ self.app.defaults["global_last_folder"] = os.path.split(str(filename))[0]
+
+ def on_import_excellon(self, signal, excellon_fileobj):
+ self.app.log.debug("import_2files_excellon()")
+
+ # How the object should be initialized
+ def obj_init(excellon_obj, app_obj):
+ # self.progress.emit(20)
+
+ try:
+ ret = excellon_obj.parse_file(file_obj=excellon_fileobj)
+ if ret == "fail":
+ app_obj.log.debug("Excellon parsing failed.")
+ app_obj.inform.emit(_("[ERROR_NOTCL] This is not Excellon file."))
+ return "fail"
+ except IOError:
+ app_obj.inform.emit(_("[ERROR_NOTCL] Cannot parse file: %s") % self.outname)
+ app_obj.log.debug("Could not import Excellon object.")
+ app_obj.progress.emit(0)
+ return "fail"
+ except:
+ msg = _("[ERROR_NOTCL] An internal error has occurred. See shell.\n")
+ msg += app_obj.traceback.format_exc()
+ app_obj.inform.emit(msg)
+ return "fail"
+
+ ret = excellon_obj.create_geometry()
+ if ret == 'fail':
+ app_obj.log.debug("Could not create geometry for Excellon object.")
+ return "fail"
+ app_obj.progress.emit(100)
+ for tool in excellon_obj.tools:
+ if excellon_obj.tools[tool]['solid_geometry']:
+ return
+ app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % name)
+ return "fail"
+
+ if self.process_finished:
+ with self.app.proc_container.new(_("Importing Excellon.")):
+
+ # Object name
+ name = self.outname
+
+ ret = self.app.new_object("excellon", name, obj_init, autoselected=False)
+ if ret == 'fail':
+ self.app.inform.emit(_('[ERROR_NOTCL] Import Excellon file failed.'))
+ return
+
+ # Register recent file
+ self.app.file_opened.emit("excellon", name)
+
+ # GUI feedback
+ self.app.inform.emit(_("[success] Opened: %s") % name)
+ else:
+ self.app.inform.emit(_('[WARNING_NOTCL] Excellon merging is in progress. Please wait...'))
+
diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py
index cd9dd56c..6119a4c1 100644
--- a/flatcamTools/__init__.py
+++ b/flatcamTools/__init__.py
@@ -14,5 +14,6 @@ from flatcamTools.ToolPaint import ToolPaint
from flatcamTools.ToolNonCopperClear import NonCopperClear
from flatcamTools.ToolTransform import ToolTransform
from flatcamTools.ToolSolderPaste import SolderPaste
+from flatcamTools.ToolPcbWizard import PcbWizard
from flatcamTools.ToolShell import FCShell
From 348fe4f13549bf9fa6e2cc49e11512d805bf3c7d Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Mon, 15 Apr 2019 05:17:16 +0300
Subject: [PATCH 10/42] - finished ToolPcbWizard; it will autodetect the
Excellon format, units from the INF file
---
README.md | 1 +
flatcamTools/ToolPcbWizard.py | 107 +++++++++++++++++++++++++---------
2 files changed, 80 insertions(+), 28 deletions(-)
diff --git a/README.md b/README.md
index 08e0ee9d..1616ed8b 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
15.04.2019
- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
+- finished ToolPcbWizard; it will autodetect the Excellon format, units from the INF file
14.04.2019
diff --git a/flatcamTools/ToolPcbWizard.py b/flatcamTools/ToolPcbWizard.py
index d990140d..e4338b4d 100644
--- a/flatcamTools/ToolPcbWizard.py
+++ b/flatcamTools/ToolPcbWizard.py
@@ -13,6 +13,8 @@ from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal
import re
import os
+from datetime import datetime
+from io import StringIO
import gettext
import FlatCAMTranslation as fcTranslate
@@ -108,8 +110,8 @@ class PcbWizard(FlatCAMTool):
form_layout1.addRow(self.frac_label, self.frac_entry)
# Zeros suppression for coordinates
- self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'},
- {'label': 'TZ', 'value': 'T'},
+ self.zeros_radio = RadioSet([{'label': 'LZ', 'value': 'LZ'},
+ {'label': 'TZ', 'value': 'TZ'},
{'label': 'No Suppression', 'value': 'D'}])
self.zeros_label = QtWidgets.QLabel(_("Zeros supp.:"))
self.zeros_label.setToolTip(
@@ -148,15 +150,19 @@ class PcbWizard(FlatCAMTool):
self.inf_loaded = False
self.process_finished = False
+ self.modified_excellon_file = ''
+
## Signals
self.excellon_brn.clicked.connect(self.on_load_excellon_click)
self.inf_btn.clicked.connect(self.on_load_inf_click)
- self.import_button.clicked.connect(self.on_import_excellon)
+ self.import_button.clicked.connect(lambda: self.on_import_excellon(
+ excellon_fileobj=self.modified_excellon_file))
+
self.file_loaded.connect(self.on_file_loaded)
self.units_radio.activated_custom.connect(self.on_units_change)
self.units = 'INCH'
- self.zeros = 'L'
+ self.zeros = 'LZ'
self.integral = 2
self.fractional = 4
@@ -191,6 +197,16 @@ class PcbWizard(FlatCAMTool):
FlatCAMTool.install(self, icon, separator, **kwargs)
def set_tool_ui(self):
+ self.units = 'INCH'
+ self.zeros = 'LZ'
+ self.integral = 2
+ self.fractional = 4
+
+ self.outname = 'file'
+
+ self.exc_file_content = None
+ self.tools_from_inf = {}
+
## Initialize form
self.int_entry.set_value(self.integral)
self.frac_entry.set_value(self.fractional)
@@ -200,6 +216,7 @@ class PcbWizard(FlatCAMTool):
self.excellon_loaded = False
self.inf_loaded = False
self.process_finished = False
+ self.modified_excellon_file = ''
self.build_ui()
@@ -207,7 +224,7 @@ class PcbWizard(FlatCAMTool):
sorted_tools = []
if not self.tools_from_inf:
- self.tools_table.setRowCount(1)
+ self.tools_table.setVisible(False)
else:
sort = []
for k, v in list(self.tools_from_inf.items()):
@@ -281,8 +298,7 @@ class PcbWizard(FlatCAMTool):
if filename == "":
self.app.inform.emit(_("Open cancelled."))
else:
- self.app.worker_task.emit({'fcn': self.load_excellon,
- 'params': [self, filename]})
+ self.app.worker_task.emit({'fcn': self.load_excellon, 'params': [filename]})
def on_load_inf_click(self):
"""
@@ -314,6 +330,7 @@ class PcbWizard(FlatCAMTool):
inf_file_content = inf_f.readlines()
tool_re = re.compile(r'^T(\d+)\s+(\d*\.?\d+)$')
+ format_re = re.compile(r'^(\d+)\.?(\d+)\s*format,\s*(inches|metric)?,\s*(absolute|incremental)?.*$')
for eline in inf_file_content:
# Cleanup lines
@@ -323,11 +340,24 @@ class PcbWizard(FlatCAMTool):
if match:
tool =int( match.group(1))
dia = float(match.group(2))
- if dia < 0.1:
- # most likely the file is in INCH
- self.units_radio.set_value('INCH')
+ # if dia < 0.1:
+ # # most likely the file is in INCH
+ # self.units_radio.set_value('INCH')
self.tools_from_inf[tool] = dia
+ continue
+ match = format_re.search(eline)
+ if match:
+ self.integral = int(match.group(1))
+ self.fractional = int(match.group(2))
+ units = match.group(3)
+ if units == 'inches':
+ self.units = 'INCH'
+ else:
+ self.units = 'METRIC'
+ self.units_radio.set_value(self.units)
+ self.int_entry.set_value(self.integral)
+ self.frac_entry.set_value(self.fractional)
if not self.tools_from_inf:
self.app.inform.emit(_("[ERROR] The INF file does not contain the tool table.\n"
@@ -335,7 +365,6 @@ class PcbWizard(FlatCAMTool):
"and edit the drill diameters manually."))
return "fail"
- self.tools_table.setVisible(True)
self.file_loaded.emit('inf', filename)
def load_excellon(self, filename):
@@ -346,20 +375,39 @@ class PcbWizard(FlatCAMTool):
def on_file_loaded(self, signal, filename):
self.build_ui()
+ time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
if signal == 'inf':
self.inf_loaded = True
+ self.tools_table.setVisible(True)
+ self.app.inform.emit(_("[success] PcbWizard .INF file loaded."))
elif signal == 'excellon':
self.excellon_loaded = True
+ self.outname = os.path.split(str(filename))[1]
+ self.app.inform.emit(_("[success] Main PcbWizard Excellon file loaded."))
if self.excellon_loaded and self.inf_loaded:
- pass
-
+ self.update_params()
+ excellon_string = ''
+ for line in self.exc_file_content:
+ excellon_string += line
+ if 'M48' in line:
+ header = ';EXCELLON RE-GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \
+ (str(self.app.version), str(self.app.version_date))
+ header += ';Created on : %s' % time_str + '\n'
+ header += ';FILE_FORMAT={integral}:{fractional}\n'.format(integral=self.integral,
+ fractional=self.fractional)
+ header += '{units},{zeros}\n'.format(units=self.units, zeros=self.zeros)
+ for k, v in self.tools_from_inf.items():
+ header += 'T{tool}C{dia}\n'.format(tool=int(k), dia=float(v))
+ excellon_string += header
+ self.modified_excellon_file = StringIO(excellon_string)
+ self.process_finished = True
# Register recent file
self.app.defaults["global_last_folder"] = os.path.split(str(filename))[0]
- def on_import_excellon(self, signal, excellon_fileobj):
+ def on_import_excellon(self, signal=None, excellon_fileobj=None):
self.app.log.debug("import_2files_excellon()")
# How the object should be initialized
@@ -394,22 +442,25 @@ class PcbWizard(FlatCAMTool):
app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % name)
return "fail"
- if self.process_finished:
- with self.app.proc_container.new(_("Importing Excellon.")):
+ if excellon_fileobj is not None and excellon_fileobj != '':
+ if self.process_finished:
+ with self.app.proc_container.new(_("Importing Excellon.")):
- # Object name
- name = self.outname
+ # Object name
+ name = self.outname
- ret = self.app.new_object("excellon", name, obj_init, autoselected=False)
- if ret == 'fail':
- self.app.inform.emit(_('[ERROR_NOTCL] Import Excellon file failed.'))
- return
+ ret = self.app.new_object("excellon", name, obj_init, autoselected=False)
+ if ret == 'fail':
+ self.app.inform.emit(_('[ERROR_NOTCL] Import Excellon file failed.'))
+ return
- # Register recent file
- self.app.file_opened.emit("excellon", name)
+ # Register recent file
+ self.app.file_opened.emit("excellon", name)
- # GUI feedback
- self.app.inform.emit(_("[success] Opened: %s") % name)
+ # GUI feedback
+ self.app.inform.emit(_("[success] Imported: %s") % name)
+ self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+ else:
+ self.app.inform.emit(_('[WARNING_NOTCL] Excellon merging is in progress. Please wait...'))
else:
- self.app.inform.emit(_('[WARNING_NOTCL] Excellon merging is in progress. Please wait...'))
-
+ self.app.inform.emit(_('[ERROR_NOTCL] The imported Excellon file is None.'))
From d7cb8a58258a12f859e94add02951c95d3b22208 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Mon, 15 Apr 2019 16:19:30 +0300
Subject: [PATCH 11/42] - Gerber Editor: reduced the delay to show UI when
editing an empty Gerber object - update the order of event handlers
connection in Editors to first connect new handlers then disconnect old
handlers. It seems that if nothing is connected some VispY functions like
canvas panning no longer works if there is at least once nothing connected to
the 'mouse_move' event - Excellon Editor: update so always there is a tool
selected even after the Execllon object was just edited; before it always
required a click inside of the tool table, not you do it only if needed. -
fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status
of the app (Editor active or not)
---
FlatCAMApp.py | 20 ++-----
README.md | 4 ++
flatcamEditors/FlatCAMExcEditor.py | 92 ++++++++++++++++++------------
flatcamEditors/FlatCAMGeoEditor.py | 44 ++++++++------
flatcamEditors/FlatCAMGrbEditor.py | 81 +++++++++++++++++++-------
5 files changed, 151 insertions(+), 90 deletions(-)
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index f567029b..850f3da8 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -2093,12 +2093,9 @@ class App(QtCore.QObject):
if isinstance(edited_object, FlatCAMGerber) or isinstance(edited_object, FlatCAMGeometry) or \
isinstance(edited_object, FlatCAMExcellon):
-
- # adjust the status of the menu entries related to the editor
- self.ui.menueditedit.setDisabled(True)
- self.ui.menueditok.setDisabled(False)
+ pass
else:
- self.inform.emit(_("[WARNING_NOTCL] Select a Geometry or Excellon Object to edit."))
+ self.inform.emit(_("[WARNING_NOTCL] Select a Geometry, Gerber or Excellon Object to edit."))
return
if isinstance(edited_object, FlatCAMGeometry):
@@ -2109,7 +2106,8 @@ class App(QtCore.QObject):
edited_tools = [int(x.text()) for x in edited_object.ui.geo_tools_table.selectedItems()]
if len(edited_tools) > 1:
self.inform.emit(_("[WARNING_NOTCL] Simultanoeus editing of tools geometry in a MultiGeo Geometry "
- "is not possible.\n Edit only one geometry at a time."))
+ "is not possible.\n"
+ "Edit only one geometry at a time."))
self.geo_editor.edit_fcgeometry(edited_object, multigeo_tool=edited_tools[0])
else:
self.geo_editor.edit_fcgeometry(edited_object)
@@ -2156,16 +2154,8 @@ class App(QtCore.QObject):
"""
self.report_usage("editor2object()")
- # adjust the status of the menu entries related to the editor
- self.ui.menueditedit.setDisabled(False)
- self.ui.menueditok.setDisabled(True)
-
# do not update a geometry or excellon object unless it comes out of an editor
if self.call_source != 'app':
- # adjust the visibility of some of the canvas context menu
- self.ui.popmenu_edit.setVisible(True)
- self.ui.popmenu_save.setVisible(False)
-
edited_obj = self.collection.get_active()
obj_type = ""
@@ -2244,6 +2234,8 @@ class App(QtCore.QObject):
self.grb_editor.deactivate_grb_editor()
elif isinstance(edited_obj, FlatCAMExcellon):
self.exc_editor.deactivate()
+ # set focus on the project tab
+ self.ui.notebook.setCurrentWidget(self.ui.project_tab)
else:
self.inform.emit(_("[WARNING_NOTCL] Select a Gerber, Geometry or Excellon Object to update."))
return
diff --git a/README.md b/README.md
index 1616ed8b..d8e8fffe 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,10 @@ CAD program, and create G-Code for Isolation routing.
- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
- finished ToolPcbWizard; it will autodetect the Excellon format, units from the INF file
+- Gerber Editor: reduced the delay to show UI when editing an empty Gerber object
+- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
+- Excellon Editor: update so always there is a tool selected even after the Execllon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
+- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
14.04.2019
diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py
index 2f02b7c6..89e23a51 100644
--- a/flatcamEditors/FlatCAMExcEditor.py
+++ b/flatcamEditors/FlatCAMExcEditor.py
@@ -12,6 +12,8 @@ from camlib import *
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, SpinBoxDelegate
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
+from copy import copy, deepcopy
+
import gettext
import FlatCAMTranslation as fcTranslate
@@ -695,12 +697,23 @@ class FlatCAMExcEditor(QtCore.QObject):
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
self.exc_edit_widget = QtWidgets.QWidget()
+ ## Box for custom widgets
+ # This gets populated in offspring implementations.
layout = QtWidgets.QVBoxLayout()
self.exc_edit_widget.setLayout(layout)
+ # add a frame and inside add a vertical box layout. Inside this vbox layout I add all the Drills widgets
+ # this way I can hide/show the frame
+ self.drills_frame = QtWidgets.QFrame()
+ self.drills_frame.setContentsMargins(0, 0, 0, 0)
+ layout.addWidget(self.drills_frame)
+ self.tools_box = QtWidgets.QVBoxLayout()
+ self.tools_box.setContentsMargins(0, 0, 0, 0)
+ self.drills_frame.setLayout(self.tools_box)
+
## Page Title box (spacing between children)
self.title_box = QtWidgets.QHBoxLayout()
- layout.addLayout(self.title_box)
+ self.tools_box.addLayout(self.title_box)
## Page Title icon
pixmap = QtGui.QPixmap('share/flatcam_icon32.png')
@@ -715,26 +728,12 @@ class FlatCAMExcEditor(QtCore.QObject):
## Object name
self.name_box = QtWidgets.QHBoxLayout()
- layout.addLayout(self.name_box)
+ self.tools_box.addLayout(self.name_box)
name_label = QtWidgets.QLabel(_("Name:"))
self.name_box.addWidget(name_label)
self.name_entry = FCEntry()
self.name_box.addWidget(self.name_entry)
- ## Box box for custom widgets
- # This gets populated in offspring implementations.
- self.custom_box = QtWidgets.QVBoxLayout()
- layout.addLayout(self.custom_box)
-
- # add a frame and inside add a vertical box layout. Inside this vbox layout I add all the Drills widgets
- # this way I can hide/show the frame
- self.drills_frame = QtWidgets.QFrame()
- self.drills_frame.setContentsMargins(0, 0, 0, 0)
- self.custom_box.addWidget(self.drills_frame)
- self.tools_box = QtWidgets.QVBoxLayout()
- self.tools_box.setContentsMargins(0, 0, 0, 0)
- self.drills_frame.setLayout(self.tools_box)
-
#### Tools Drills ####
self.tools_table_label = QtWidgets.QLabel("%s" % _('Tools Table'))
self.tools_table_label.setToolTip(
@@ -1134,6 +1133,7 @@ class FlatCAMExcEditor(QtCore.QObject):
return storage
def set_ui(self):
+
# updated units
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
@@ -1177,7 +1177,7 @@ class FlatCAMExcEditor(QtCore.QObject):
tool_dia = float('%.2f' % v['C'])
self.tool2tooldia[int(k)] = tool_dia
- def build_ui(self):
+ def build_ui(self, first_run=None):
try:
# if connected, disconnect the signal from the slot on item_changed as it creates issues
@@ -1271,6 +1271,11 @@ class FlatCAMExcEditor(QtCore.QObject):
self.tools_table_exc.setItem(self.tool_row, 1, dia) # Diameter
self.tools_table_exc.setItem(self.tool_row, 2, drill_count) # Number of drills per tool
self.tools_table_exc.setItem(self.tool_row, 3, slot_count) # Number of drills per tool
+
+ if first_run is True:
+ # set now the last tool selected
+ self.last_tool_selected = int(tool_id)
+
self.tool_row += 1
# make the diameter column editable
@@ -1568,6 +1573,13 @@ class FlatCAMExcEditor(QtCore.QObject):
self.edited_obj_name = self.name_entry.get_value()
def activate(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(True)
+ self.app.ui.menueditok.setDisabled(False)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(False)
+ self.app.ui.popmenu_save.setVisible(True)
+
self.connect_canvas_event_handlers()
# initialize working objects
@@ -1604,14 +1616,20 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
- # adjust the visibility of some of the canvas context menu
- self.app.ui.popmenu_edit.setVisible(False)
- self.app.ui.popmenu_save.setVisible(True)
-
# Tell the App that the editor is active
self.editor_active = True
+ # show the UI
+ self.drills_frame.show()
+
def deactivate(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(False)
+ self.app.ui.menueditok.setDisabled(True)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(True)
+ self.app.ui.popmenu_save.setVisible(False)
+
self.disconnect_canvas_event_handlers()
self.clear()
self.app.ui.exc_edit_toolbar.setDisabled(True)
@@ -1661,42 +1679,44 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.g_editor_cmenu.setEnabled(False)
self.app.ui.e_editor_cmenu.setEnabled(False)
- # adjust the visibility of some of the canvas context menu
- self.app.ui.popmenu_edit.setVisible(True)
- self.app.ui.popmenu_save.setVisible(False)
-
# Show original geometry
if self.exc_obj:
self.exc_obj.visible = True
+ # hide the UI
+ self.drills_frame.hide()
+
def connect_canvas_event_handlers(self):
## Canvas events
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
+ self.canvas.vis_connect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_connect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_connect('mouse_release', self.on_exc_click_release)
+
# make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor
-
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.disconnect()
- self.canvas.vis_connect('mouse_press', self.on_canvas_click)
- self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_exc_click_release)
-
def disconnect_canvas_event_handlers(self):
- self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
- self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
-
# we restore the key and mouse control to FlatCAMApp method
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
+ self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
+
def clear(self):
self.active_tool = None
# self.shape_buffer = []
@@ -1741,7 +1761,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.set_ui()
# now that we hava data, create the GUI interface and add it to the Tool Tab
- self.build_ui()
+ self.build_ui(first_run=True)
# we activate this after the initial build as we don't need to see the tool been populated
self.tools_table_exc.itemChanged.connect(self.on_tool_edit)
@@ -1992,7 +2012,7 @@ class FlatCAMExcEditor(QtCore.QObject):
try:
selected_dia = self.tool2tooldia[self.tools_table_exc.currentRow() + 1]
- self.last_tool_selected = self.tools_table_exc.currentRow() + 1
+ self.last_tool_selected = copy(self.tools_table_exc.currentRow()) + 1
for obj in self.storage_dict[selected_dia].get_objects():
self.selected.append(obj)
except Exception as e:
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index add13229..5067abf3 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -2855,6 +2855,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.replot()
def activate(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(True)
+ self.app.ui.menueditok.setDisabled(False)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(False)
+ self.app.ui.popmenu_save.setVisible(True)
+
self.connect_canvas_event_handlers()
# initialize working objects
@@ -2887,14 +2894,17 @@ class FlatCAMGeoEditor(QtCore.QObject):
for w in sel_tab_widget_list:
w.setEnabled(False)
- # adjust the visibility of some of the canvas context menu
- self.app.ui.popmenu_edit.setVisible(False)
- self.app.ui.popmenu_save.setVisible(True)
-
# Tell the App that the editor is active
self.editor_active = True
def deactivate(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(False)
+ self.app.ui.menueditok.setDisabled(True)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(True)
+ self.app.ui.popmenu_save.setVisible(False)
+
self.disconnect_canvas_event_handlers()
self.clear()
self.app.ui.geo_edit_toolbar.setDisabled(True)
@@ -2942,10 +2952,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Tell the app that the editor is no longer active
self.editor_active = False
- # adjust the visibility of some of the canvas context menu
- self.app.ui.popmenu_edit.setVisible(True)
- self.app.ui.popmenu_save.setVisible(False)
-
try:
# re-enable all the widgets in the Selected Tab that were disabled after entering in Edit Geometry Mode
sel_tab_widget_list = self.app.ui.selected_tab.findChildren(QtWidgets.QWidget)
@@ -2961,9 +2967,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
def connect_canvas_event_handlers(self):
## Canvas events
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
+ self.canvas.vis_connect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_connect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_connect('mouse_release', self.on_geo_click_release)
+
# make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor
-
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
@@ -2971,23 +2982,20 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.collection.view.clicked.disconnect()
- self.canvas.vis_connect('mouse_press', self.on_canvas_click)
- self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_geo_click_release)
-
def disconnect_canvas_event_handlers(self):
-
- self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
- self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
-
# we restore the key and mouse control to FlatCAMApp method
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
+ self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
+
def add_shape(self, shape):
"""
Adds a shape to the shape storage.
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 9770f25b..e29b1137 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -8,7 +8,7 @@ import shapely.affinity as affinity
from numpy import arctan2, Inf, array, sqrt, sign, dot
from rtree import index as rtindex
import threading, time
-import copy
+from copy import copy, deepcopy
from camlib import *
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
@@ -36,8 +36,14 @@ class FCPad(FCShapeTool):
self.name = 'pad'
self.draw_app = draw_app
+ try:
+ self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
+ except KeyError:
+ self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add a Pad, first select a tool in Tool Table"))
+ self.draw_app.in_action = False
+ self.complete = True
+ return
self.storage_obj = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
- self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
# if those cause KeyError exception it means that the aperture type is not 'R'. Only 'R' type has those keys
@@ -51,7 +57,6 @@ class FCPad(FCShapeTool):
pass
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
-
if isinstance(geo, DrawToolShape) and geo.geo is not None:
self.draw_app.draw_utility_geometry(geo=geo)
@@ -191,8 +196,15 @@ class FCPadArray(FCShapeTool):
self.name = 'array'
self.draw_app = draw_app
+ try:
+ self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
+ except KeyError:
+ self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add an Pad Array first select a tool in Tool Table"))
+ self.complete = True
+ self.draw_app.in_action = False
+ self.draw_app.array_frame.hide()
+ return
self.storage_obj = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
- self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
# if those cause KeyError exception it means that the aperture type is not 'R'. Only 'R' type has those keys
@@ -229,12 +241,6 @@ class FCPadArray(FCShapeTool):
self.draw_app.app.inform.emit(self.start_msg)
- try:
- self.selected_size = self.draw_app.tool2tooldia[self.draw_app.last_aperture_selected]
- except KeyError:
- self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add an Pad Array first select a tool in Tool Table"))
- return
-
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y), static=True)
if isinstance(geo, DrawToolShape) and geo.geo is not None:
@@ -466,7 +472,7 @@ class FCPadArray(FCShapeTool):
self.geometry.append(DrawToolShape(geo))
self.complete = True
self.draw_app.app.inform.emit(_("[success] Done. Pad Array added."))
- self.draw_app.in_action = True
+ self.draw_app.in_action = False
self.draw_app.array_frame.hide()
return
@@ -1518,7 +1524,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.pad_direction_radio.set_value('CW')
self.pad_axis_radio.set_value('X')
- def build_ui(self):
+ def build_ui(self, first_run=None):
try:
# if connected, disconnect the signal from the slot on item_changed as it creates issues
@@ -1601,6 +1607,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apertures_table.setItem(self.apertures_row, 4, ap_dim_item) # Aperture Dimensions
self.apertures_row += 1
+ if first_run is True:
+ # set now the last aperture selected
+ self.last_aperture_selected = ap_code
for ap_code in sorted_macros:
ap_code = str(ap_code)
@@ -1618,6 +1627,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apertures_table.setItem(self.apertures_row, 2, ap_type_item) # Aperture Type
self.apertures_row += 1
+ if first_run is True:
+ # set now the last aperture selected
+ self.last_aperture_selected = ap_code
self.apertures_table.selectColumn(0)
self.apertures_table.resizeColumnsToContents()
@@ -1872,6 +1884,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apsize_entry.setReadOnly(False)
def activate_grb_editor(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(True)
+ self.app.ui.menueditok.setDisabled(False)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(False)
+ self.app.ui.popmenu_save.setVisible(True)
+
self.connect_canvas_event_handlers()
# init working objects
@@ -1914,6 +1933,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.editor_active = True
def deactivate_grb_editor(self):
+ # adjust the status of the menu entries related to the editor
+ self.app.ui.menueditedit.setDisabled(False)
+ self.app.ui.menueditok.setDisabled(True)
+ # adjust the visibility of some of the canvas context menu
+ self.app.ui.popmenu_edit.setVisible(True)
+ self.app.ui.popmenu_save.setVisible(False)
+
self.disconnect_canvas_event_handlers()
self.clear()
self.app.ui.grb_edit_toolbar.setDisabled(True)
@@ -1978,28 +2004,33 @@ class FlatCAMGrbEditor(QtCore.QObject):
# make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
+ self.canvas.vis_connect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_connect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_connect('mouse_release', self.on_grb_click_release)
+
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.disconnect()
- self.canvas.vis_connect('mouse_press', self.on_canvas_click)
- self.canvas.vis_connect('mouse_move', self.on_canvas_move)
- self.canvas.vis_connect('mouse_release', self.on_grb_click_release)
-
def disconnect_canvas_event_handlers(self):
- self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
- self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
# we restore the key and mouse control to FlatCAMApp method
+ # first connect to new, then disconnect the old handlers
+ # don't ask why but if there is nothing connected I've seen issues
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
+ self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
+ self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
+ self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
+
def clear(self):
self.active_tool = None
# self.shape_buffer = []
@@ -2104,7 +2135,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.grb_plot_promises.append(apid)
self.app.worker_task.emit({'fcn': job_thread, 'params': [self, apid]})
- self.start_delayed_plot(check_period=1000)
+ # do the delayed plot only if there is something to plot (the gerber is not empty)
+ if bool(self.gerber_obj.apertures):
+ self.start_delayed_plot(check_period=1000)
+ else:
+ self.set_ui()
+ # now that we have data (empty data actually), create the GUI interface and add it to the Tool Tab
+ self.build_ui(first_run=True)
def update_fcgerber(self, grb_obj):
"""
@@ -2296,7 +2333,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
try:
# selected_apid = str(self.tool2tooldia[row + 1])
selected_apid = self.apertures_table.item(row, 1).text()
- self.last_aperture_selected = selected_apid
+ self.last_aperture_selected = copy(selected_apid)
for obj in self.storage_dict[selected_apid]['solid_geometry']:
self.selected.append(obj)
@@ -2685,7 +2722,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.set_ui()
# now that we have data, create the GUI interface and add it to the Tool Tab
- self.build_ui()
+ self.build_ui(first_run=True)
self.plot_all()
# HACK: enabling/disabling the cursor seams to somehow update the shapes making them more 'solid'
From 2b4beebba2cdd69eebb23b941f44f2afb6b7bbaf Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Mon, 15 Apr 2019 19:29:23 +0300
Subject: [PATCH 12/42] - added support in Excellon parser for autodetection of
Excellon file format for the Excellon files generate by the following ECAD
sw: DipTrace, Eagle, Altium, Sprint Layout
---
README.md | 1 +
camlib.py | 44 ++++++++++++++++++++++++++++++++++++++------
2 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index d8e8fffe..8a2adb8a 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ CAD program, and create G-Code for Isolation routing.
- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
- Excellon Editor: update so always there is a tool selected even after the Execllon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
+- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generate by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
14.04.2019
diff --git a/camlib.py b/camlib.py
index 7676bee1..05773d7c 100644
--- a/camlib.py
+++ b/camlib.py
@@ -3738,6 +3738,8 @@ class Excellon(Geometry):
self.excellon_format_upper_mm = excellon_format_upper_mm or self.defaults["excellon_format_upper_mm"]
self.excellon_format_lower_mm = excellon_format_lower_mm or self.defaults["excellon_format_lower_mm"]
self.excellon_units = excellon_units or self.defaults["excellon_units"]
+ # detected Excellon format is stored here:
+ self.excellon_format = None
# Attributes to be included in serialization
# Always append to it because it carries contents
@@ -3766,10 +3768,10 @@ class Excellon(Geometry):
# Ignored in the parser
#self.fmat_re = re.compile(r'^FMAT,([12])$')
- # Number format and units
+ # Uunits and possible Excellon zeros and possible Excellon format
# INCH uses 6 digits
# METRIC uses 5/6
- self.units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?.*$')
+ self.units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?,?(\d*\.\d+)?.*$')
# Tool definition/parameters (?= is look-ahead
# NOTE: This might be an overkill!
@@ -3831,6 +3833,10 @@ class Excellon(Geometry):
# Allegro Excellon format support
self.tool_units_re = re.compile(r'(\;\s*Holesize \d+.\s*\=\s*(\d+.\d+).*(MILS|MM))')
+ # Altium Excellon format support
+ # it's a comment like this: ";FILE_FORMAT=2:5"
+ self.altium_format = re.compile(r'^;\s*(?:FILE_FORMAT)?(?:Format)?[=|:]\s*(\d+)[:|.](\d+).*$')
+
# Parse coordinates
self.leadingzeros_re = re.compile(r'^[-\+]?(0*)(\d*)')
@@ -3946,6 +3952,16 @@ class Excellon(Geometry):
log.debug(" Tool definition: %s %s" % (name_tool, spec))
spec['solid_geometry'] = []
continue
+ # search for Altium Excellon Format / Sprint Layout who is included as a comment
+ match = self.altium_format.search(eline)
+ if match:
+ self.excellon_format_upper_mm = match.group(1)
+ self.excellon_format_lower_mm = match.group(2)
+
+ self.excellon_format_upper_in = match.group(1)
+ self.excellon_format_lower_in = match.group(2)
+ log.warning("Altium Excellon format preset found: %s:%s" % (match.group(1), match.group(2)))
+ continue
else:
log.warning("Line ignored, it's a comment: %s" % eline)
else:
@@ -4373,8 +4389,16 @@ class Excellon(Geometry):
if match:
self.units_found = match.group(1)
self.zeros = match.group(2) # "T" or "L". Might be empty
-
- # self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)]
+ self.excellon_format = match.group(3)
+ if self.excellon_format:
+ upper = len(self.excellon_format.partition('.')[0])
+ lower = len(self.excellon_format.partition('.')[2])
+ if self.units == 'MM':
+ self.excellon_format_upper_mm = upper
+ self.excellon_format_lower_mm = lower
+ else:
+ self.excellon_format_upper_in = upper
+ self.excellon_format_lower_in = lower
# Modified for issue #80
self.convert_units({"INCH": "IN", "METRIC": "MM"}[self.units_found])
@@ -4423,8 +4447,16 @@ class Excellon(Geometry):
if match:
self.units_found = match.group(1)
self.zeros = match.group(2) # "T" or "L". Might be empty
-
- # self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)]
+ self.excellon_format = match.group(3)
+ if self.excellon_format:
+ upper = len(self.excellon_format.partition('.')[0])
+ lower = len(self.excellon_format.partition('.')[2])
+ if self.units == 'MM':
+ self.excellon_format_upper_mm = upper
+ self.excellon_format_lower_mm = lower
+ else:
+ self.excellon_format_upper_in = upper
+ self.excellon_format_lower_in = lower
# Modified for issue #80
self.convert_units({"INCH": "IN", "METRIC": "MM"}[self.units_found])
From 2ba0b494ff23549b8394057a4f4584515e5d1fb6 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Mon, 15 Apr 2019 22:48:22 +0300
Subject: [PATCH 13/42] - Gerber Editor: finished a new tool: Poligonize Tool
(ALT+N in Editor). It will fuse a selection of tracks into a polygon. It will
fill a selection of polygons if they are apart and it will make a single
polygon if the selection is overlapped. All the newly created filled polygons
will be stored in aperture '0' (if it does not exist it will be automatically
created) - fixed a bug in Move command in context menu who crashed the app
when triggered - Gerber Editor: when adding a new aperture it will be store
as the last selected and it will be used for any tools that are triggered
until a new aperture is selected.
---
FlatCAMApp.py | 2 +-
README.md | 5 ++-
camlib.py | 12 ++---
flatcamEditors/FlatCAMGrbEditor.py | 69 ++++++++++++++++++-----------
flatcamGUI/FlatCAMGUI.py | 11 ++++-
share/poligonize32.png | Bin 0 -> 522 bytes
6 files changed, 64 insertions(+), 35 deletions(-)
create mode 100644 share/poligonize32.png
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 850f3da8..d563be51 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -5653,7 +5653,7 @@ class App(QtCore.QObject):
def obj_move(self):
self.report_usage("obj_move()")
- self.move_tool.run()
+ self.move_tool.run(toggle=False)
def on_fileopengerber(self):
"""
diff --git a/README.md b/README.md
index 8a2adb8a..a73e875c 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,10 @@ CAD program, and create G-Code for Isolation routing.
- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
- Excellon Editor: update so always there is a tool selected even after the Execllon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
-- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generate by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
+- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generated by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
+- Gerber Editor: finished a new tool: Poligonize Tool (ALT+N in Editor). It will fuse a selection of tracks into a polygon. It will fill a selection of polygons if they are apart and it will make a single polygon if the selection is overlapped. All the newly created filled polygons will be stored in aperture '0' (if it does not exist it will be automatically created)
+- fixed a bug in Move command in context menu who crashed the app when triggered
+- Gerber Editor: when adding a new aperture it will be store as the last selected and it will be used for any tools that are triggered until a new aperture is selected.
14.04.2019
diff --git a/camlib.py b/camlib.py
index 05773d7c..41d03454 100644
--- a/camlib.py
+++ b/camlib.py
@@ -3928,9 +3928,10 @@ class Excellon(Geometry):
log.warning("Found ALLEGRO start of the header: %s" % eline)
continue
- # Header End #
- # Since there might be comments in the header that include char % or M95
- # we ignore the lines starting with ';' which show they are comments
+ # Search for Header End #
+ # Since there might be comments in the header that include header end char (% or M95)
+ # we ignore the lines starting with ';' that contains such header end chars because it is not a
+ # real header end.
if self.comm_re.search(eline):
match = self.tool_units_re.search(eline)
if match:
@@ -3938,7 +3939,7 @@ class Excellon(Geometry):
line_units_found = True
line_units = match.group(3)
self.convert_units({"MILS": "IN", "MM": "MM"}[line_units])
- log.warning("Type of Allegro UNITS found inline: %s" % line_units)
+ log.warning("Type of Allegro UNITS found inline in comments: %s" % line_units)
if match.group(2):
name_tool += 1
@@ -3960,7 +3961,8 @@ class Excellon(Geometry):
self.excellon_format_upper_in = match.group(1)
self.excellon_format_lower_in = match.group(2)
- log.warning("Altium Excellon format preset found: %s:%s" % (match.group(1), match.group(2)))
+ log.warning("Altium Excellon format preset found in comments: %s:%s" %
+ (match.group(1), match.group(2)))
continue
else:
log.warning("Line ignored, it's a comment: %s" % eline)
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index e29b1137..d94ebb9d 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -2,7 +2,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QSettings
from shapely.geometry import LineString, LinearRing, MultiLineString
-from shapely.ops import cascaded_union
+from shapely.ops import cascaded_union, unary_union
import shapely.affinity as affinity
from numpy import arctan2, Inf, array, sqrt, sign, dot
@@ -497,39 +497,42 @@ class FCPoligonize(FCShapeTool):
self.make()
def click(self, point):
- # self.draw_app.in_action = True
- # if self.draw_app.selected:
- # self.make()
- # else:
- # self.draw_app.app.inform.emit(_("[WARNING_NOTCL] No shapes are selected. Select shapes and try again ..."))
-
return ""
def make(self):
- geo = []
- for shape in self.draw_app.selected:
- current_storage = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
- print(self.draw_app.active_tool)
- aha = []
- if shape.geo:
- shape_points = list(shape.geo.exterior.coords)
- for pt in shape_points:
- aha.append(Point(pt))
- concave_hull, bla_bla = alpha_shape(points=aha, alpha=0.5)
- geo.append(concave_hull)
- print(geo)
- self.geometry = DrawToolShape(geo)
- self.draw_app.on_grb_shape_complete(current_storage)
+ if not self.draw_app.selected:
+ self.draw_app.in_action = False
+ self.complete = True
+ self.draw_app.app.inform.emit(_("[ERROR_NOTCL] Failed. Nothing selected."))
+ self.draw_app.select_tool("select")
+ return
+
+ try:
+ current_storage = self.draw_app.storage_dict['0']['solid_geometry']
+ except KeyError:
+ self.draw_app.on_aperture_add(apid='0')
+ current_storage = self.draw_app.storage_dict['0']['solid_geometry']
+
+ fused_geo = [Polygon(sh.geo.exterior) for sh in self.draw_app.selected]
+
+ fused_geo = MultiPolygon(fused_geo)
+ fused_geo = fused_geo.buffer(0.0000001)
+ if isinstance(fused_geo, MultiPolygon):
+ for geo in fused_geo:
+ self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(geo))
+ else:
+ self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(fused_geo))
+
+ self.draw_app.delete_selected()
+ self.draw_app.plot_all()
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit(_("[success] Done. Poligonize completed."))
- self.draw_app.build_ui()
# MS: always return to the Select Tool if modifier key is not pressed
# else return to the current tool
-
key_modifier = QtWidgets.QApplication.keyboardModifiers()
if self.draw_app.app.defaults["global_mselect_key"] == 'Control':
modifier_to_use = Qt.ControlModifier
@@ -1472,6 +1475,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.grb_add_track_menuitem.triggered.connect(self.on_track_add)
self.app.ui.grb_add_region_menuitem.triggered.connect(self.on_region_add)
+ self.app.ui.grb_convert_poly_menuitem.triggered.connect(self.on_poligonize)
self.app.ui.grb_add_buffer_menuitem.triggered.connect(self.on_buffer)
self.app.ui.grb_add_scale_menuitem.triggered.connect(self.on_scale)
self.app.ui.grb_transform_menuitem.triggered.connect(self.transform_tool.run)
@@ -1760,6 +1764,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.build_ui()
+ self.last_aperture_selected = ap_id
+
# make a quick sort through the tool2tooldia dict so we find which row to select
row_to_be_selected = None
for key in sorted(self.tool2tooldia):
@@ -2346,15 +2352,22 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.options[key] = self.sender().isChecked()
return self.options[key]
- def on_grb_shape_complete(self, storage=None):
+ def on_grb_shape_complete(self, storage=None, specific_shape=None):
self.app.log.debug("on_shape_complete()")
+ if specific_shape:
+ geo = specific_shape
+ else:
+ geo = self.active_tool.geometry
+ if geo is None:
+ return
+
if storage is not None:
# Add shape
- self.add_gerber_shape(self.active_tool.geometry, storage)
+ self.add_gerber_shape(geo, storage)
else:
stora = self.storage_dict[self.last_aperture_selected]['solid_geometry']
- self.add_gerber_shape(self.active_tool.geometry, storage=stora)
+ self.add_gerber_shape(geo, storage=stora)
# Remove any utility shapes
self.delete_utility_geometry()
@@ -2372,6 +2385,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
:return: None
"""
# List of DrawToolShape?
+
if isinstance(shape, list):
for subshape in shape:
self.add_gerber_shape(subshape, storage)
@@ -2859,6 +2873,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
def on_region_add(self):
self.select_tool('region')
+ def on_poligonize(self):
+ self.select_tool('poligonize')
+
def on_buffer(self):
buff_value = 0.01
log.debug("FlatCAMGrbEditor.on_buffer()")
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index 37914892..d8b37baf 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -470,6 +470,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('Add Region\tN'))
self.grb_editor_menu.addSeparator()
+ self.grb_convert_poly_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/poligonize32.png'),
+ _("Poligonize\tALT+N"))
self.grb_add_buffer_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/buffer16-2.png'),
_('Buffer\tB'))
self.grb_add_scale_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/scale32.png'),
@@ -690,7 +692,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.add_pad_ar_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/padarray32.png'), _('Add Pad Array'))
self.grb_add_track_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/track32.png'), _("Add Track"))
self.grb_add_region_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Add Region"))
- self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Poligonize"))
+ self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/poligonize32.png'),
+ _("Poligonize"))
self.grb_edit_toolbar.addSeparator()
@@ -2529,11 +2532,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
elif modifiers == QtCore.Qt.ShiftModifier:
pass
elif modifiers == QtCore.Qt.AltModifier:
+ # Poligonize Tool
+ if key == QtCore.Qt.Key_N or key == 'N':
+ self.app.grb_editor.on_poligonize()
+ return
+
# Transformation Tool
if key == QtCore.Qt.Key_R or key == 'R':
self.app.grb_editor.on_transform()
return
-
elif modifiers == QtCore.Qt.NoModifier:
# Abort the current action
if key == QtCore.Qt.Key_Escape or key == 'Escape':
diff --git a/share/poligonize32.png b/share/poligonize32.png
new file mode 100644
index 0000000000000000000000000000000000000000..95a5e3863f99259a8ecf23f96e3b596bf1ae976a
GIT binary patch
literal 522
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50
z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKpuOE
zr>`sfV-|j9Yk4PW%{~SOMj=lZ#}JR>Z>QMi9dZz8wYTnUQN0v2Nl*R6%pDTBD(f6F
zHF;TudQ}4gCS?5Z4p^g58I*A5*6+th^JeO`pw^Vzv_VmUMdszl<(z^^VtH)(DL-3W
zQ|n^pXz(!kA68la=h@C1+-HQ&6|Q>1$j-1d*7f4Dr6o3R$_!TI$M526JyIlp^!^cH
ze_vG|hBcil|2$`#SrmBg$jHeWM8P_Say}BTLKo=IEp-h{bPY{I3=OPIEv<|Vbq&m|3=B5!+rAY=
dLvDUbW?Cg~4Tm1{a{@Imc)I$ztaD0e0sy|S!DRpd
literal 0
HcmV?d00001
From d92750d124345a2ac63c621c619619d34bf5a2a0 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Tue, 16 Apr 2019 15:27:51 +0300
Subject: [PATCH 14/42] - added ability to use ENTER key to finish tool adding
in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
---
FlatCAMObj.py | 2 ++
README.md | 4 ++++
flatcamEditors/FlatCAMExcEditor.py | 2 +-
flatcamEditors/FlatCAMGrbEditor.py | 5 ++++-
flatcamGUI/ObjectUI.py | 2 +-
flatcamTools/ToolNonCopperClear.py | 3 ++-
flatcamTools/ToolPaint.py | 3 ++-
flatcamTools/ToolSolderPaste.py | 5 +++--
8 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/FlatCAMObj.py b/FlatCAMObj.py
index 83678a70..fcb955dc 100644
--- a/FlatCAMObj.py
+++ b/FlatCAMObj.py
@@ -3032,6 +3032,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
+ self.ui.addtool_entry.returnPressed.connect(lambda: self.on_tool_add())
def set_tool_offset_visibility(self, current_row):
if current_row is None:
@@ -3107,6 +3108,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# I use lambda's because the connected functions have parameters that could be used in certain scenarios
self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add())
+
self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
diff --git a/README.md b/README.md
index a73e875c..2e9f41a4 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+16.04.2019
+
+- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
+
15.04.2019
- working on a new tool to process automatically PcbWizard Excellon files which are generated in 2 files
diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py
index 89e23a51..c75ea1a2 100644
--- a/flatcamEditors/FlatCAMExcEditor.py
+++ b/flatcamEditors/FlatCAMExcEditor.py
@@ -1020,7 +1020,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn)
self.name_entry.returnPressed.connect(self.on_name_activate)
self.addtool_btn.clicked.connect(self.on_tool_add)
- # self.addtool_entry.editingFinished.connect(self.on_tool_add)
+ self.addtool_entry.returnPressed.connect(self.on_tool_add)
self.deltool_btn.clicked.connect(self.on_tool_delete)
# self.tools_table_exc.selectionModel().currentChanged.connect(self.on_row_selected)
self.tools_table_exc.cellPressed.connect(self.on_row_selected)
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index d94ebb9d..06cafa3e 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -1084,7 +1084,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
)
grid1.addWidget(self.apdim_lbl, 4, 0)
- self.apdim_entry = EvalEntry()
+ self.apdim_entry = EvalEntry2()
grid1.addWidget(self.apdim_entry, 4, 1)
apadd_lbl = QtWidgets.QLabel('%s' % _('Add Aperture:'))
@@ -1466,6 +1466,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.aptype_cb.currentIndexChanged[str].connect(self.on_aptype_changed)
self.addaperture_btn.clicked.connect(self.on_aperture_add)
+ self.apsize_entry.returnPressed.connect(self.on_aperture_add)
+ self.apdim_entry.returnPressed.connect(self.on_aperture_add)
+
self.delaperture_btn.clicked.connect(self.on_aperture_delete)
self.apertures_table.cellPressed.connect(self.on_row_selected)
diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py
index 72391d8e..d5cbac25 100644
--- a/flatcamGUI/ObjectUI.py
+++ b/flatcamGUI/ObjectUI.py
@@ -975,7 +975,7 @@ class GeometryObjectUI(ObjectUI):
"Diameter for the new tool"
)
)
- self.addtool_entry = FCEntry()
+ self.addtool_entry = FCEntry2()
# hlay.addWidget(self.addtool_label)
# hlay.addStretch()
diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py
index 4b7ee0de..a29bc229 100644
--- a/flatcamTools/ToolNonCopperClear.py
+++ b/flatcamTools/ToolNonCopperClear.py
@@ -121,7 +121,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.addtool_entry_lbl.setToolTip(
_("Diameter for the new tool to add in the Tool Table")
)
- self.addtool_entry = FCEntry()
+ self.addtool_entry = FCEntry2()
# hlay.addWidget(self.addtool_label)
# hlay.addStretch()
@@ -254,6 +254,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.tools_box.addStretch()
self.addtool_btn.clicked.connect(self.on_tool_add)
+ self.addtool_entry.returnPressed.connect(self.on_tool_add)
self.deltool_btn.clicked.connect(self.on_tool_delete)
self.generate_ncc_button.clicked.connect(self.on_ncc)
diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py
index c6aff23c..4e3c1e8a 100644
--- a/flatcamTools/ToolPaint.py
+++ b/flatcamTools/ToolPaint.py
@@ -118,7 +118,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.addtool_entry_lbl.setToolTip(
_("Diameter for the new tool.")
)
- self.addtool_entry = FCEntry()
+ self.addtool_entry = FCEntry2()
# hlay.addWidget(self.addtool_label)
# hlay.addStretch()
@@ -307,6 +307,7 @@ class ToolPaint(FlatCAMTool, Gerber):
## Signals
self.addtool_btn.clicked.connect(self.on_tool_add)
+ self.addtool_entry.returnPressed.connect(self.on_tool_add)
# self.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
self.tools_table.itemChanged.connect(self.on_tool_edit)
self.deltool_btn.clicked.connect(self.on_tool_delete)
diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py
index 477c055f..4f7a21e7 100644
--- a/flatcamTools/ToolSolderPaste.py
+++ b/flatcamTools/ToolSolderPaste.py
@@ -8,7 +8,7 @@
from FlatCAMTool import FlatCAMTool
from FlatCAMCommon import LoudDict
-from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable
+from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCEntry2, FCTable
from FlatCAMApp import log
from camlib import distance
from FlatCAMObj import FlatCAMCNCjob
@@ -102,7 +102,7 @@ class SolderPaste(FlatCAMTool):
self.addtool_entry_lbl.setToolTip(
_("Diameter for the new Nozzle tool to add in the Tool Table")
)
- self.addtool_entry = FCEntry()
+ self.addtool_entry = FCEntry2()
# hlay.addWidget(self.addtool_label)
# hlay.addStretch()
@@ -415,6 +415,7 @@ class SolderPaste(FlatCAMTool):
## Signals
self.combo_context_del_action.triggered.connect(self.on_delete_object)
self.addtool_btn.clicked.connect(self.on_tool_add)
+ self.addtool_entry.returnPressed.connect(self.on_tool_add)
self.deltool_btn.clicked.connect(self.on_tool_delete)
self.soldergeo_btn.clicked.connect(self.on_create_geo_click)
self.solder_gcode_btn.clicked.connect(self.on_create_gcode_click)
From 83cb0b875592b83c415fa4b2184edb4eb80c0a19 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Tue, 16 Apr 2019 18:27:24 +0300
Subject: [PATCH 15/42] - Gerber Editor: started to add modes of laying a track
---
README.md | 1 +
flatcamEditors/FlatCAMGrbEditor.py | 99 +++++++++++++++++++++++++-----
2 files changed, 83 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 2e9f41a4..9bb424d3 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
16.04.2019
- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
+- Gerber Editor: started to add modes of laying a track
15.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 06cafa3e..91e5d99f 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -565,6 +565,10 @@ class FCRegion(FCShapeTool):
size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size'])
self.buf_val = (size_ap / 2) if size_ap > 0 else 0.0000001
+ self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
+ self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
+ self.temp_points = []
+
self.start_msg = _("Click on 1st point ...")
def click(self, point):
@@ -577,6 +581,10 @@ class FCRegion(FCShapeTool):
return ""
+ def update_grid_info(self):
+ self.gridx_size =float( self.draw_app.app.ui.grid_gap_x_entry.get_value())
+ self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
+
def utility_geometry(self, data=None):
if len(self.points) == 1:
@@ -619,8 +627,10 @@ class FCTrack(FCRegion):
"""
def make(self):
-
- self.geometry = DrawToolShape(LineString(self.points).buffer(self.buf_val))
+ if len(self.temp_points) == 1:
+ self.geometry = DrawToolShape(Point(self.temp_points).buffer(self.buf_val))
+ else:
+ self.geometry = DrawToolShape(LineString(self.temp_points).buffer(self.buf_val))
self.name = 'track'
self.draw_app.in_action = False
@@ -632,13 +642,62 @@ class FCTrack(FCRegion):
self.draw_app.apertures_table.clearSelection()
self.draw_app.plot_all()
- def utility_geometry(self, data=None):
- if len(self.points) > 0:
- temp_points = [x for x in self.points]
- temp_points.append(data)
+ def click(self, point):
+ self.draw_app.in_action = True
+ self.points.append(point)
- return DrawToolUtilityShape(LineString(temp_points).buffer(self.buf_val))
- return None
+ if len(self.temp_points) == 1:
+ g = DrawToolShape(Point(self.temp_points).buffer(self.buf_val))
+ else:
+ g = DrawToolShape(LineString(self.temp_points).buffer(self.buf_val))
+
+ self.draw_app.add_gerber_shape(g, self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry'])
+ self.draw_app.plot_all()
+ if len(self.points) > 0:
+ self.draw_app.app.inform.emit(_("Click on next Point or click Right mouse button to complete ..."))
+ return "Click on next point or hit ENTER to complete ..."
+
+ return ""
+
+ def utility_geometry(self, data=None):
+ self.update_grid_info()
+
+ if len(self.points) == 0:
+ return None
+ elif len(self.points) > 0:
+
+ # TODO make sure to check the status of GRID SNAP BUTTON: if enabled ot not
+ self.temp_points = [self.points[-1]]
+ old_x = self.points[-1][0]
+ old_y = self.points[-1][1]
+ x = data[0]
+ y = data[1]
+ if (x > old_x and y == old_y) or (x < old_x and y == old_y) or (x == old_x and y > old_y) or (x == old_x and y < old_y):
+ self.temp_points.append(data)
+ elif x > old_x and y > old_y:
+ self.temp_points.append((x-self.gridx_size, old_y))
+ self.temp_points.append((x, old_y+self.gridy_size))
+ self.temp_points.append(data)
+
+ elif x > old_x and y < old_y:
+ self.temp_points.append((x-self.gridx_size, old_y))
+ self.temp_points.append((x, old_y-self.gridy_size))
+ self.temp_points.append(data)
+
+ elif x < old_x and y > old_y:
+ self.temp_points.append((x+self.gridx_size, old_y))
+ self.temp_points.append((x, old_y+self.gridy_size))
+ self.temp_points.append(data)
+
+ elif x < old_x and y < old_y:
+ self.temp_points.append((x+self.gridx_size, old_y))
+ self.temp_points.append((x, old_y-self.gridy_size))
+ self.temp_points.append(data)
+
+ if len(self.temp_points) == 1:
+ return DrawToolUtilityShape(Point(self.temp_points).buffer(self.buf_val))
+
+ return DrawToolUtilityShape(LineString(self.temp_points).buffer(self.buf_val))
def on_key(self, key):
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
@@ -2491,16 +2550,19 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.inform.emit(_("[success] Done."))
# MS: always return to the Select Tool if modifier key is not pressed
- # else return to the current tool
- key_modifier = QtWidgets.QApplication.keyboardModifiers()
- if (self.app.defaults["global_mselect_key"] == 'Control' and
- key_modifier == Qt.ControlModifier) or \
- (self.app.defaults["global_mselect_key"] == 'Shift' and
- key_modifier == Qt.ShiftModifier):
-
+ # else return to the current tool but not for FCTrack
+ if isinstance(self.active_tool, FCTrack):
self.select_tool(self.active_tool.name)
else:
- self.select_tool("select")
+ key_modifier = QtWidgets.QApplication.keyboardModifiers()
+ if (self.app.defaults["global_mselect_key"] == 'Control' and
+ key_modifier == Qt.ControlModifier) or \
+ (self.app.defaults["global_mselect_key"] == 'Shift' and
+ key_modifier == Qt.ShiftModifier):
+
+ self.select_tool(self.active_tool.name)
+ else:
+ self.select_tool("select")
except Exception as e:
log.warning("Error: %s" % str(e))
raise
@@ -2663,11 +2725,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Add the new utility shape
self.tool_shape.add(
shape=el, color=(self.app.defaults["global_draw_color"] + '80'),
+ # face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None)
else:
# Add the new utility shape
self.tool_shape.add(
- shape=geo.geo, color=(self.app.defaults["global_draw_color"] + '80'),
+ shape=geo.geo,
+ color=(self.app.defaults["global_draw_color"] + '80'),
+ # face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None)
self.tool_shape.redraw()
From 862eb2ae78e1b4903c55e00bab51b41282734445 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Tue, 16 Apr 2019 23:21:19 +0300
Subject: [PATCH 16/42] - Gerber Editor: Add Track Tool: added 5 modes for
laying a track: 45-degrees, reverse-45 degrees, 90-degrees, reverse
90-degrees and free angle. Key 'T' will cycle forward through the modes and
key 'R' will cycle in reverse through the track laying modes. - Gerber
Editor: Add Track Tool: first right click will finish the track. Second right
click will exit the Track Tool and return to Select Tool.
---
README.md | 4 +-
flatcamEditors/FlatCAMGrbEditor.py | 132 +++++++++++++++++++++++------
2 files changed, 110 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 9bb424d3..0e83387d 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing.
- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
- Gerber Editor: started to add modes of laying a track
+- Gerber Editor: Add Track Tool: added 5 modes for laying a track: 45-degrees, reverse-45 degrees, 90-degrees, reverse 90-degrees and free angle. Key 'T' will cycle forward through the modes and key 'R' will cycle in reverse through the track laying modes.
+- Gerber Editor: Add Track Tool: first right click will finish the track. Second right click will exit the Track Tool and return to Select Tool.
15.04.2019
@@ -20,7 +22,7 @@ CAD program, and create G-Code for Isolation routing.
- finished ToolPcbWizard; it will autodetect the Excellon format, units from the INF file
- Gerber Editor: reduced the delay to show UI when editing an empty Gerber object
- update the order of event handlers connection in Editors to first connect new handlers then disconnect old handlers. It seems that if nothing is connected some VispY functions like canvas panning no longer works if there is at least once nothing connected to the 'mouse_move' event
-- Excellon Editor: update so always there is a tool selected even after the Execllon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
+- Excellon Editor: update so always there is a tool selected even after the Excellon object was just edited; before it always required a click inside of the tool table, not you do it only if needed.
- fixed the menu File -> Edit -> Edit/Close Editor entry to reflect the status of the app (Editor active or not)
- added support in Excellon parser for autodetection of Excellon file format for the Excellon files generated by the following ECAD sw: DipTrace, Eagle, Altium, Sprint Layout
- Gerber Editor: finished a new tool: Poligonize Tool (ALT+N in Editor). It will fuse a selection of tracks into a polygon. It will fill a selection of polygons if they are apart and it will make a single polygon if the selection is overlapped. All the newly created filled polygons will be stored in aperture '0' (if it does not exist it will be automatically created)
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 91e5d99f..88f9a8f6 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -582,7 +582,7 @@ class FCRegion(FCShapeTool):
return ""
def update_grid_info(self):
- self.gridx_size =float( self.draw_app.app.ui.grid_gap_x_entry.get_value())
+ self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
def utility_geometry(self, data=None):
@@ -625,6 +625,11 @@ class FCTrack(FCRegion):
"""
Resulting type: Polygon
"""
+ def __init__(self, draw_app):
+ FCRegion.__init__(self, draw_app)
+ self.draw_app = draw_app
+ self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
+ self.mode = 1
def make(self):
if len(self.temp_points) == 1:
@@ -663,37 +668,61 @@ class FCTrack(FCRegion):
self.update_grid_info()
if len(self.points) == 0:
- return None
+ return DrawToolUtilityShape(Point(data).buffer(self.buf_val))
elif len(self.points) > 0:
- # TODO make sure to check the status of GRID SNAP BUTTON: if enabled ot not
self.temp_points = [self.points[-1]]
old_x = self.points[-1][0]
old_y = self.points[-1][1]
x = data[0]
y = data[1]
- if (x > old_x and y == old_y) or (x < old_x and y == old_y) or (x == old_x and y > old_y) or (x == old_x and y < old_y):
- self.temp_points.append(data)
- elif x > old_x and y > old_y:
- self.temp_points.append((x-self.gridx_size, old_y))
- self.temp_points.append((x, old_y+self.gridy_size))
- self.temp_points.append(data)
- elif x > old_x and y < old_y:
- self.temp_points.append((x-self.gridx_size, old_y))
- self.temp_points.append((x, old_y-self.gridy_size))
- self.temp_points.append(data)
+ mx = abs(round((x - old_x) / self.gridx_size))
+ my = abs(round((y - old_y) / self.gridy_size))
- elif x < old_x and y > old_y:
- self.temp_points.append((x+self.gridx_size, old_y))
- self.temp_points.append((x, old_y+self.gridy_size))
- self.temp_points.append(data)
-
- elif x < old_x and y < old_y:
- self.temp_points.append((x+self.gridx_size, old_y))
- self.temp_points.append((x, old_y-self.gridy_size))
- self.temp_points.append(data)
+ if self.draw_app.app.ui.grid_snap_btn.isChecked():
+ if self.mode == 1:
+ if x > old_x:
+ if mx > my:
+ self.temp_points.append((old_x + self.gridx_size*(mx-my), old_y))
+ if mx < my:
+ if y < old_y:
+ self.temp_points.append((old_x, old_y - self.gridy_size * (my-mx)))
+ else:
+ self.temp_points.append((old_x, old_y - self.gridy_size * (mx-my)))
+ if x < old_x:
+ if mx > my:
+ self.temp_points.append((old_x - self.gridx_size*(mx-my), old_y))
+ if mx < my:
+ if y < old_y:
+ self.temp_points.append((old_x, old_y - self.gridy_size * (my-mx)))
+ else:
+ self.temp_points.append((old_x, old_y - self.gridy_size * (mx-my)))
+ elif self.mode == 2:
+ if x > old_x:
+ if mx > my:
+ self.temp_points.append((old_x + self.gridx_size*my, y))
+ if mx < my:
+ if y < old_y:
+ self.temp_points.append((x, old_y - self.gridy_size * mx))
+ else:
+ self.temp_points.append((x, old_y + self.gridy_size * mx))
+ if x < old_x:
+ if mx > my:
+ self.temp_points.append((old_x - self.gridx_size * my, y))
+ if mx < my:
+ if y < old_y:
+ self.temp_points.append((x, old_y - self.gridy_size * mx))
+ else:
+ self.temp_points.append((x, old_y + self.gridy_size * mx))
+ elif self.mode == 3:
+ self.temp_points.append((old_x, y))
+ elif self.mode == 4:
+ self.temp_points.append((x, old_y))
+ else:
+ pass
+ self.temp_points.append(data)
if len(self.temp_points) == 1:
return DrawToolUtilityShape(Point(self.temp_points).buffer(self.buf_val))
@@ -702,13 +731,61 @@ class FCTrack(FCRegion):
def on_key(self, key):
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
- self.points = self.points[0:-1]
+ self.temp_points = self.points[0:-1]
# Remove any previous utility shape
self.draw_app.tool_shape.clear(update=False)
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
self.draw_app.draw_utility_geometry(geo=geo)
return _("Backtracked one point ...")
+ if key == 'T' or key == QtCore.Qt.Key_T:
+ if self.mode == 1:
+ self.mode = 2
+ msg = _('Track Mode 2: Reverse 45 degrees ...')
+ elif self.mode == 2:
+ self.mode = 3
+ msg = _('Track Mode 3: 90 degrees ...')
+ elif self.mode == 3:
+ self.mode = 4
+ msg = _('Track Mode 4: Reverse 90 degrees ...')
+ elif self.mode == 4:
+ self.mode = 5
+ msg = _('Track Mode 5: Free angle ...')
+ else:
+ self.mode = 1
+ msg = _('Track Mode 1: 45 degrees ...')
+
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+
+ return msg
+
+ if key == 'R' or key == QtCore.Qt.Key_R:
+ if self.mode == 1:
+ self.mode = 5
+ msg = _('Track Mode 5: Free angle ...')
+ elif self.mode == 5:
+ self.mode = 4
+ msg = _('Track Mode 4: Reverse 90 degrees ...')
+ elif self.mode == 4:
+ self.mode = 3
+ msg = _('Track Mode 3: 90 degrees ...')
+ elif self.mode == 3:
+ self.mode = 2
+ msg = _('Track Mode 2: Reverse 45 degrees ...')
+ else:
+ self.mode = 1
+ msg = _('Track Mode 1: 45 degrees ...')
+
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+
+ return msg
+
class FCScale(FCShapeTool):
def __init__(self, draw_app):
@@ -2537,8 +2614,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.panning_action = False
else:
if self.in_action is False:
- self.app.cursor = QtGui.QCursor()
- self.app.ui.popMenu.popup(self.app.cursor.pos())
+ if self.active_tool.complete is False and not isinstance(self.active_tool, FCApertureSelect):
+ self.active_tool.complete = True
+ self.delete_utility_geometry()
+ self.select_tool('select')
+ else:
+ self.app.cursor = QtGui.QCursor()
+ self.app.ui.popMenu.popup(self.app.cursor.pos())
else:
# if right click on canvas and the active tool need to be finished (like Path or Polygon)
# right mouse click will finish the action
From 7707baa9b2e9622ba509249297262c42baf7a8d1 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 01:10:21 +0300
Subject: [PATCH 17/42] - Gerber Editor: added protections for the Pad Array
and Pad Tool for the case when the aperture size is zero (the aperture where
to store the regions)
---
README.md | 1 +
flatcamEditors/FlatCAMGrbEditor.py | 47 ++++++++++++++++++++++++++++--
2 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 0e83387d..47de23b6 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: started to add modes of laying a track
- Gerber Editor: Add Track Tool: added 5 modes for laying a track: 45-degrees, reverse-45 degrees, 90-degrees, reverse 90-degrees and free angle. Key 'T' will cycle forward through the modes and key 'R' will cycle in reverse through the track laying modes.
- Gerber Editor: Add Track Tool: first right click will finish the track. Second right click will exit the Track Tool and return to Select Tool.
+- Gerber Editor: added protections for the Pad Array and Pad Tool for the case when the aperture size is zero (the aperture where to store the regions)
15.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 88f9a8f6..4e7c5a2f 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -39,10 +39,19 @@ class FCPad(FCShapeTool):
try:
self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
except KeyError:
- self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add a Pad, first select a tool in Tool Table"))
+ self.draw_app.app.inform.emit(_(
+ "[WARNING_NOTCL] To add an Pad first select a aperture in Aperture Table"))
self.draw_app.in_action = False
self.complete = True
return
+
+ if self.radius == 0:
+ self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Aperture size is zero. It needs to be greater than zero."))
+ self.dont_execute = True
+ return
+ else:
+ self.dont_execute = False
+
self.storage_obj = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@@ -72,6 +81,10 @@ class FCPad(FCShapeTool):
return "Done."
def utility_geometry(self, data=None):
+ if self.dont_execute is True:
+ self.draw_app.select_tool('select')
+ return
+
self.points = data
geo_data = self.util_shape(data)
if geo_data:
@@ -199,11 +212,20 @@ class FCPadArray(FCShapeTool):
try:
self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
except KeyError:
- self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add an Pad Array first select a tool in Tool Table"))
+ self.draw_app.app.inform.emit(_(
+ "[WARNING_NOTCL] To add an Pad Array first select a aperture in Aperture Table"))
self.complete = True
self.draw_app.in_action = False
self.draw_app.array_frame.hide()
return
+
+ if self.radius == 0:
+ self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Aperture size is zero. It needs to be greater than zero."))
+ self.dont_execute = True
+ return
+ else:
+ self.dont_execute = False
+
self.storage_obj = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@@ -274,6 +296,10 @@ class FCPadArray(FCShapeTool):
self.origin = origin
def utility_geometry(self, data=None, static=None):
+ if self.dont_execute is True:
+ self.draw_app.select_tool('select')
+ return
+
self.pad_axis = self.draw_app.pad_axis_radio.get_value()
self.pad_direction = self.draw_app.pad_direction_radio.get_value()
self.pad_array = self.draw_app.array_type_combo.get_value()
@@ -823,6 +849,11 @@ class FCScale(FCShapeTool):
self.draw_app.on_scale()
self.deactivate_scale()
+ def clean_up(self):
+ self.draw_app.selected = []
+ self.draw_app.apertures_table.clearSelection()
+ self.draw_app.plot_all()
+
class FCBuffer(FCShapeTool):
def __init__(self, draw_app):
@@ -860,6 +891,11 @@ class FCBuffer(FCShapeTool):
self.draw_app.on_buffer()
self.deactivate_buffer()
+ def clean_up(self):
+ self.draw_app.selected = []
+ self.draw_app.apertures_table.clearSelection()
+ self.draw_app.plot_all()
+
class FCApertureMove(FCShapeTool):
def __init__(self, draw_app):
@@ -1078,6 +1114,11 @@ class FCTransform(FCShapeTool):
self.origin = (0, 0)
self.draw_app.transform_tool.run()
+ def clean_up(self):
+ self.draw_app.selected = []
+ self.draw_app.apertures_table.clearSelection()
+ self.draw_app.plot_all()
+
class FlatCAMGrbEditor(QtCore.QObject):
@@ -2781,8 +2822,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.draw_utility_geometry(geo=geo)
### Selection area on canvas section ###
- dx = pos[0] - self.pos[0]
if event.is_dragging == 1 and event.button == 1:
+ dx = pos[0] - self.pos[0]
self.app.delete_selection_shape()
if dx < 0:
self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x,y),
From b35dc84f0dd171bf1c1a8e9e62ca7b2165541440 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 01:38:29 +0300
Subject: [PATCH 18/42] - Gerber Editor: added some messages to warn user if no
selection exists when trying to do aperture deletion or aperture geometry
deletion
---
README.md | 4 ++++
flatcamEditors/FlatCAMGrbEditor.py | 12 ++++++++++--
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 47de23b6..ed313ad1 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+17.04.2019
+
+- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
+
16.04.2019
- added ability to use ENTER key to finish tool adding in Editors, NCC Tool, Paint Tool and SolderPaste Tool.
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 4e7c5a2f..7255cbb0 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -1962,6 +1962,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
try:
if apid is None or apid is False:
# deleted_tool_dia = float(self.apertures_table.item(self.apertures_table.currentRow(), 1).text())
+ if len(self.apertures_table.selectionModel().selectedRows()) == 0:
+ self.app.inform.emit(_("[WARNING_NOTCL] Select an aperture in Aperture Table"))
+ return
for index in self.apertures_table.selectionModel().selectedRows():
row = index.row()
deleted_apcode_list.append(self.apertures_table.item(row, 1).text())
@@ -1972,7 +1975,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
else:
deleted_apcode_list.append(apid)
except:
- self.app.inform.emit(_("[WARNING_NOTCL] Select a tool in Tool Table"))
+ self.app.inform.emit(_("[WARNING_NOTCL] Select an aperture in Aperture Table"))
return
for deleted_aperture in deleted_apcode_list:
@@ -2964,12 +2967,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
def delete_selected(self):
temp_ref = [s for s in self.selected]
+
+ if len(temp_ref) == 0:
+ self.app.inform.emit(_("[ERROR_NOTCL] Failed. No aperture geometry is selected."))
+ return
+
for shape_sel in temp_ref:
self.delete_shape(shape_sel)
self.selected = []
self.build_ui()
- self.app.inform.emit(_("[success] Done. Apertures deleted."))
+ self.app.inform.emit(_("[success] Done. Apertures geometry deleted."))
def delete_shape(self, shape):
self.is_modified = True
From 5a855292c47f05dac5eff0e9e214922b0e0fb30b Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 02:26:41 +0300
Subject: [PATCH 19/42] - wip
---
flatcamEditors/FlatCAMGrbEditor.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 7255cbb0..fee5f331 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -653,6 +653,9 @@ class FCTrack(FCRegion):
"""
def __init__(self, draw_app):
FCRegion.__init__(self, draw_app)
+
+ self.name = 'track'
+
self.draw_app = draw_app
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
self.mode = 1
@@ -662,7 +665,6 @@ class FCTrack(FCRegion):
self.geometry = DrawToolShape(Point(self.temp_points).buffer(self.buf_val))
else:
self.geometry = DrawToolShape(LineString(self.temp_points).buffer(self.buf_val))
- self.name = 'track'
self.draw_app.in_action = False
self.complete = True
From a27d19c64e373dacb6205a9d51fa1629b96c6f5c Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 02:49:12 +0300
Subject: [PATCH 20/42] - fixed version check
---
FlatCAMApp.py | 2 +-
README.md | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index d563be51..b19cb4a1 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -7772,7 +7772,7 @@ The normal flow when working in FlatCAM is the following:
self.log.debug("version_check()")
- if self.ui.general_defaults_form.general_gui_group.send_stats_cb.get_value() is True:
+ if self.ui.general_defaults_form.general_app_group.send_stats_cb.get_value() is True:
full_url = App.version_url + \
"?s=" + str(self.defaults['global_serial']) + \
"&v=" + str(self.version) + \
diff --git a/README.md b/README.md
index ed313ad1..2398e87b 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
17.04.2019
- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
+- fixed version check
16.04.2019
From c49ee7d27df9825905d76a6d45d64b578e725102 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 16:17:12 +0300
Subject: [PATCH 21/42] - added custom mouse cursors for some tools in Gerber
Editor
---
README.md | 1 +
flatcamEditors/FlatCAMGrbEditor.py | 58 ++++++++++++++++++++++++++---
share/aero.png | Bin 0 -> 2710 bytes
share/aero_array.png | Bin 0 -> 2713 bytes
share/aero_buffer.png | Bin 0 -> 2540 bytes
share/aero_circle.png | Bin 0 -> 2725 bytes
share/aero_path.png | Bin 0 -> 2712 bytes
7 files changed, 53 insertions(+), 6 deletions(-)
create mode 100644 share/aero.png
create mode 100644 share/aero_array.png
create mode 100644 share/aero_buffer.png
create mode 100644 share/aero_circle.png
create mode 100644 share/aero_path.png
diff --git a/README.md b/README.md
index 2398e87b..28f29376 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
- fixed version check
+- added custom mouse cursors for some tools in Gerber Editor
16.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index fee5f331..71dbae67 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -36,6 +36,13 @@ class FCPad(FCShapeTool):
self.name = 'pad'
self.draw_app = draw_app
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_circle.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
try:
self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2
except KeyError:
@@ -226,6 +233,13 @@ class FCPadArray(FCShapeTool):
else:
self.dont_execute = False
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_array.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
self.storage_obj = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@@ -593,7 +607,18 @@ class FCRegion(FCShapeTool):
self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
+
self.temp_points = []
+ # this will store the inflexion point in the geometry
+ self.inter_point = None
+
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.start_msg = _("Click on 1st point ...")
@@ -612,16 +637,18 @@ class FCRegion(FCShapeTool):
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
def utility_geometry(self, data=None):
+ if len(self.points) == 0:
+ return DrawToolUtilityShape(Point(data).buffer(self.buf_val))
if len(self.points) == 1:
- temp_points = [x for x in self.points]
- temp_points.append(data)
- return DrawToolUtilityShape(LineString(temp_points).buffer(self.buf_val, join_style=1))
+ self.temp_points = [x for x in self.points]
+ self.temp_points.append(data)
+ return DrawToolUtilityShape(LineString(self.temp_points).buffer(self.buf_val, join_style=1))
if len(self.points) > 1:
- temp_points = [x for x in self.points]
- temp_points.append(data)
- return DrawToolUtilityShape(LinearRing(temp_points).buffer(self.buf_val, join_style=1))
+ self.temp_points = [x for x in self.points]
+ self.temp_points.append(data)
+ return DrawToolUtilityShape(LinearRing(self.temp_points).buffer(self.buf_val, join_style=1))
return None
def make(self):
@@ -657,6 +684,14 @@ class FCTrack(FCRegion):
self.name = 'track'
self.draw_app = draw_app
+
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
self.mode = 1
@@ -1042,6 +1077,11 @@ class FCApertureSelect(DrawTool):
self.grb_editor_app.hide_tool('all')
self.grb_editor_app.hide_tool('select')
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
def set_origin(self, origin):
self.origin = origin
@@ -2660,8 +2700,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.panning_action = False
else:
if self.in_action is False:
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
if self.active_tool.complete is False and not isinstance(self.active_tool, FCApertureSelect):
self.active_tool.complete = True
+ self.in_action = False
self.delete_utility_geometry()
self.select_tool('select')
else:
diff --git a/share/aero.png b/share/aero.png
new file mode 100644
index 0000000000000000000000000000000000000000..a5bc59818d7cb2deedd53ed749c310edcf7921e4
GIT binary patch
literal 2710
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
Q!2kdN07*qoM6N<$f{5}J_y7O^
literal 0
HcmV?d00001
diff --git a/share/aero_array.png b/share/aero_array.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8a4a4f48fb579f1c07086d433bd52cb134ba5e8
GIT binary patch
literal 2713
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
T*)TS500000NkvXXu0mjfp7t!*
literal 0
HcmV?d00001
diff --git a/share/aero_buffer.png b/share/aero_buffer.png
new file mode 100644
index 0000000000000000000000000000000000000000..077ae73b445f3756180b1cc3885e0bfd7aea35e1
GIT binary patch
literal 2540
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
literal 0
HcmV?d00001
diff --git a/share/aero_path.png b/share/aero_path.png
new file mode 100644
index 0000000000000000000000000000000000000000..10e4a6dfc6e8a98f3a3c4d4d85d76037bcc7ecb4
GIT binary patch
literal 2712
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
Date: Wed, 17 Apr 2019 17:34:15 +0300
Subject: [PATCH 22/42] - Gerber Editor: work in progress to add multiple modes
of drawing for the Region Tool
---
README.md | 1 +
flatcamEditors/FlatCAMGrbEditor.py | 173 ++++++++++++++++++++++++++++-
2 files changed, 170 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 28f29376..07972183 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
- fixed version check
- added custom mouse cursors for some tools in Gerber Editor
+- Gerber Editor: work in progress to add multiple modes of drawing for the Region Tool
16.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 71dbae67..8891e0df 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -616,14 +616,19 @@ class FCRegion(FCShapeTool):
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
-
self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+ self.mode = 1
+
self.start_msg = _("Click on 1st point ...")
def click(self, point):
self.draw_app.in_action = True
+
+ if self.inter_point is not None:
+ self.points.append(self.inter_point)
+
self.points.append(point)
if len(self.points) > 0:
@@ -637,16 +642,129 @@ class FCRegion(FCShapeTool):
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
def utility_geometry(self, data=None):
+
+ x = data[0]
+ y = data[1]
+
if len(self.points) == 0:
return DrawToolUtilityShape(Point(data).buffer(self.buf_val))
if len(self.points) == 1:
self.temp_points = [x for x in self.points]
- self.temp_points.append(data)
- return DrawToolUtilityShape(LineString(self.temp_points).buffer(self.buf_val, join_style=1))
+
+ old_x = self.points[0][0]
+ old_y = self.points[0][1]
+ mx = abs(round((x - old_x) / self.gridx_size))
+ my = abs(round((y - old_y) / self.gridy_size))
+
+ if self.draw_app.app.ui.grid_snap_btn.isChecked():
+ if self.mode != 5:
+ if self.mode == 1:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ elif self.mode == 2:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ elif self.mode == 3:
+ self.temp_points.append((old_x, y))
+ elif self.mode == 4:
+ self.temp_points.append((x, old_y))
+ if self.inter_point is not None:
+ self.temp_points.append(self.inter_point)
+ else:
+ self.inter_point = data
+ self.temp_points.append(data)
+ else:
+ self.inter_point = data
+ self.temp_points.append(data)
+
+ if len(self.temp_points) > 1:
+ try:
+ return DrawToolUtilityShape(LineString(self.temp_points).buffer(self.buf_val, join_style=1))
+ except:
+ pass
+ else:
+ return DrawToolUtilityShape(Point(self.temp_points).buffer(self.buf_val))
if len(self.points) > 1:
self.temp_points = [x for x in self.points]
+
+ old_x = self.points[-1][0]
+ old_y = self.points[-1][1]
+ mx = abs(round((x - old_x) / self.gridx_size))
+ my = abs(round((y - old_y) / self.gridy_size))
+
+ if self.draw_app.app.ui.grid_snap_btn.isChecked():
+ if self.mode != 5:
+ if self.mode == 1:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ elif self.mode == 2:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ elif self.mode == 3:
+ self.temp_points.append((old_x, y))
+ elif self.mode == 4:
+ self.temp_points.append((x, old_y))
+
+ self.temp_points.append(self.inter_point)
+
self.temp_points.append(data)
return DrawToolUtilityShape(LinearRing(self.temp_points).buffer(self.buf_val, join_style=1))
return None
@@ -673,6 +791,54 @@ class FCRegion(FCShapeTool):
self.draw_app.draw_utility_geometry(geo=geo)
return _("Backtracked one point ...")
+ if key == 'T' or key == QtCore.Qt.Key_T:
+ if self.mode == 1:
+ self.mode = 2
+ msg = _('Track Mode 2: Reverse 45 degrees ...')
+ elif self.mode == 2:
+ self.mode = 3
+ msg = _('Track Mode 3: 90 degrees ...')
+ elif self.mode == 3:
+ self.mode = 4
+ msg = _('Track Mode 4: Reverse 90 degrees ...')
+ elif self.mode == 4:
+ self.mode = 5
+ msg = _('Track Mode 5: Free angle ...')
+ else:
+ self.mode = 1
+ msg = _('Track Mode 1: 45 degrees ...')
+
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+
+ return msg
+
+ if key == 'R' or key == QtCore.Qt.Key_R:
+ if self.mode == 1:
+ self.mode = 5
+ msg = _('Track Mode 5: Free angle ...')
+ elif self.mode == 5:
+ self.mode = 4
+ msg = _('Track Mode 4: Reverse 90 degrees ...')
+ elif self.mode == 4:
+ self.mode = 3
+ msg = _('Track Mode 3: 90 degrees ...')
+ elif self.mode == 3:
+ self.mode = 2
+ msg = _('Track Mode 2: Reverse 45 degrees ...')
+ else:
+ self.mode = 1
+ msg = _('Track Mode 1: 45 degrees ...')
+
+ # Remove any previous utility shape
+ self.draw_app.tool_shape.clear(update=False)
+ geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
+ self.draw_app.draw_utility_geometry(geo=geo)
+
+ return msg
+
class FCTrack(FCRegion):
"""
@@ -693,7 +859,6 @@ class FCTrack(FCRegion):
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
- self.mode = 1
def make(self):
if len(self.temp_points) == 1:
From 7218c2d920a8429ef322b639455845ef125cf747 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 20:36:36 +0300
Subject: [PATCH 23/42] - Gerber Editor: added multiple modes to lay a Region:
45-degrees, reverse 45-degrees, 90-degrees, reverse 90-degrees and
free-angle. Added also key shortcuts 'T' and 'R' to cycle forward,
respectively in reverse through the modes.
---
README.md | 2 +-
flatcamEditors/FlatCAMGrbEditor.py | 129 +++++++++++++++--------------
2 files changed, 69 insertions(+), 62 deletions(-)
diff --git a/README.md b/README.md
index 07972183..aecb636c 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ CAD program, and create G-Code for Isolation routing.
- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
- fixed version check
- added custom mouse cursors for some tools in Gerber Editor
-- Gerber Editor: work in progress to add multiple modes of drawing for the Region Tool
+- Gerber Editor: added multiple modes to lay a Region: 45-degrees, reverse 45-degrees, 90-degrees, reverse 90-degrees and free-angle. Added also key shortcuts 'T' and 'R' to cycle forward, respectively in reverse through the modes.
16.04.2019
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 8891e0df..9d23a39c 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -620,6 +620,7 @@ class FCRegion(FCShapeTool):
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.mode = 1
+ self.draw_app.app.inform.emit(_('Corner Mode 1: 45 degrees ...'))
self.start_msg = _("Click on 1st point ...")
@@ -628,7 +629,6 @@ class FCRegion(FCShapeTool):
if self.inter_point is not None:
self.points.append(self.inter_point)
-
self.points.append(point)
if len(self.points) > 0:
@@ -694,13 +694,15 @@ class FCRegion(FCShapeTool):
else:
self.inter_point = (x, old_y + self.gridy_size * mx)
elif self.mode == 3:
- self.temp_points.append((old_x, y))
+ self.inter_point = (old_x, y)
elif self.mode == 4:
- self.temp_points.append((x, old_y))
+ self.inter_point = (x, old_y)
+
if self.inter_point is not None:
self.temp_points.append(self.inter_point)
else:
self.inter_point = data
+
self.temp_points.append(data)
else:
self.inter_point = data
@@ -714,67 +716,68 @@ class FCRegion(FCShapeTool):
else:
return DrawToolUtilityShape(Point(self.temp_points).buffer(self.buf_val))
- if len(self.points) > 1:
+ if len(self.points) > 2:
self.temp_points = [x for x in self.points]
-
old_x = self.points[-1][0]
old_y = self.points[-1][1]
mx = abs(round((x - old_x) / self.gridx_size))
my = abs(round((y - old_y) / self.gridy_size))
- if self.draw_app.app.ui.grid_snap_btn.isChecked():
- if self.mode != 5:
- if self.mode == 1:
- if x > old_x:
- if mx > my:
- self.inter_point = (old_x + self.gridx_size * (mx - my), old_y)
- if mx < my:
- if y < old_y:
- self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
- else:
- self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
- if x < old_x:
- if mx > my:
- self.inter_point = (old_x - self.gridx_size * (mx - my), old_y)
- if mx < my:
- if y < old_y:
- self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
- else:
- self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
- elif self.mode == 2:
- if x > old_x:
- if mx > my:
- self.inter_point = (old_x + self.gridx_size * my, y)
- if mx < my:
- if y < old_y:
- self.inter_point = (x, old_y - self.gridy_size * mx)
- else:
- self.inter_point = (x, old_y + self.gridy_size * mx)
- if x < old_x:
- if mx > my:
- self.inter_point = (old_x - self.gridx_size * my, y)
- if mx < my:
- if y < old_y:
- self.inter_point = (x, old_y - self.gridy_size * mx)
- else:
- self.inter_point = (x, old_y + self.gridy_size * mx)
- elif self.mode == 3:
- self.temp_points.append((old_x, y))
- elif self.mode == 4:
- self.temp_points.append((x, old_y))
-
- self.temp_points.append(self.inter_point)
+ if mx and my:
+ if self.draw_app.app.ui.grid_snap_btn.isChecked():
+ if self.mode != 5:
+ if self.mode == 1:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * (mx - my), old_y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (old_x, old_y - self.gridy_size * (my - mx))
+ else:
+ self.inter_point = (old_x, old_y - self.gridy_size * (mx - my))
+ elif self.mode == 2:
+ if x > old_x:
+ if mx > my:
+ self.inter_point = (old_x + self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ if x < old_x:
+ if mx > my:
+ self.inter_point = (old_x - self.gridx_size * my, y)
+ if mx < my:
+ if y < old_y:
+ self.inter_point = (x, old_y - self.gridy_size * mx)
+ else:
+ self.inter_point = (x, old_y + self.gridy_size * mx)
+ elif self.mode == 3:
+ self.inter_point = (old_x, y)
+ elif self.mode == 4:
+ self.inter_point = (x, old_y)
+ self.temp_points.append(self.inter_point)
self.temp_points.append(data)
+
return DrawToolUtilityShape(LinearRing(self.temp_points).buffer(self.buf_val, join_style=1))
return None
def make(self):
# self.geometry = LinearRing(self.points)
- self.geometry = DrawToolShape(Polygon(self.points).buffer(self.buf_val, join_style=2))
+ if len(self.points) > 2:
+ self.geometry = DrawToolShape(Polygon(self.points).buffer(self.buf_val, join_style=2))
self.draw_app.in_action = False
self.complete = True
- self.draw_app.app.inform.emit(_("[success] Done. Region completed."))
+ self.draw_app.app.inform.emit(_("[success] Done."))
def clean_up(self):
self.draw_app.selected = []
@@ -784,7 +787,10 @@ class FCRegion(FCShapeTool):
def on_key(self, key):
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
- self.points = self.points[0:-1]
+ if self.mode == 5:
+ self.points = self.points[0:-1]
+ else:
+ self.points = self.points[0:-2]
# Remove any previous utility shape
self.draw_app.tool_shape.clear(update=False)
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
@@ -794,19 +800,19 @@ class FCRegion(FCShapeTool):
if key == 'T' or key == QtCore.Qt.Key_T:
if self.mode == 1:
self.mode = 2
- msg = _('Track Mode 2: Reverse 45 degrees ...')
+ msg = _('Corner Mode 2: Reverse 45 degrees ...')
elif self.mode == 2:
self.mode = 3
- msg = _('Track Mode 3: 90 degrees ...')
+ msg = _('Corner Mode 3: 90 degrees ...')
elif self.mode == 3:
self.mode = 4
- msg = _('Track Mode 4: Reverse 90 degrees ...')
+ msg = _('Corner Mode 4: Reverse 90 degrees ...')
elif self.mode == 4:
self.mode = 5
- msg = _('Track Mode 5: Free angle ...')
+ msg = _('Corner Mode 5: Free angle ...')
else:
self.mode = 1
- msg = _('Track Mode 1: 45 degrees ...')
+ msg = _('Corner Mode 1: 45 degrees ...')
# Remove any previous utility shape
self.draw_app.tool_shape.clear(update=False)
@@ -818,19 +824,19 @@ class FCRegion(FCShapeTool):
if key == 'R' or key == QtCore.Qt.Key_R:
if self.mode == 1:
self.mode = 5
- msg = _('Track Mode 5: Free angle ...')
+ msg = _('Corner Mode 5: Free angle ...')
elif self.mode == 5:
self.mode = 4
- msg = _('Track Mode 4: Reverse 90 degrees ...')
+ msg = _('Corner Mode 4: Reverse 90 degrees ...')
elif self.mode == 4:
self.mode = 3
- msg = _('Track Mode 3: 90 degrees ...')
+ msg = _('Corner Mode 3: 90 degrees ...')
elif self.mode == 3:
self.mode = 2
- msg = _('Track Mode 2: Reverse 45 degrees ...')
+ msg = _('Corner Mode 2: Reverse 45 degrees ...')
else:
self.mode = 1
- msg = _('Track Mode 1: 45 degrees ...')
+ msg = _('Corner Mode 1: 45 degrees ...')
# Remove any previous utility shape
self.draw_app.tool_shape.clear(update=False)
@@ -868,7 +874,7 @@ class FCTrack(FCRegion):
self.draw_app.in_action = False
self.complete = True
- self.draw_app.app.inform.emit(_("[success] Done. Path completed."))
+ self.draw_app.app.inform.emit(_("[success] Done."))
def clean_up(self):
self.draw_app.selected = []
@@ -2874,6 +2880,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.active_tool.complete = True
self.in_action = False
self.delete_utility_geometry()
+ self.app.inform.emit(_("[success] Done."))
self.select_tool('select')
else:
self.app.cursor = QtGui.QCursor()
From b749a47652ce8d34377d42e92be40f74820cff3a Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Wed, 17 Apr 2019 21:30:43 +0300
Subject: [PATCH 24/42] - Excellon Editor: fixed issue not remembering last
tool after adding a new tool - added custom mouse cursors for Excellon and
Geometry Editors in some of their tools
---
README.md | 2 ++
flatcamEditors/FlatCAMExcEditor.py | 30 ++++++++++++++++
flatcamEditors/FlatCAMGeoEditor.py | 55 +++++++++++++++++++++++++++++
share/aero_array.png | Bin 2713 -> 4563 bytes
share/aero_drill.png | Bin 0 -> 4538 bytes
share/aero_drill_array.png | Bin 0 -> 4996 bytes
6 files changed, 87 insertions(+)
create mode 100644 share/aero_drill.png
create mode 100644 share/aero_drill_array.png
diff --git a/README.md b/README.md
index aecb636c..302b830f 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ CAD program, and create G-Code for Isolation routing.
- fixed version check
- added custom mouse cursors for some tools in Gerber Editor
- Gerber Editor: added multiple modes to lay a Region: 45-degrees, reverse 45-degrees, 90-degrees, reverse 90-degrees and free-angle. Added also key shortcuts 'T' and 'R' to cycle forward, respectively in reverse through the modes.
+- Excellon Editor: fixed issue not remembering last tool after adding a new tool
+- added custom mouse cursors for Excellon and Geometry Editors in some of their tools
16.04.2019
diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py
index c75ea1a2..25d44fc9 100644
--- a/flatcamEditors/FlatCAMExcEditor.py
+++ b/flatcamEditors/FlatCAMExcEditor.py
@@ -47,6 +47,13 @@ class FCDrillAdd(FCShapeTool):
self.draw_app.select_tool("select")
return
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_drill.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
if isinstance(geo, DrawToolShape) and geo.geo is not None:
@@ -82,6 +89,11 @@ class FCDrillAdd(FCShapeTool):
def make(self):
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
# 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:
@@ -137,6 +149,13 @@ class FCDrillArray(FCShapeTool):
self.draw_app.app.inform.emit(_("[WARNING_NOTCL] To add an Drill Array first select a tool in Tool Table"))
return
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_drill_array.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y), static=True)
if isinstance(geo, DrawToolShape) and geo.geo is not None:
@@ -252,6 +271,11 @@ class FCDrillArray(FCShapeTool):
self.geometry = []
geo = None
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
# 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 not in self.draw_app.points_edit:
@@ -537,6 +561,11 @@ class FCDrillSelect(DrawTool):
DrawTool.__init__(self, exc_editor_app)
self.name = 'drill_select'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
self.exc_editor_app = exc_editor_app
self.storage = self.exc_editor_app.storage_dict
# self.selected = self.exc_editor_app.selected
@@ -1433,6 +1462,7 @@ class FlatCAMExcEditor(QtCore.QObject):
for key in sorted(self.tool2tooldia):
if self.tool2tooldia[key] == tool_dia:
row_to_be_selected = int(key) - 1
+ self.last_tool_selected = int(key)
break
self.tools_table_exc.selectRow(row_to_be_selected)
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index 5067abf3..4af68b11 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -1932,6 +1932,13 @@ class FCCircle(FCShapeTool):
DrawTool.__init__(self, draw_app)
self.name = 'circle'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_circle.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
self.start_msg = _("Click on CENTER ...")
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@@ -1958,6 +1965,11 @@ class FCCircle(FCShapeTool):
return None
def make(self):
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
p1 = self.points[0]
p2 = self.points[1]
radius = distance(p1, p2)
@@ -2173,6 +2185,13 @@ class FCRectangle(FCShapeTool):
DrawTool.__init__(self, draw_app)
self.name = 'rectangle'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
self.start_msg = _("Click on 1st corner ...")
def click(self, point):
@@ -2196,6 +2215,11 @@ class FCRectangle(FCShapeTool):
return None
def make(self):
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
p1 = self.points[0]
p2 = self.points[1]
# self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
@@ -2213,6 +2237,13 @@ class FCPolygon(FCShapeTool):
DrawTool.__init__(self, draw_app)
self.name = 'polygon'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
+
self.start_msg = _("Click on 1st point ...")
def click(self, point):
@@ -2239,6 +2270,11 @@ class FCPolygon(FCShapeTool):
return None
def make(self):
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
# self.geometry = LinearRing(self.points)
self.geometry = DrawToolShape(Polygon(self.points))
self.draw_app.in_action = False
@@ -2260,11 +2296,25 @@ class FCPath(FCPolygon):
"""
Resulting type: LineString
"""
+ def __init__(self, draw_app):
+ FCPolygon.__init__(self, draw_app)
+
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
def make(self):
self.geometry = DrawToolShape(LineString(self.points))
self.name = 'path'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit(_("[success] Done. Path completed."))
@@ -2293,6 +2343,11 @@ class FCSelect(DrawTool):
DrawTool.__init__(self, draw_app)
self.name = 'select'
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
+
self.storage = self.draw_app.storage
# self.shape_buffer = self.draw_app.shape_buffer
# self.selected = self.draw_app.selected
diff --git a/share/aero_array.png b/share/aero_array.png
index e8a4a4f48fb579f1c07086d433bd52cb134ba5e8..655351ade3be98971c2e112212b74e90dd2ef572 100644
GIT binary patch
delta 4425
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
P00000NkvXXu0mjf&?cG)
delta 2580
zcmV+v3hVXLBbgPDBYy|0dQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U=NKk{l}z
zh2L4lEP+3PSdKstF*}&$&qr3dZTGb&t{wMj!dcywnKTK?bEG4w!~V~|Pxu!{?y-g{
zUE63aa@0{rHZC;2j@LQG`g}h}d7rueZakDP4`qhRo%y|q&_3bx9#8UV
zynJkvzq2UikAI!>w-$AcBYt~)gWox>=j_+pS#1Hh+WV!xyvlRM#0i|`d93m2{0i6o
z`3yc9EiPDdUUh~zxfv;g^+`2(vUJjZ&MPfUta)eP&N(kozGDjfBd>>)dOzvp9^l7|
z!o-5P!f^huB|ZC&yC0h=J9}bgPAryq;=51yJn*}p?|&9L=el`HQC~Rihv0l&7Ben$
z_nTP|lFl1bdGJl)b&8*lOKgEbd0@WmS)8vWrlhaFRUV#8l)%&)U-6Q!sH&z4hL;k8WDDs%X=$szYt$QIIhS
zqm4dvjA6#6R*kic`NqJ^v&=eWw%Mo7F>T>Kt1ek>^`&bp^X$cufZ)x$s}HxGHm_x@#xbmc@Y
zXE2s)g7JPCz@hWyENZGXU*;}napV$8k&Kmc$?MLB4w$ZGk=g{${)&>)zKgBXD}9?t8~PArXFVpt)|nO
zpSn#`YIWKabX4=MW8KpB8aIlbjz}A#oOwr5w$+HbqJ^vW6Hi&fIO4g>Jl=bqXJ)rG
zvZ~10PVyh4-`ox%tDY+5kZF51sZ?BP318Tde~-BETZGM6b{yo(k&{zyf?xnTz<(d&
zyvEJLmEk(#)_&0PM$SG)KClg_q>-d&XHj!nyZPV5?KYccf+HDs7D0XvoHvyLr!k%*E|KzF5TrP`VV0_%_D
zP;{CKiB=7oC$5y&iSE92^b?qte1FQ?%lEm(+Qw0B>Y|F*ZC0j`!PjNjS|T0BP41p%
zN0l)QIH09qB*4W)40`hE$+H!fjdQrCj5prmf7|L)fC4+4j(j~Vj(X{>x-KQ*A5f5Z
z`3y}TZprLW3bVx3Ik{@D@Hx9)?x-@e^nQfU`e}$v0B|L5dP_Q7!?-oPWTxTYgZp
zjC23es0!9;R9=2c7T3i-ox=Dw6}bj>k9#0Z1`F!hy31MgcZ(utA_K&SJB?zjtyMY`
zZB3Uwf^ZTMe?3j-3A7bPos+xZ57oW$mbdF3^lpV{oir$UJ1|czbJVmp{?_bRkI2b9
zJWPLeg5HMa?73{0i-$TMbAJpctodjqTJd1PzYu}$yXs6@sppU+qLEg65Tc`LI7N3o
zo02Rwk7FFF9?8^?=+LB^Yu2i*)$~PCy=d|iKcB;B4C_>Pk_NEZ=gv4-J
zgy63{B5A^V9fA+(s<^1>L!peimL^n-suHf+!b7P=W6Oj?ATrDL$A3fEOIpWiPP^rz
zqzU&9_=Q4}GO);?=`z6p^fDG&8eNKrAIJ^pK=N8vhoU9;0BnFTs?Y1pE<_CluehZ#
zCYAX8pTL?4RT=1P>FBJdE4BBEh5d(KPZmTcTcTdR87aBVUBy?urLgRn9aklb
zoTxZeTghoApHMoCv>c4b>B%2%~R2mUd9O$oPd&NDG@CikGbDVzeL_Polsr
zp?=bJjw^1q?!=sS)i^L|&TZ}(qOI09{VtA$MS
zVDxL4{`4HupN0O;LN`H;@L%(2H>Jd4ln(#^0fcEoLr_UWLm*I6Pew^hMF0SJoPCi!
zNW(xJ#b48kq7@Mfia2DbP8LK(9Hojyuu$3xtvZ-o`hNvY8j=(jN5Qq=;KyRs!Nplu
z2UkH5`~Y!rby9SZ691PJTEuv8+>dwn9(V5mpwmJ#Z=4Gb3p_Jqq?7Z+A!4!6#&R38qM;H`6NeR5qkJLbvch?bvs$jQ<~{if
zgE?&_#dVq^h+zqFBp^aY6(y8mAwsK0iisrcM?L(*jz2*znOr3>ax9<%6_Voz|AXJ%
zn)#_oHz^neI$mu1V-)Dz1sXNm{yw(t#tGnm24AkUmcLR5WlN
z54hX`hMsiEkQ~WRQ^@Cm_cQvYEHH2jbg#L+HTQA)0Hmp_BaP)
zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=QZwj(JHg#WXOSpovYV>y7}oY}!Fe?FyMuiMo%
zeY^YDoVKg3?Q|vyQYa#X!}-s@Z}=CFl5-42Td$+m$fJxh(&SB#ugB|EVtM{PkNke+
z^AE>E{&G>`Q1&xlAM1IaKRK@7E@ke2*hR(Ur5EAQL$96WoRag#M;sVl_Aharhr
zUnycT<;rI?P34f5quM2~G}Krs0|Mznn!v;;UYy*?RyJ!ctNG1cZN>-^7TS`rYICko#m2IY8-@qo5#>6Nd!`}SL?(fK4@YH>m
zx!>mPCt0)k&~1Ja*$e&|=%0c9p9kuPs!VZ}
zF0(DFJ%?Tsb$mv`OH52ulRfgeniTq=f-jYbHCP(`-Kyz-1km3)s4Y+Bzsi7Ir7T(!G91RZ+A4rLD{=7$xOd$Jygy5@Y%=7blOfm*X0?G_tL
z`?YaWvUE5h>o_;XJ>v%MOoH9lcx`9Fj@Dv7@FFaf?q-({j7p^uVF&A3T*4sW^
zWSs)lj%G|?bX%*Ub&Uyay|MT#88{v$4P%Rco-q1^$X&whbe)WwY}YveBxz`t=Zr;1
zI@5qqjcX*-av_=m#7?$tZDJV_n4C{q@ghVP$6dt{$&8Hlh>Xk3x39dT<%JZT-0GUE
zT288$ITMpQT$jG%`_9+
zLX+AR=S9ITY{p%uDg^@w%WhDG)z#|@f~2o%&$|+H
zjiwk^+bxFJJWml+xOfj-IOS~$d_=QMgy>clX*RCcTg&T0_gFw$?dNdAp3
zNH`lrxZ2)fP{|p}+_bq3dq2o)%%nZ0**Yx|QPy_j21u~PxdId$N<%9v<4vsSVc=5y
zDakNcbI+X*6?+f7#}c^tnFsDBQ)i=3C3d@Xlw}>uq!<9T`EY7*5`&59jWg>m7=2&4
z9w0>P0ida*x=s?7TMh6ET74ZyP=3))5a88Z!gM{0$aLSUw6RWxZ>c?4%eBv8FH{4jDZautj*APngW%*U4>usIlbX^#W1SnF0hD
zoU(Uy#*CXbVV_DiVzjZ-TFKQ
zb%ae{Q)PG!$PPhT*Xsf0x#8`9U90&
z+W4wtP-{3a+R?>-bR1!<_%)V-g-SGTxiLL3ZE0CTs_7+Lp670oaeR^eINM?e0{jXy
z3hwSyp#TS+=*D~zFQM)bqzhSgFK~>ELT1*0w#yQcQ!3>|cYgt?yV$~xQ52)&R=m&h
z%1}@VWMj(fE~SDq%e)!=$t$AQ3~FOCLBym8G{W?75jaTNR%`%f$s>>oMb2kwk;>mF
z3YQB$lxBWtTK!P7fMwbvt{s20HrDJG+!zP=&Oszf!%bXS9>v-Y=^&?G%A@I%w?ksu
z#E4~Bg0>qI0WxpPxQyaEIz)X^_bhZ(orCsXZ?DAI;3DJzYY6re#upJ7m8IIE3_FH>
zOQH`)cFeu)-Uh=!e8fBRPl<-QWWM~V@7Vpk+`=d7GBsCj1>T0X&x7QyTG4S9Ox7@E
zlB87GzE=_3iqq;$Bm`miJqsAbeb&IVluSQgC^o>ftULRn1(-W!r1Gr-stYaEG%&hrL&S(pgqbUeI!xKAGX1K+Q#xkt~`mrl0N+P@$
zXbH}BkTHfNny3U73N^Y>tVe1ZJy0&WK{k}0kUD(l#ef{Q?!G&bAJ;`A+Hp!*`W>a%0A@-RS&Ko)s9hBCl;
zfj3Ac^)Ap2E{s^tg|p>7twA@=f+>KF9c*#Rc}T~;P_gC?25zKMu|I)fkeHEIpTxCu
zqdQOZ*}_G{QVw_qnx`GUCRy^ZjDfqUgOAys<_cniBmS%>{_ZvWsSCZ!gh3|OwWbDT
zcoM{j!lSiJ6|%w{AirsNqwe^L*Bi&8c0HKNjejzp+?_X=9(23jhR8E4{>gqs03=D
zk~?g!kb0B=&rP#;BYZMT$V`@A2~jRME-i6GL}CM(-*~RpHgZ1;j_AU)1{&8BC-Hru
zBlLNSa>xh(4Gfd2>Ee(T|62j(@hek5VT
z&_zImbK*d&dqWWT;H4`CjUPZ3-?(YVp0ep{LIs!x4zvxiRV1j@A)Beu&DWF?aFy*p
z>s4=9{WoE^47|s3z*zbC7EZa=kt*m=)Bv`x*;+i?@fB2_a25
zfKzu8My+KGDb3Pj2wFl%L;b_sUk?%V?%P|m%lb%DdP>Qjb{#A}VnIU?YaQ6#S?Oaj
z$#gv?YtV$0$y9L)k1c^T$c5l&!07G0I
zW*B_4oa=pyji$dpKTxkkw;v*DcP%7jC_@@pm<1+lbC2#jX;56GuF)`{es_8CI~p*b
ztm=ioVDbQy!6)j6^
zTY0;R3-h4r@OG`|EyLxr>A=a(0Y{17+R>xp
zsG>0-eZ!|{iwk`Fl^B@+-tWmnUpL)|*NZFPV}Y7uuKK3g7ZUYb(3^P6ihR&cE#E&*
zn~wwY4OdQu>?dAr-!VQkHqHS**X4kq!Mo?uNt`bAaZT9mLX1-_of;N=
z8|{5O#oGxq2`XRROXpI1ICCm2uRuT7b?H{mQgj)ERK4|Z{@bW#b8bP_G5<6u18>DJ
z;`^S=d-+xw{ZXRvJLiw*_(O{6&61m5E`3`bm5NkE>>%Qhp*potRK!uLSOg2Dt2Y(i;4ld5RI=Bjg;17t4tCOOOl=xjzXc6Nb$349Fy)Sp)0YbCJR5K6<
zRLwFn$%L3Mtcbx^1TaW5j;O>;J(XR^!E=1w!^ii#7|-&q`*ZXwMUw$Ok$9HrhDE$i
zJiTe@ocD>ttRgAI=fqKiE=c^yb=l=N&Si%Mo*6cBnK|Mxu~_b6xrH9ABZ{g~
zzL0lW;k?CJt<_okp8SQOqPCpnI;~M8v4|AX5Fw+EDr&G0qg^A#M25~29{v%>pC*?~
zt|}Ng=CJ`4lH&*egWtV1OOq3BQYZoRyx8`~7!ceAS`FL&KDO=F3E+PQuC$JSqY2D>
zlHTZOkt1MW8@RacX!0I#xdRM8>5?HilAo4PDgp0j^i2g|=oaW(b^F#n$LRx*qh2lD
z00)P_c$u=-Jl@^g-M4>h+Wq?h%uRC3`8l*y0000^P)t-sEopB2V{HHc0003F0tFTU
z0uTTI4gmrX0t6KS0S^HJ5dZ-X0RayI0T2TP7y$wj0tFZU|Nr>;`|$Ad=;-R<;p6o5
z_p`LT!otbc*4U+h9qj-B00DGTPE!Ct=GbNc0004EOGiWihy@);00009a7bBm001r{
z001r{0eGc9b^rhX2XskIMF-;q5fL>Q?U=?90001eNklVgc9os+oV;9AV8Y2kEXcJta`D(bhQc#=^l=&ev#@qJVS9gyF(-e!
Y08JqVqWFQ;1poj507*qoM6N<$f@6cHLI3~&
literal 0
HcmV?d00001
diff --git a/share/aero_drill_array.png b/share/aero_drill_array.png
new file mode 100644
index 0000000000000000000000000000000000000000..7136f86db0863666e29ae42ef120ff6896db2198
GIT binary patch
literal 4996
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
O0000
Date: Wed, 17 Apr 2019 22:15:02 +0300
Subject: [PATCH 25/42] - added a warning regarding the fact that the loaded
Excellon file has no tool info about the diameters. This is the case for at
least the Excellon's generated by PCB Wizard.
---
camlib.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/camlib.py b/camlib.py
index 41d03454..178a66fe 100644
--- a/camlib.py
+++ b/camlib.py
@@ -4026,6 +4026,13 @@ class Excellon(Geometry):
# the bellow construction is so each tool will have a slightly different diameter
# starting with a default value, to allow Excellon editing after that
self.diameterless = True
+ self.app.inform.emit(_("[WARNING] No tool diameter info's. See shell.\n"
+ "A tool change event: T%s was found but the Excellon file "
+ "have no informations regarding the tool "
+ "diameters therefore the application will try to load it by "
+ "using some 'fake' diameters.\nThe user needs to edit the "
+ "resulting Excellon object and change the diameters to "
+ "reflect the real diameters.") % current_tool)
if self.excellon_units == 'MM':
diam = self.toolless_diam + (int(current_tool) - 1) / 100
From 839baea4b8e26cd30284eca02ecb1a47d6340589 Mon Sep 17 00:00:00 2001
From: Marius Stanciu
Date: Thu, 18 Apr 2019 16:35:50 +0300
Subject: [PATCH 26/42] - Gerber Editor: added custom mouse cursors for each
mode in Add Track Tool - Gerber Editor: Poligonize Tool will first fuse
polygons that touch each other and at a second try will create a polygon. The
polygon will be automatically moved to Aperture '0' (regions).
---
FlatCAMProcess.py | 4 +-
README.md | 4 ++
flatcamEditors/FlatCAMGrbEditor.py | 85 +++++++++++++++++++----------
share/aero_path.png | Bin 2712 -> 0 bytes
share/aero_path1.png | Bin 0 -> 6749 bytes
share/aero_path2.png | Bin 0 -> 6749 bytes
share/aero_path3.png | Bin 0 -> 6750 bytes
share/aero_path4.png | Bin 0 -> 6749 bytes
share/aero_path5.png | Bin 0 -> 6748 bytes
9 files changed, 61 insertions(+), 32 deletions(-)
delete mode 100644 share/aero_path.png
create mode 100644 share/aero_path1.png
create mode 100644 share/aero_path2.png
create mode 100644 share/aero_path3.png
create mode 100644 share/aero_path4.png
create mode 100644 share/aero_path5.png
diff --git a/FlatCAMProcess.py b/FlatCAMProcess.py
index d71e62d8..1556ab4a 100644
--- a/FlatCAMProcess.py
+++ b/FlatCAMProcess.py
@@ -134,13 +134,13 @@ class FCVisibleProcessContainer(QtCore.QObject, FCProcessContainer):
self.something_changed.connect(self.update_view)
def on_done(self, proc):
- self.app.log.debug("FCVisibleProcessContainer.on_done()")
+ # self.app.log.debug("FCVisibleProcessContainer.on_done()")
super(FCVisibleProcessContainer, self).on_done(proc)
self.something_changed.emit()
def on_change(self, proc):
- self.app.log.debug("FCVisibleProcessContainer.on_change()")
+ # self.app.log.debug("FCVisibleProcessContainer.on_change()")
super(FCVisibleProcessContainer, self).on_change(proc)
self.something_changed.emit()
diff --git a/README.md b/README.md
index 302b830f..ceb4c452 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+18.04.2019
+- Gerber Editor: added custom mouse cursors for each mode in Add Track Tool
+- Gerber Editor: Poligonize Tool will first fuse polygons that touch each other and at a second try will create a polygon. The polygon will be automatically moved to Aperture '0' (regions).
+
17.04.2019
- Gerber Editor: added some messages to warn user if no selection exists when trying to do aperture deletion or aperture geometry deletion
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index 9d23a39c..67177756 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -540,7 +540,6 @@ class FCPoligonize(FCShapeTool):
return ""
def make(self):
-
if not self.draw_app.selected:
self.draw_app.in_action = False
self.complete = True
@@ -548,20 +547,22 @@ class FCPoligonize(FCShapeTool):
self.draw_app.select_tool("select")
return
- try:
- current_storage = self.draw_app.storage_dict['0']['solid_geometry']
- except KeyError:
- self.draw_app.on_aperture_add(apid='0')
- current_storage = self.draw_app.storage_dict['0']['solid_geometry']
-
fused_geo = [Polygon(sh.geo.exterior) for sh in self.draw_app.selected]
-
fused_geo = MultiPolygon(fused_geo)
fused_geo = fused_geo.buffer(0.0000001)
+
+ current_storage = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry']
if isinstance(fused_geo, MultiPolygon):
for geo in fused_geo:
self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(geo))
else:
+ if len(fused_geo.interiors) == 0:
+ try:
+ current_storage = self.draw_app.storage_dict['0']['solid_geometry']
+ except KeyError:
+ self.draw_app.on_aperture_add(apid='0')
+ current_storage = self.draw_app.storage_dict['0']['solid_geometry']
+
self.draw_app.on_grb_shape_complete(current_storage, specific_shape=DrawToolShape(fused_geo))
self.draw_app.delete_selected()
@@ -694,9 +695,9 @@ class FCRegion(FCShapeTool):
else:
self.inter_point = (x, old_y + self.gridy_size * mx)
elif self.mode == 3:
- self.inter_point = (old_x, y)
- elif self.mode == 4:
self.inter_point = (x, old_y)
+ elif self.mode == 4:
+ self.inter_point = (old_x, y)
if self.inter_point is not None:
self.temp_points.append(self.inter_point)
@@ -761,9 +762,9 @@ class FCRegion(FCShapeTool):
else:
self.inter_point = (x, old_y + self.gridy_size * mx)
elif self.mode == 3:
- self.inter_point = (old_x, y)
- elif self.mode == 4:
self.inter_point = (x, old_y)
+ elif self.mode == 4:
+ self.inter_point = (old_x, y)
self.temp_points.append(self.inter_point)
self.temp_points.append(data)
@@ -861,7 +862,7 @@ class FCTrack(FCRegion):
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
- self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path.png'))
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path1.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
@@ -950,9 +951,9 @@ class FCTrack(FCRegion):
else:
self.temp_points.append((x, old_y + self.gridy_size * mx))
elif self.mode == 3:
- self.temp_points.append((old_x, y))
- elif self.mode == 4:
self.temp_points.append((x, old_y))
+ elif self.mode == 4:
+ self.temp_points.append((old_x, y))
else:
pass
@@ -973,20 +974,34 @@ class FCTrack(FCRegion):
return _("Backtracked one point ...")
if key == 'T' or key == QtCore.Qt.Key_T:
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
if self.mode == 1:
self.mode = 2
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path2.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 2: Reverse 45 degrees ...')
elif self.mode == 2:
self.mode = 3
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path3.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 3: 90 degrees ...')
elif self.mode == 3:
self.mode = 4
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path4.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 4: Reverse 90 degrees ...')
elif self.mode == 4:
self.mode = 5
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path5.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 5: Free angle ...')
else:
self.mode = 1
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path1.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 1: 45 degrees ...')
# Remove any previous utility shape
@@ -997,20 +1012,34 @@ class FCTrack(FCRegion):
return msg
if key == 'R' or key == QtCore.Qt.Key_R:
+ try:
+ QtGui.QGuiApplication.restoreOverrideCursor()
+ except:
+ pass
if self.mode == 1:
self.mode = 5
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path5.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 5: Free angle ...')
elif self.mode == 5:
self.mode = 4
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path4.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 4: Reverse 90 degrees ...')
elif self.mode == 4:
self.mode = 3
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path3.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 3: 90 degrees ...')
elif self.mode == 3:
self.mode = 2
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path2.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 2: Reverse 45 degrees ...')
else:
self.mode = 1
+ self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path1.png'))
+ QtGui.QGuiApplication.setOverrideCursor(self.cursor)
msg = _('Track Mode 1: 45 degrees ...')
# Remove any previous utility shape
@@ -1477,30 +1506,26 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apdim_entry = EvalEntry2()
grid1.addWidget(self.apdim_entry, 4, 1)
- apadd_lbl = QtWidgets.QLabel('%s' % _('Add Aperture:'))
- apadd_lbl.setToolTip(
- _("Add an aperture to the aperture list")
+ apadd_del_lbl = QtWidgets.QLabel('%s' % _('Add/Delete Aperture:'))
+ apadd_del_lbl.setToolTip(
+ _("Add/Delete an aperture in the aperture table")
)
- grid1.addWidget(apadd_lbl, 5, 0)
+ self.apertures_box.addWidget(apadd_del_lbl)
- self.addaperture_btn = QtWidgets.QPushButton(_('Go'))
+ hlay_ad = QtWidgets.QHBoxLayout()
+ self.apertures_box.addLayout(hlay_ad)
+
+ self.addaperture_btn = QtWidgets.QPushButton(_('Add'))
self.addaperture_btn.setToolTip(
_( "Add a new aperture to the aperture list.")
)
- grid1.addWidget(self.addaperture_btn, 5, 1)
- apdelete_lbl = QtWidgets.QLabel('%s' % _('Del Aperture:'))
- apdelete_lbl.setToolTip(
- _( "Delete a aperture in the aperture list.\n"
- "It will delete also the associated geometry.")
- )
- grid1.addWidget(apdelete_lbl, 6, 0)
-
- self.delaperture_btn = QtWidgets.QPushButton(_('Go'))
+ self.delaperture_btn = QtWidgets.QPushButton(_('Delete'))
self.delaperture_btn.setToolTip(
_( "Delete a aperture in the aperture list")
)
- grid1.addWidget(self.delaperture_btn, 6, 1)
+ hlay_ad.addWidget(self.addaperture_btn)
+ hlay_ad.addWidget(self.delaperture_btn)
### BUFFER TOOL ###
diff --git a/share/aero_path.png b/share/aero_path.png
deleted file mode 100644
index 10e4a6dfc6e8a98f3a3c4d4d85d76037bcc7ecb4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 2712
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
JtI=`2@K67)Xd(ar
literal 0
HcmV?d00001
diff --git a/share/aero_path2.png b/share/aero_path2.png
new file mode 100644
index 0000000000000000000000000000000000000000..d528c860446cd97d7c2424d3c62246860fce83ca
GIT binary patch
literal 6749
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
K4pU@&GV)&~&wTFy
literal 0
HcmV?d00001
diff --git a/share/aero_path3.png b/share/aero_path3.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3d7bac4fae427e26fc924ef87194e35ecd06a6c
GIT binary patch
literal 6750
zcmeH}cT^ME9>*skflvaWBSpeNloAxBh=8HDCnW&{L^Oh60O^8s1f)bj7C`}}gAq_c
zkg|x<~_(F|ZrjNa&kdp`g$&j(xC`dK&xAiaIP&bhifBmIKBosrIguIB(C
z@ZDz{j}(!|@O6si1Pr1DwD{0s?hYc!<`?!n_*{)Uos%kFa-Ddpoj{F1)`65>u8~J7
z*Wm}OKN3p<*em005(G57yFPyWQfJ{onO;iS*u2^xexQ9=Qz3SuBQ&gfDMwSG3@1Ln
zvO9O=CqbN-`i2Jq&C#e)D>p*_dEbVI5lz`_Zcb{Li-UeneeTj}uS&1++{VL(3N=r2
zyvHQ>eEG6wXFM7c&0#NsHdY(q4w{uiAE%OOH|QSI6egO_s!aHo1pK11=p=vtDum8g
zw5s~P$Y;$qwsDC-JL|mJQ2`Wnvuy+sb^Km|nqSy*fkA8`GGn<)G59G>=
z*~%mL2~Ba~W=c9`uOEnaeVlnt9q(@;Iu9aU1=uox5C2uJGKo*d&~
zMHCwpI9X-)UqO&LLzy-3t{RbmD|UE3C6fRWbzy*5RRG#}l13l>|rx@maRUtVk
zH=LRX>9u%ByhFrOo-afS*LSU`zeP)~KEM_pc3TJhr`K%WDty``L56$_1Qn63S0ie1
z5G|L6jBc~i1^rn^>&rxf^s{62qwaMCQ1eBC@_VMo51wyGctSfj?$s_(lH~;JwwQe<
zbynfNn$#8Ew=01!N;QBXl%5Gz0{T9VwTNqWe1(XsOT&fD|C~P(yD1LuRDoS-zE;0K
zza5KVyUnHU=zozI+qfy$`m*uJLY>0h!UmHEzUQ*pJ^FZ+iF-c`%CuzSJEriRt|2a;
zZX7gU%Bb+msmG6;pX%_pU&)X@XpHEVj-COEEv0YYA$gH3?3A-G6&QcLm8??J=}s)s
ztmTn>bud0|SxtrOiB=2+DwXQ5t|XWqd0u7tWDMHw(Vo{qTsBr}Z6y~``5EQ1pIm2O
zo9Q^=D
z4acFcI16LRuR5vX2!~!i-HnEMI(zRTo)>xluxE$6NDyJPkgMBy!wUT#xQl6;|N8Xz;PVbT>IxRG+D=in$_@`;k^wSDu1zP?Mz=
zVB=+t>K1Pa1Ea2;cN2DSXZbr({cE8)m2@A9T`7H;x51~!yVvOU($J!LJr7*MQZkJ3Mbpb0-$fDYw9i=Y(p
zCO?;7eN;7Z1Z$W~QM;ke$CV`iJQKVrU1b#$xX`plbj7J&kabj$P^|5S>gZDUxG}xB
znvWPR_wpgh?4x|rQnB&T^$jq5$|BU37=jZX|3`rj)h)MKL{`SJtldM~I>oJJ
zC^Cpx8|XNsUvo`IL~Q|`;UUg8&{u$RddM=E<;&GDswvx}n)IUcsep@TZwo4J()vCT
z-O^O$10AkFcV7!TlarH1nAXY`Wgj)gutke7-Q*z%Ih`Cnqx&gxZavp3dD<>~ktraL
zH{B~-k%uZ=&H3nVT$Au?0}T{>-GV0W9R%f1+pX05i5+$GXd?Ef0jZ>fvZQDGV0^u^
z@(>4pe`SzT)1#|!OCSo&KY3yLQi*-o6qWpA(fdNw?>%Sx7A8m+EM*
zOwNj*Xbml6dAwLl>h>>8=8lIhzKYYO;Bpb`1oq#}IQOQ9Yp2HU
zA(61&@~X57TYqn#p6=%32#BVZy?r*{soO1E#2A5tyG2*Whw8~g>RQF=H{HJO1&&uH
zI-z9GJ-k=2#gAT=jPOh}e)V>|grsCo?memzTAwhFTD@>7>9iF7P?IDN<={{`;V=QO
zBA>m4_95^~#n7?>2lLo@G<{EbE{aS?Jp)rXcDcT3}*
zE1ej7LOLODbK+ypdQ5XxO$t@5-JzvCT9pSOQ5etjq8yQbT#}@hvQoHzQ-Yj}OgAax
z5jrA(iDYYAxV+Uo=h?pg)=5!D2wHdAoqiTFKL@^g%j1&xz{PRMJetYIZU9%pRGE9b
z_njRLHZ#Dyzii;v6Bhef;Iet0lTp$HQD$PPiM}h$Qb4E{3O?UHY)4lO1KzytUW9vl
z!x9CgLxv`Yj%L?ygxu*K;W7{My=kH>A;#E~9K)Dc=}T~wGg1J=35E)Qzz6`~Wb|MN
zKM2GH0&@e5{@a0If(n7aJYcBc*47qnjXpoWG&A#IY;1gad9||Y$Yj
zJ<89*%o5O=>SOF_0FdJ`Lp^{16*FR7uzDMvxBvkACBHRLAW`!oLuBzYHq~bthw!km
zGws=2-2wo_ma(3;l`Si18Nois7_k#O4(vFvJ%+kk#U
zz=*$tgAsoRXV8BG|3+^g|ApQ*{x5pF_^&kg_MHEfM(}J4|2K``-46a6jo{k`{*C5m
ztl78t?FIbdghBH|1-9=wKNNr=_+UR=?~en)3>aV)M6mu*{NWtJfB}FFA+){XztmwB
zLU4SqAn1GIpEWklKWhx-2kGy!JM@3CYtwRegpsj
literal 0
HcmV?d00001
diff --git a/share/aero_path4.png b/share/aero_path4.png
new file mode 100644
index 0000000000000000000000000000000000000000..4dc7b9ab68bab53b31f71b6fffac24f6f7c663d7
GIT binary patch
literal 6749
zcmeH}XH*ke8-OPvp$9@oYWNUEq$wRj=xynUG@}7Tno^~g2nZMvSp=mh9fN?l$lmGz0v@}(X
zh(~$iu1-!u>^04A3;+P>lAp1ehmn;x!qwfy*5SrAgom%|HN-U^2U`H}`S``uDTe(O
zY`6WwBsoM8XmHyK%l(9C(!Y7K;7cjyN=htu&O`j=R@`_Hq7o$jZo6ruco!yOvVhO=
zrYepwy~m7n?OIs)T506eKC>3Hw|~EeTcmYZK{R}_Eg-OTEk!{zAH%)8!Ie7lGYiH=
zZqEsaqOL`jpB>rx<@BDD=9Yxs(h}b=E%lDA+|sQpF2ybrskKrXqGhjBT*r7%e*L;_
zp*0#7N{wbmX~~Yz`7SQuGMvs1oY)?Eq%34WS=hO~(1|SX#Ci!bKJsZLJ6Sd&b#~fhCREKJ+A&(p
zthv;ppV^D6Q@RN)kgK7mYy9KeNov3Cw;4?1WemR*NCz`7-xIku)~L3KwPps=1DcFe
zw~Tx*&(HKhO@h);R*U_k*=yIV1p=qykkZ;cAvt+9t6>4PFodevr7>y>c(!_mjdAk8
zU3e2s04dVW0T~Q97!EJTJi&oDY)E<=#q`?ICRkWz6+q6!Kqjt<2l2CHeX7fOiU
zvb0=%IFP0H1ho?>!#rF3@x!%urkJ9}X(4vLv>SspJ5lm9oCb3xnN3lt*zrh6pV3Qv
z7T&JCc_o;wx@$wO8^ya>V3;Z8xC?mBY@2qAe%>cQhTJn)#1M`5gUT@wMf;kB9=*I3
zwMA=_+jt!R8|~`R8l0Fi?&kgI8B)?n#pjtS+)zEYSyIPr*O3~cR8l3`n+hi)Ieg+BrsrgT58lKp-P1|)k_}hPebWVRQp~xep
z+HU0fbeku7BZ2>%7QBZ)bQZ|I!2dLh;6gC6kVrBpGVoO0NXpZl=`_UaHB<IN9M#YicaplH(1f
zg!83+hH_b`Sy-97pue+f#}XrF;B`rrZT%@RH@8R?b
zJGS^2vL2<;Jv$8pIl|Qu^_@GdCXy{Rk2w9x?8shrG|i561xP^V-aqgh&=HBiroh;w
z(L&F*APa+O)v}(qTm2jW<*rKgE2AIIdRqh{I{o_vFwWSoek)ZSaYC|k={zLKdNI!k
zEi`ry{3EnOOf)lagj1S&Pk7p$A$*zos=A};7fyi2c`vdoS@{cQKR!_eJMY2UoR+tN
zMI^Zv)=iMnHBA_h55*JAv|H(>O*mG)vz5_;YL@UVwq{Z2Q2Lvo_DqK8W}(fBl1Uju
zjp%mSM{kS^X~1yry{xRDvX0@
zwItGva7b-i`6-pkkYU44&(W3>T(i1Q?}kUDghWm4pc%?mA~Tuz+FFaFlOiS?1MJLY$5C#b3WOH>&V;<~X-f!leCS=r$
z)7dGxJlax2EU=)-pkAd9Jq?dJ#=Z2$m;IjE=@XsM_m42<;EwRF^o
zKfI*C2lAS46gdc(E=RmE1(=2SdZDK`8hfnew=B)wH+
z=IQFz+f#oL4pC4a6LXVDH=LQEN8zXX7xvi=T@6-hJ^Ywvar3b%g1L~X
z`9jKWSbb7i%(!f;RYPH@G(DUrGlKqYAw2yeFF`eCBU5;vrzsT?uai&DD#L6LOxdz>
z`=EZwxplYOM(hkLwDQW0ovV=LCGh>nPPe!Ry(S>bC=yePK}-%waq82)j}}|xvxB6<
z`Gb$E$k23t-h~&qK*U)Cvi!lZJ=YUcGJ9p7y=*~Fq9bth64Z%u?It#Kp