Merged in test_beta914 (pull request #143)

Test beta914
This commit is contained in:
Marius Stanciu 2019-04-23 19:06:02 +00:00
commit 79b402d198
45 changed files with 11796 additions and 6877 deletions

View File

@ -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/23"
beta = True
# current date now
@ -1838,6 +1838,7 @@ class App(QtCore.QObject):
'mpf']
self.svg_list = ['svg']
self.dxf_list = ['dxf']
self.pdf_list = ['pdf']
self.prj_list = ['flatprj']
# global variable used by NCC Tool to signal that some polygons could not be cleared, if True
@ -2003,9 +2004,15 @@ class App(QtCore.QObject):
self.properties_tool = Properties(self)
self.properties_tool.install(icon=QtGui.QIcon('share/properties32.png'), pos=self.ui.menuoptions)
self.pdf_tool = ToolPDF(self)
self.pdf_tool.install(icon=QtGui.QIcon('share/pdf32.png'), pos=self.ui.menufileimport,
separator=True)
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)
self.log.debug("Tools are installed.")
@ -2090,12 +2097,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):
@ -2106,7 +2110,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)
@ -2153,16 +2158,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 = ""
@ -2241,6 +2238,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
@ -4472,7 +4471,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):
@ -4510,7 +4508,7 @@ class App(QtCore.QObject):
jump_loc = self.plotcanvas.vispy_canvas.translate_coords_2((location[0], location[1]))
cursor.setPos(canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
self.inform.emit(_("Done."))
self.inform.emit(_("[success] Done."))
def on_copy_object(self):
self.report_usage("on_copy_object()")
@ -5117,9 +5115,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 +5310,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:
@ -5662,7 +5657,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):
"""
@ -7085,7 +7080,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."))
@ -7606,6 +7601,7 @@ class App(QtCore.QObject):
"project": "share/project16.png",
"svg": "share/geometry16.png",
"dxf": "share/dxf16.png",
"pdf": "share/pdf32.png",
"image": "share/image16.png"
}
@ -7613,11 +7609,13 @@ class App(QtCore.QObject):
openers = {
'gerber': lambda fname: self.worker_task.emit({'fcn': self.open_gerber, 'params': [fname]}),
'excellon': lambda fname: self.worker_task.emit({'fcn': self.open_excellon, 'params': [fname]}),
'geometry': lambda fname: self.worker_task.emit({'fcn': self.import_dxf, 'params': [fname]}),
'cncjob': lambda fname: self.worker_task.emit({'fcn': self.open_gcode, 'params': [fname]}),
'project': self.open_project,
'svg': self.import_svg,
'dxf': self.import_dxf,
'image': self.import_image
'image': self.import_image,
'pdf': lambda fname: self.worker_task.emit({'fcn': self.pdf_tool.open_pdf, 'params': [fname]})
}
# Open file
@ -7781,7 +7779,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
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) + \

View File

@ -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())

View File

@ -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()

View File

@ -9,6 +9,91 @@ CAD program, and create G-Code for Isolation routing.
=================================================
23.04.2019
- Gerber Editor: added two new tools: Add Disc and Add SemiDisc (porting of Circle and Arc from Geometry Editor)
- Gerber Editor: made Add Pad repeat until user exits the Add Pad through either mouse right click, or ESC key or deselecting the Add Pad menu item
- Gerber and Geometry Editors: fixed some issues with the Add Arc/Add Semidisc; in mode 132, the norm() function was not the one from numpy but from a FlatCAM Class. Also fixed some of the texts and made sure that when changing the mode, the current points are reset to prepare for the newly selected mode.
- Fixed Measurement Tool to show the mouse coordinates on the status bar (it was broken at some point)
- updated the translation files
- added more custom mouse cursors in Geometry and Gerber Editors
- RELEASE 8.914
22.04.2019
- added PDF file as type in the Recent File list and capability to load it from there
- PDF's can be drag & dropped on the GUI to be loaded
- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7
- PDF Import tool: added support for PDF files that embed multiple Gerber layers (top, bottom, outline, silkscreen etc). Each will be opened in it's own Gerber file. The requirement is that each one is drawn in a different color
- PDF Import tool: fixed bugs when drag & dropping PDF files on canvas the files geometry previously opened was added to the new one. Also scaling issues. Solved.
- PDF Import tool: added support for detection of circular geometry drawn with white color which means actually invisible color. When detected, FlatCAM will build an Excellon file out of those geoms.
- PDF Import tool: fixed storing geometries in apertures with the right size (before they were all stored in aperture D10)
21.04.2019
- fixed the PDF import tool to work with files generated by the Microsoft PDF printer (chained subpaths)
- in PDF import tool added support for paths filled and at the same time stroked ('B' and 'B*'commands)
- added a shortcut key for PDF Import Tool (ALT+Q) and updated the Shortcut list (also with the 'T' and 'R' keys for Gerber Editor where they control the bend in Track and Region tool and the 'M' and 'D' keys for Add Arc tool in Geometry Editor)
20.04.2019
- finished adding the PDF import tool although it does not support all kinds of outputs from PDF printers. Microsoft PDF printer is not supported.
19.04.2019
- started to work on PDF import tool
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).
- Gerber Editor: Region Tool will add regions only in '0' aperture
- Gerber Editor: the bending mode will now survive until the tool is exited
- Gerber Editor: solved some bugs related with deleting an aperture and updating the last_selected_aperture
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
- 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
- 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.
- 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
- 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 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)
- 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
- 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
- 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
- solved bug in Gerber apertures size and dimensions values conversion when file units are different than app units
13.04.2019
- updating the German translation

172
camlib.py
View File

@ -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 *
@ -2160,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
@ -3178,21 +3183,32 @@ 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 = []
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 = MultiPolygon(self.apertures[apid]['clear_geometry'])
for solid_geo in self.apertures[apid]['solid_geometry']:
if clear_geo.intersects(solid_geo):
res_geo = solid_geo.difference(clear_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)
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
self.follow_geometry = follow_buffer
@ -3722,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
@ -3750,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!
@ -3815,13 +3833,17 @@ 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*)')
# 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()``.
@ -3830,9 +3852,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:
@ -3900,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:
@ -3910,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
@ -3924,6 +3953,17 @@ 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 in comments: %s:%s" %
(match.group(1), match.group(2)))
continue
else:
log.warning("Line ignored, it's a comment: %s" % eline)
else:
@ -3986,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
@ -4351,8 +4398,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])
@ -4401,8 +4456,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])
@ -7346,6 +7409,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.
@ -7581,13 +7701,17 @@ def three_point_circle(p1, p2, p3):
b2 = dot((p3 - p2), array([[0, 1], [-1, 0]], dtype=float32))
# Params
T = solve(transpose(array([-b1, b2])), a1 - a2)
try:
T = solve(transpose(array([-b1, b2])), a1 - a2)
except Exception as e:
log.debug("camlib.three_point_circle() --> %s" % str(e))
return
# Center
center = a1 + b1 * T[0]
# Radius
radius = norm(center - p1)
radius = np.linalg.norm(center - p1)
return center, radius, T[0]

View File

@ -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
@ -45,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:
@ -80,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:
@ -135,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:
@ -250,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:
@ -535,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
@ -695,12 +726,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 +757,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("<b>%s</b>" % _('Tools Table'))
self.tools_table_label.setToolTip(
@ -1021,7 +1049,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)
@ -1134,6 +1162,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 +1206,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 +1300,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
@ -1428,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)
@ -1568,6 +1603,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 +1646,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 +1709,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_canvas_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)
# 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 +1791,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 +2042,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:
@ -2136,7 +2186,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()

View File

@ -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 as numpy_norm
from rtree import index as rtindex
from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
@ -1931,14 +1932,22 @@ class FCCircle(FCShapeTool):
DrawTool.__init__(self, draw_app)
self.name = 'circle'
self.start_msg = _("Click on CENTER ...")
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_circle_geo.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.start_msg = _("Click on Center point ...")
self.draw_app.app.inform.emit(_("Click on Center point ..."))
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
def click(self, point):
self.points.append(point)
if len(self.points) == 1:
self.draw_app.app.inform.emit(_("Click on Circle perimeter point to complete ..."))
self.draw_app.app.inform.emit(_("Click on Perimeter point to complete ..."))
return "Click on perimeter to complete ..."
if len(self.points) == 2:
@ -1957,6 +1966,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)
@ -1970,7 +1984,15 @@ class FCArc(FCShapeTool):
DrawTool.__init__(self, draw_app)
self.name = 'arc'
self.start_msg = _("Click on CENTER ...")
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_arc.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.start_msg = _("Click on Center point ...")
self.draw_app.app.inform.emit(_("Click on Center point ..."))
# Direction of rotation between point 1 and 2.
# 'cw' or 'ccw'. Switch direction by hitting the
@ -1989,11 +2011,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 ..."))
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 to complete ..."))
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:
@ -2003,18 +2035,25 @@ 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 == 'M' or key == QtCore.Qt.Key_M:
# delete the possible points made before this action; we want to start anew
self.points[:] = []
# and delete the utility geometry made up until this point
self.draw_app.delete_utility_geometry()
if key == 'p':
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 Point1 ...')
else:
self.mode = 'c12'
return 'Mode: ' + self.mode
return _('Mode: Center -> Start -> Stop. Click on Center point ...')
def utility_geometry(self, data=None):
if len(self.points) == 1: # Show the radius
@ -2043,7 +2082,11 @@ class FCArc(FCShapeTool):
p3 = array(self.points[1])
p2 = array(data)
center, radius, t = three_point_circle(p1, p2, p3)
try:
center, radius, t = three_point_circle(p1, p2, p3)
except TypeError:
return
direction = 'cw' if sign(t) > 0 else 'ccw'
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
@ -2065,7 +2108,7 @@ class FCArc(FCShapeTool):
# Perpendicular vector
b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
b /= norm(b)
b /= numpy_norm(b)
# Distance
t = distance(data, a)
@ -2078,7 +2121,7 @@ class FCArc(FCShapeTool):
# Center = a + bt
center = a + b * t
radius = norm(center - p1)
radius = numpy_norm(center - p1)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
@ -2128,7 +2171,7 @@ class FCArc(FCShapeTool):
# Perpendicular vector
b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
b /= norm(b)
b /= numpy_norm(b)
# Distance
t = distance(pc, a)
@ -2141,7 +2184,7 @@ class FCArc(FCShapeTool):
# Center = a + bt
center = a + b * t
radius = norm(center - p1)
radius = numpy_norm(center - p1)
startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
@ -2160,6 +2203,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):
@ -2183,6 +2233,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])])
@ -2200,6 +2255,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):
@ -2226,6 +2288,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
@ -2233,20 +2300,39 @@ 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):
"""
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_path5.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."))
@ -2260,9 +2346,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):
@ -2270,6 +2361,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
@ -2442,6 +2538,14 @@ class FCText(FCShapeTool):
FCShapeTool.__init__(self, draw_app)
self.name = 'text'
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except:
pass
self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_text.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
# self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app
self.app = draw_app.app
@ -2832,6 +2936,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
@ -2864,14 +2975,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)
@ -2919,10 +3033,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)
@ -2938,9 +3048,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)
@ -2948,23 +3063,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_canvas_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)
# 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.
@ -3287,7 +3399,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():

File diff suppressed because it is too large Load Diff

View File

@ -470,6 +470,12 @@ 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_semidisc_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/semidisc32.png'),
_("Add SemiDisc\tE"))
self.grb_add_disc_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/disc32.png'),
_("Add Disc\tD"))
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,6 +696,12 @@ 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/poligonize32.png'),
_("Poligonize"))
self.grb_add_semidisc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/semidisc32.png'), _("SemiDisc"))
self.grb_add_disc_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/disc32.png'), _("Disc"))
self.grb_edit_toolbar.addSeparator()
self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer'))
@ -965,6 +977,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr height="20">
<td height="20"><strong>B</strong></td>
<td>&nbsp;New Gerber</td>
</tr>
<tr height="20">
<td height="20"><strong>E</strong></td>
<td>&nbsp;Edit Object (if selected)</td>
@ -1141,6 +1157,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>ALT+P</strong></td>
<td>&nbsp;Paint Area Tool</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+Q</strong></td>
<td>&nbsp;PDF Import Tool</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+R</strong></td>
<td>&nbsp;Transformations Tool</td>
@ -1233,6 +1253,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>C</strong></td>
<td>&nbsp;Copy Geo Item</td>
</tr>
<tr height="20">
<td height="20"><strong>D</strong></td>
<td>&nbsp;Within Add Arc will toogle the ARC direction: CW or CCW</td>
</tr>
<tr height="20">
<td height="20"><strong>E</strong></td>
<td>&nbsp;Polygon Intersection Tool</td>
@ -1253,6 +1277,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>M</strong></td>
<td>&nbsp;Move Geo Item</td>
</tr>
<tr height="20">
<td height="20"><strong>M</strong></td>
<td>&nbsp;Within Add Arc will cycle through the ARC modes</td>
</tr>
<tr height="20">
<td height="20"><strong>N</strong></td>
<td>&nbsp;Draw a Polygon</td>
@ -1431,6 +1459,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>C</strong></td>
<td>&nbsp;Copy</td>
</tr>
<tr height="20">
<td height="20"><strong>D</strong></td>
<td>&nbsp;Add Disc</td>
</tr>
<tr height="20">
<td height="20"><strong>E</strong></td>
<td>&nbsp;Add SemiDisc</td>
</tr>
<tr height="20">
<td height="20"><strong>J</strong></td>
<td>&nbsp;Jump to Location (x, y)</td>
@ -1447,6 +1483,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>P</strong></td>
<td>&nbsp;Add Pad</td>
</tr>
<tr height="20">
<td height="20"><strong>R</strong></td>
<td>&nbsp;Within Track & Region Tools will cycle in REVERSE the bend modes</td>
</tr>
<tr height="20">
<td height="20"><strong>S</strong></td>
<td>&nbsp;Scale</td>
@ -1455,6 +1495,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>T</strong></td>
<td>&nbsp;Add Track</td>
</tr>
<tr height="20">
<td height="20"><strong>T</strong></td>
<td>&nbsp;Within Track & Region Tools will cycle FORWARD the bend modes</td>
</tr>
<tr height="20">
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
@ -1952,6 +1996,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:
@ -2078,6 +2126,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.paint_tool.run(toggle=True)
return
# Paint Tool
if key == QtCore.Qt.Key_Q:
self.app.pdf_tool.run()
return
# Transformation Tool
if key == QtCore.Qt.Key_R:
self.app.transform_tool.run(toggle=True)
@ -2380,132 +2433,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':
@ -2525,11 +2576,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':
@ -2599,114 +2654,126 @@ 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
# Add Disc Tool
if key == QtCore.Qt.Key_D or key == 'D':
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.select_tool('disc')
return
# Add SemiDisc Tool
if key == QtCore.Qt.Key_E or key == 'E':
self.app.grb_editor.launched_from_shortcuts = True
self.app.grb_editor.select_tool('semidisc')
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':
@ -2909,6 +2976,23 @@ 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.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:
@ -2962,6 +3046,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.worker_task.emit({'fcn': self.app.import_dxf,
'params': [self.filename, object_type, None]})
if extension in self.app.pdf_list:
self.app.worker_task.emit({'fcn': self.app.pdf_tool.open_pdf,
'params': [self.filename]})
if extension in self.app.prj_list:
# self.app.open_project() is not Thread Safe
self.app.open_project(self.filename)

View File

@ -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):

View File

@ -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()

View File

@ -103,33 +103,44 @@ 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
self.rel_point1 = None
self.rel_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[:] = []
self.rel_point1 = None
self.rel_point2 = None
if self.app.tool_tab_locked is True:
return
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])
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)
@ -154,123 +165,114 @@ 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(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')
def activate_measure_tool(self):
# 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)
self.canvas.vis_connect('key_release', self.on_key_release_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(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)
def deactivate_measure_tool(self):
# DISABLE the Measuring TOOL
self.active = False
self.points = []
# reconnect the mouse/key events to the functions from where the tool was called
self.canvas.vis_connect('key_press', self.app.ui.keyPressEvent)
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('key_press', self.app.geo_editor.on_canvas_key)
self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release)
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('key_press', self.app.exc_editor.on_canvas_key)
self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
elif self.app.call_source == 'grb_editor':
self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release)
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('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)
# 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 False or activate is None:
# DISABLE the Measuring TOOL
self.deactivate()
# self.app.ui.notebook.setTabText(2, _("Tools"))
# self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
self.app.command_active = None
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")
elif activate is True:
# ENABLE the Measuring TOOL
self.clicked_meas = 0
log.debug("Measurement Tool --> exit tool")
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
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():
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
# Reset here the relative coordinates so there is a new reference on the click position
if self.rel_point1 is None:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0.0, 0.0))
self.rel_point1 = pos
else:
self.rel_point2 = copy(self.rel_point1)
self.rel_point1 = 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.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)
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(
@ -279,34 +281,47 @@ class Measurement(FlatCAMTool):
self.distance_x_entry.set_value('%.4f' % abs(dx))
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.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (pos[0], pos[1]))
self.deactivate_measure_tool()
def on_mouse_move_meas(self, event):
pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
try: # May fail in case mouse not within axes
pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
if self.app.grid_status():
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[0], pos_canvas[1])
self.app.app_cursor.enabled = False
# 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])
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
if self.rel_point1 is not None:
dx = pos[0] - self.rel_point1[0]
dy = pos[1] - self.rel_point1[1]
else:
dx = pos[0]
dy = pos[1]
self.point2 = (pos[0], pos[1])
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (pos[0], pos[1]))
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
# update utility geometry
if len(self.points) == 1:
self.utility_geometry(pos=pos)
except:
self.app.ui.position_label.setText("")
self.app.ui.rel_position_label.setText("")
# 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)
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()

View File

@ -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)

1001
flatcamTools/ToolPDF.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -0,0 +1,466 @@
############################################################
# 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
from datetime import datetime
from io import StringIO
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("<b>Load files:</b>"))
# 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("<b>Excellon format:</b>"))
# 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': 'LZ'},
{'label': 'TZ', 'value': 'TZ'},
{'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("<b>%s:</b>" % _('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
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(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 = 'LZ'
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):
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)
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.modified_excellon_file = ''
self.build_ui()
def build_ui(self):
sorted_tools = []
if not self.tools_from_inf:
self.tools_table.setVisible(False)
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': [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+)$')
format_re = re.compile(r'^(\d+)\.?(\d+)\s*format,\s*(inches|metric)?,\s*(absolute|incremental)?.*$')
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
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"
"Try to open the Excellon file from File -> Open -> Excellon\n"
"and edit the drill diameters manually."))
return "fail"
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()
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:
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=None, excellon_fileobj=None):
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 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
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] 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(_('[ERROR_NOTCL] The imported Excellon file is None.'))

View File

@ -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)

View File

@ -14,5 +14,7 @@ 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.ToolPDF import ToolPDF
from flatcamTools.ToolShell import FCShell

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
share/aero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
share/aero_arc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
share/aero_array.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
share/aero_buffer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
share/aero_circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
share/aero_circle_geo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
share/aero_disc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
share/aero_drill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
share/aero_drill_array.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
share/aero_path1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
share/aero_path2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
share/aero_path3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
share/aero_path4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
share/aero_path5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
share/aero_semidisc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
share/aero_text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
share/disc32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

BIN
share/pdf32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
share/poligonize32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

BIN
share/semidisc32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B