Merged in beta_8.905 (pull request #130)

Beta 8.905
This commit is contained in:
Marius Stanciu 2019-01-29 21:05:47 +00:00
commit 13cae5d0be
26 changed files with 2176 additions and 1031 deletions

View File

@ -6,6 +6,7 @@ from FlatCAMApp import App
from multiprocessing import freeze_support
import VisPyPatches
if sys.platform == "win32":
# cx_freeze 'module win32' workaround
import OpenGL.platform.win32

File diff suppressed because it is too large Load Diff

View File

@ -291,7 +291,7 @@ class TextInputTool(FlatCAMTool):
font_name=self.font_name,
font_size=font_to_geo_size,
font_type=font_to_geo_type,
units=self.app.general_options_form.general_group.units_radio.get_value().upper())
units=self.app.general_options_form.general_app_group.units_radio.get_value().upper())
def font_family(self, font):
self.text_input_entry.selectAll()
@ -613,6 +613,7 @@ class FCCircle(FCShapeTool):
radius = distance(p1, p2)
self.geometry = DrawToolShape(Point(p1).buffer(radius, int(self.steps_per_circ / 4)))
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Adding Circle completed.")
class FCArc(FCShapeTool):
@ -794,6 +795,7 @@ class FCArc(FCShapeTool):
self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
self.direction, self.steps_per_circ)))
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Arc completed.")
class FCRectangle(FCShapeTool):
@ -831,6 +833,7 @@ class FCRectangle(FCShapeTool):
# self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
self.geometry = DrawToolShape(Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Rectangle completed.")
class FCPolygon(FCShapeTool):
@ -869,6 +872,7 @@ class FCPolygon(FCShapeTool):
self.geometry = DrawToolShape(Polygon(self.points))
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Polygon completed.")
def on_key(self, key):
if key == 'backspace':
@ -885,6 +889,7 @@ class FCPath(FCPolygon):
self.geometry = DrawToolShape(LineString(self.points))
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Path completed.")
def utility_geometry(self, data=None):
if len(self.points) > 0:
@ -1143,6 +1148,7 @@ class FCMove(FCShapeTool):
self.start_msg = "Click on reference point."
def set_origin(self, origin):
self.draw_app.app.inform.emit("Click on destination point.")
self.origin = origin
def click(self, point):
@ -1173,6 +1179,7 @@ class FCMove(FCShapeTool):
# self.draw_app.set_selected(g)
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Geometry(s) Move completed.")
def utility_geometry(self, data=None):
"""
@ -1208,6 +1215,7 @@ class FCCopy(FCMove):
self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
for geom in self.draw_app.get_selected()]
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Geometry(s) Copy completed.")
class FCText(FCShapeTool):
@ -1241,6 +1249,7 @@ class FCText(FCShapeTool):
self.text_gui.text_path = []
self.text_gui.hide_tool()
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Adding Text completed.")
def utility_geometry(self, data=None):
"""
@ -1281,6 +1290,7 @@ class FCBuffer(FCShapeTool):
self.draw_app.buffer(buffer_distance, join_style)
self.app.ui.notebook.setTabText(2, "Tools")
self.disactivate()
self.draw_app.app.inform.emit("[success]Done. Buffer Tool completed.")
def on_buffer_int(self):
buffer_distance = self.buff_tool.buffer_distance_entry.get_value()
@ -1290,6 +1300,7 @@ class FCBuffer(FCShapeTool):
self.draw_app.buffer_int(buffer_distance, join_style)
self.app.ui.notebook.setTabText(2, "Tools")
self.disactivate()
self.draw_app.app.inform.emit("[success]Done. Buffer Int Tool completed.")
def on_buffer_ext(self):
buffer_distance = self.buff_tool.buffer_distance_entry.get_value()
@ -1299,6 +1310,7 @@ class FCBuffer(FCShapeTool):
self.draw_app.buffer_ext(buffer_distance, join_style)
self.app.ui.notebook.setTabText(2, "Tools")
self.disactivate()
self.draw_app.app.inform.emit("[success]Done. Buffer Ext Tool completed.")
def activate(self):
self.buff_tool.buffer_button.clicked.disconnect()
@ -1360,6 +1372,7 @@ class FCRotate(FCShapeTool):
# Delete old
self.draw_app.delete_selected()
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Geometry rotate completed.")
# MS: automatically select the Select Tool after finishing the action but is not working yet :(
#self.draw_app.select_tool("select")
@ -1901,6 +1914,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_cutpath_btn.triggered.connect(self.cutpath)
self.app.ui.geo_delete_btn.triggered.connect(self.on_delete_btn)
self.app.ui.geo_move_menuitem.triggered.connect(self.on_move)
self.app.ui.geo_cornersnap_menuitem.triggered.connect(self.on_corner_snap)
## Toolbar events and properties
self.tools = {
"select": {"button": self.app.ui.geo_select_btn,
@ -2002,7 +2018,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
try:
self.options[option] = float(entry.text())
except Exception as e:
log.debug(str(e))
log.debug("FlatCAMGeoEditor.__init__().entry2option() --> %s" % str(e))
return
self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_x_entry.textChanged.connect(
@ -2565,6 +2582,15 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.on_tool_select('rotate')
self.active_tool.set_origin(self.snap(self.x, self.y))
if event.key == '1':
self.app.on_zoom_fit(None)
if event.key == '2':
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
if event.key == '3':
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
# Arc Tool
if event.key.name == 'A':
self.select_tool('arc')
@ -2580,6 +2606,22 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.active_tool.set_origin(self.snap(self.x, self.y))
self.app.inform.emit("Click on target point.")
# Substract Tool
if event.key.name == 'E':
if self.get_selected() is not None:
self.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 event.key.name == 'G':
self.app.ui.grid_snap_btn.trigger()
@ -2596,14 +2638,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Corner Snap
if event.key.name == 'K':
self.app.ui.corner_snap_btn.trigger()
self.on_corner_snap()
# Move
if event.key.name == 'M':
self.app.ui.geo_move_btn.setChecked(True)
self.on_tool_select('move')
self.active_tool.set_origin(self.snap(self.x, self.y))
self.app.inform.emit("Click on target point.")
self.on_move_click()
# Polygon Tool
if event.key.name == 'N':
@ -2621,14 +2660,42 @@ class FlatCAMGeoEditor(QtCore.QObject):
if event.key.name == 'R':
self.select_tool('rectangle')
# Select Tool
# Substract Tool
if event.key.name == 'S':
self.select_tool('select')
if self.get_selected() is not None:
self.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 event.key.name == 'T':
self.select_tool('text')
# Substract Tool
if event.key.name == 'U':
if self.get_selected() is not None:
self.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_()
# Cut Action Tool
if event.key.name == 'X':
if self.get_selected() is not None:
@ -2661,11 +2728,15 @@ class FlatCAMGeoEditor(QtCore.QObject):
def on_shortcut_list(self):
msg = '''<b>Shortcut list in Geometry Editor</b><br>
<br>
<b>1:</b> Zoom Fit<br>
<b>2:</b> Zoom Out<br>
<b>3:</b> Zoom In<br>
<b>A:</b> Add an 'Arc'<br>
<b>B:</b> Add a Buffer Geo<br>
<b>C:</b> Copy Geo Item<br>
<b>E:</b> Intersection Tool<br>
<b>G:</b> Grid Snap On/Off<br>
<b>G:</b> Paint Tool<br>
<b>I:</b> Paint Tool<br>
<b>K:</b> Corner Snap On/Off<br>
<b>M:</b> Move Geo Item<br>
<br>
@ -2673,8 +2744,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
<b>O:</b> Add a 'Circle'<br>
<b>P:</b> Add a 'Path'<br>
<b>R:</b> Add an 'Rectangle'<br>
<b>S:</b> Select Tool Active<br>
<b>S:</b> Substraction Tool<br>
<b>T:</b> Add Text Geometry<br>
<b>U:</b> Union Tool<br>
<br>
<b>X:</b> Cut Path<br>
<br>
@ -2682,7 +2754,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
<br>
<b>Space:</b> Rotate selected Geometry<br>
<b>Enter:</b> Finish Current Action<br>
<b>Escape:</b> Abort Current Action<br>
<b>Escape:</b> Select Tool (Exit any other Tool)<br>
<b>Delete:</b> Delete Obj'''
helpbox =QtWidgets.QMessageBox()
@ -2718,6 +2790,17 @@ class FlatCAMGeoEditor(QtCore.QObject):
if shape in self.selected:
self.selected.remove(shape) # TODO: Check performance
def on_move(self):
self.app.ui.geo_move_btn.setChecked(True)
self.on_tool_select('move')
def on_move_click(self):
self.on_move()
self.active_tool.set_origin(self.snap(self.x, self.y))
def on_corner_snap(self):
self.app.ui.corner_snap_btn.trigger()
def get_selected(self):
"""
Returns list of shapes that are selected in the editor.
@ -2950,7 +3033,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
shapes = self.get_selected()
results = shapes[0].geo
try:
results = shapes[0].geo
except Exception as e:
log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e))
self.app.inform.emit("[warning_notcl]A selection of at least 2 geo items is required to do Intersection.")
self.select_tool('select')
return
for shape in shapes[1:]:
results = results.intersection(shape.geo)
@ -3638,7 +3727,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.move_timer.setSingleShot(True)
## Current application units in Upper Case
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
self.key = None # Currently pressed key
self.modifiers = None
@ -3712,7 +3801,7 @@ class FlatCAMExcEditor(QtCore.QObject):
def set_ui(self):
# updated units
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
self.olddia_newdia.clear()
self.tool2tooldia.clear()
@ -3752,7 +3841,7 @@ class FlatCAMExcEditor(QtCore.QObject):
pass
# updated units
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
# make a new name for the new Excellon object (the one with edited content)
self.edited_obj_name = self.exc_obj.options['name']
@ -4744,6 +4833,18 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.inform.emit("[warning_notcl]Cancelled. Nothing selected to delete.")
return
if event.key == '1':
self.launched_from_shortcuts = True
self.app.on_zoom_fit(None)
if event.key == '2':
self.launched_from_shortcuts = True
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
if event.key == '3':
self.launched_from_shortcuts = True
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
# Add Array of Drill Hole Tool
if event.key.name == 'A':
self.launched_from_shortcuts = True
@ -4828,6 +4929,9 @@ class FlatCAMExcEditor(QtCore.QObject):
def on_shortcut_list(self):
msg = '''<b>Shortcut list in Geometry Editor</b><br>
<br>
<b>1:</b> Zoom Fit<br>
<b>2:</b> Zoom Out<br>
<b>3:</b> Zoom In<br>
<b>A:</b> Add an 'Drill Array'<br>
<b>C:</b> Copy Drill Hole<br>
<b>D:</b> Add an Drill Hole<br>

File diff suppressed because it is too large Load Diff

View File

@ -366,6 +366,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if grb_final.solid_geometry is None:
grb_final.solid_geometry = []
if type(grb_final.solid_geometry) is not list:
grb_final.solid_geometry = [grb_final.solid_geometry]
@ -380,10 +381,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# Expand lists
if type(grb) is list:
FlatCAMGerber.merge(grb, grb_final)
else: # If not list, just append
for geos in grb.solid_geometry:
grb_final.solid_geometry.append(geos)
# If not list, just append
else:
grb_final.solid_geometry.append(grb.solid_geometry)
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
def __init__(self, name):
Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
@ -402,9 +404,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"isooverlap": 0.15,
"milling_type": "cl",
"combine_passes": True,
"ncctools": "1.0, 0.5",
"nccoverlap": 0.4,
"nccmargin": 1,
"noncoppermargin": 0.0,
"noncopperrounded": False,
"bboxmargin": 0.0,
@ -755,9 +754,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
factor = Gerber.convert_units(self, units)
self.options['isotooldia'] *= factor
self.options['cutoutmargin'] *= factor
self.options['cutoutgapsize'] *= factor
self.options['noncoppermargin'] *= factor
self.options['bboxmargin'] *= factor
def plot(self, **kwargs):
@ -899,14 +895,21 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
:return: None
"""
# flag to signal that we need to reorder the tools dictionary and drills and slots lists
flag_order = False
try:
flattened_list = list(itertools.chain(*exc_list))
except TypeError:
flattened_list = exc_list
# this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
# values will be list of Shapely Points
custom_dict = {}
# values will be list of Shapely Points; for drills
custom_dict_drills = {}
# this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
# values will be list of Shapely Points; for slots
custom_dict_slots = {}
for exc in flattened_list:
# copy options of the current excellon obj to the final excellon obj
@ -920,21 +923,29 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
for drill in exc.drills:
exc_tool_dia = float('%.3f' % exc.tools[drill['tool']]['C'])
if exc_tool_dia not in custom_dict:
custom_dict[exc_tool_dia] = [drill['point']]
if exc_tool_dia not in custom_dict_drills:
custom_dict_drills[exc_tool_dia] = [drill['point']]
else:
custom_dict[exc_tool_dia].append(drill['point'])
custom_dict_drills[exc_tool_dia].append(drill['point'])
# add the zeros and units to the exc_final object
for slot in exc.slots:
exc_tool_dia = float('%.3f' % exc.tools[slot['tool']]['C'])
if exc_tool_dia not in custom_dict_slots:
custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]]
else:
custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']])
# add the zeros and units to the exc_final object
exc_final.zeros = exc.zeros
exc_final.units = exc.units
# variable to make tool_name for the tools
current_tool = 0
# Here we add data to the exc_final object
# the tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points
for tool_dia in custom_dict:
# the tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
# drills
for tool_dia in custom_dict_drills:
# we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter)
current_tool += 1
@ -943,7 +954,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
exc_final.tools[tool_name] = spec
# rebuild the drills list of dict's that belong to the exc_final object
for point in custom_dict[tool_dia]:
for point in custom_dict_drills[tool_dia]:
exc_final.drills.append(
{
"point": point,
@ -951,6 +962,94 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
}
)
# Here we add data to the exc_final object
# the tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
# of two Shapely Points in case of slots
for tool_dia in custom_dict_slots:
# we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter)
# but only if there are no drills
if not exc_final.tools:
current_tool += 1
tool_name = str(current_tool)
spec = {"C": float(tool_dia)}
exc_final.tools[tool_name] = spec
else:
dia_list = []
for v in exc_final.tools.values():
dia_list.append(float(v["C"]))
if tool_dia not in dia_list:
flag_order = True
current_tool = len(dia_list) + 1
tool_name = str(current_tool)
spec = {"C": float(tool_dia)}
exc_final.tools[tool_name] = spec
else:
for k, v in exc_final.tools.items():
if v["C"] == tool_dia:
current_tool = int(k)
break
# rebuild the slots list of dict's that belong to the exc_final object
for point in custom_dict_slots[tool_dia]:
exc_final.slots.append(
{
"start": point[0],
"stop": point[1],
"tool": str(current_tool)
}
)
# flag_order == True means that there was an slot diameter not in the tools and we also have drills
# and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots
current_tool = 0
if flag_order is True:
dia_list = []
temp_drills = []
temp_slots = []
temp_tools = {}
for v in exc_final.tools.values():
dia_list.append(float(v["C"]))
dia_list.sort()
for ordered_dia in dia_list:
current_tool += 1
tool_name_temp = str(current_tool)
spec_temp = {"C": float(ordered_dia)}
temp_tools[tool_name_temp] = spec_temp
for drill in exc_final.drills:
exc_tool_dia = float('%.3f' % exc_final.tools[drill['tool']]['C'])
if exc_tool_dia == ordered_dia:
temp_drills.append(
{
"point": drill["point"],
"tool": str(current_tool)
}
)
for slot in exc_final.slots:
slot_tool_dia = float('%.3f' % exc_final.tools[slot['tool']]['C'])
if slot_tool_dia == ordered_dia:
temp_slots.append(
{
"start": slot["start"],
"stop": slot["stop"],
"tool": str(current_tool)
}
)
# delete the exc_final tools, drills and slots
exc_final.tools = dict()
exc_final.drills[:] = []
exc_final.slots[:] = []
# update the exc_final tools, drills and slots with the ordered values
exc_final.tools = temp_tools
exc_final.drills[:] = temp_drills
exc_final.slots[:] = temp_slots
# create the geometry for the exc_final object
exc_final.create_geometry()
@ -1192,7 +1291,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
"""
excellon_code = ''
units = self.app.general_options_form.general_group.units_radio.get_value().upper()
units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
# store here if the file has slots, return 1 if any slots, 0 if only drills
has_slots = 0
@ -1252,7 +1351,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
"""
excellon_code = ''
units = self.app.general_options_form.general_group.units_radio.get_value().upper()
units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
# store here if the file has slots, return 1 if any slots, 0 if only drills
has_slots = 0
@ -2759,7 +2858,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0))
def export_dxf(self):
units = self.app.general_options_form.general_group.units_radio.get_value().upper()
units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
dwg = None
try:
dwg = ezdxf.new('R2010')
@ -2859,7 +2958,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else:
self.app.inform.emit("[error_notcl] Failed. No tool selected in the tool table ...")
def mtool_gen_cncjob(self, use_thread=True):
def mtool_gen_cncjob(self, segx=None, segy=None, use_thread=True):
"""
Creates a multi-tool CNCJob out of this Geometry object.
The actual work is done by the target FlatCAMCNCjob object's
@ -2883,6 +2982,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
outname = "%s_%s" % (self.options["name"], 'cnc')
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
# Object initialization function for app.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init_single_geometry(job_obj, app_obj):
@ -2901,6 +3003,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# job_obj.create_geometry()
job_obj.options['Tools_in_use'] = self.get_selected_tools_table_items()
job_obj.segx = segx
job_obj.segy = segy
for tooluid_key in self.sel_tools:
tool_cnt += 1
@ -3242,6 +3346,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
toolchange=None, toolchangez=None, toolchangexy=None,
extracut=None, startz=None, endz=None,
ppname_g=None,
segx=None,
segy=None,
use_thread=True):
"""
Only used for TCL Command.
@ -3273,6 +3379,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
multidepth = multidepth if multidepth is not None else self.options["multidepth"]
depthperpass = depthperpass if depthperpass is not None else self.options["depthperpass"]
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
extracut = extracut if extracut is not None else self.options["extracut"]
startz = startz if startz is not None else self.options["startz"]
endz = endz if endz is not None else self.options["endz"]
@ -3307,6 +3416,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
job_obj.options['type'] = 'Geometry'
job_obj.options['tool_dia'] = tooldia
job_obj.segx = segx
job_obj.segy = segy
# TODO: The tolerance should not be hard coded. Just for testing.
job_obj.generate_from_geometry_2(self, tooldia=tooldia, offset=offset, tolerance=0.0005,
z_cut=z_cut, z_move=z_move,
@ -3358,8 +3470,20 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
:rtype: None
"""
try:
xfactor = float(xfactor)
except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.")
return
if yfactor is None:
yfactor = xfactor
else:
try:
yfactor = float(yfactor)
except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.")
return
if point is None:
px = 0
@ -3367,16 +3491,33 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else:
px, py = point
if type(self.solid_geometry) == list:
geo_list = self.flatten(self.solid_geometry)
self.solid_geometry = []
# for g in geo_list:
# self.solid_geometry.append(affinity.scale(g, xfactor, yfactor, origin=(px, py)))
self.solid_geometry = [affinity.scale(g, xfactor, yfactor, origin=(px, py))
for g in geo_list]
# if type(self.solid_geometry) == list:
# geo_list = self.flatten(self.solid_geometry)
# self.solid_geometry = []
# # for g in geo_list:
# # self.solid_geometry.append(affinity.scale(g, xfactor, yfactor, origin=(px, py)))
# self.solid_geometry = [affinity.scale(g, xfactor, yfactor, origin=(px, py))
# for g in geo_list]
# else:
# self.solid_geometry = affinity.scale(self.solid_geometry, xfactor, yfactor,
# origin=(px, py))
# self.app.inform.emit("[success]Geometry Scale done.")
def scale_recursion(geom):
if type(geom) == list:
geoms=list()
for local_geom in geom:
geoms.append(scale_recursion(local_geom))
return geoms
else:
return affinity.scale(geom, xfactor, yfactor, origin=(px, py))
if self.multigeo is True:
for tool in self.tools:
self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = affinity.scale(self.solid_geometry, xfactor, yfactor,
origin=(px, py))
self.solid_geometry=scale_recursion(self.solid_geometry)
self.app.inform.emit("[success]Geometry Scale done.")
def offset(self, vect):
"""
@ -3388,7 +3529,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
:rtype: None
"""
dx, dy = vect
try:
dx, dy = vect
except TypeError:
self.app.inform.emit("[error_notcl]An (x,y) pair of values are needed. "
"Probable you entered only one value in the Offset field.")
return
def translate_recursion(geom):
if type(geom) == list:
@ -3404,6 +3550,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.tools[tool]['solid_geometry'] = translate_recursion(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry=translate_recursion(self.solid_geometry)
self.app.inform.emit("[success]Geometry Offset done.")
def convert_units(self, units):
self.ui_disconnect()
@ -4042,13 +4189,16 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
cw_row = cw_index.row()
self.shapes.clear(update=True)
for tooluid_key in self.cnc_tools:
tooldia = float('%.4f' % float(self.cnc_tools[tooluid_key]['tooldia']))
gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
# tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text())
if self.ui.cnc_tools_table.cellWidget((tooluid_key - 1), 6).isChecked():
self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed)
for r in range(self.ui.cnc_tools_table.rowCount()):
if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key):
if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked():
self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed)
self.shapes.redraw()

View File

@ -33,7 +33,7 @@ class FlatCAMTool(QtWidgets.QWidget):
self.menuAction = None
def install(self, icon=None, separator=None, **kwargs):
def install(self, icon=None, separator=None, shortcut=None, **kwargs):
before = None
# 'pos' is the menu where the Action has to be installed
@ -54,8 +54,13 @@ class FlatCAMTool(QtWidgets.QWidget):
# if provided, add an icon to this Action
if icon is not None:
self.menuAction.setIcon(icon)
# set the text name of the Action, which will be displayed in the menu
self.menuAction.setText(self.toolName)
if shortcut is None:
self.menuAction.setText(self.toolName)
else:
self.menuAction.setText(self.toolName + '\t%s' % shortcut)
# add a ToolTip to the new Action
# self.menuAction.setToolTip(self.toolTip) # currently not available

View File

@ -12,6 +12,7 @@ import inspect # TODO: Remove
import FlatCAMApp
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt
import webbrowser
class KeySensitiveListView(QtWidgets.QTreeView):
@ -262,6 +263,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_G:
self.app.on_fileopengerber()
if key == QtCore.Qt.Key_N:
self.app.on_file_new_click()
if key == QtCore.Qt.Key_M:
self.app.measurement_tool.run()
if key == QtCore.Qt.Key_O:
@ -272,6 +276,11 @@ class ObjectCollection(QtCore.QAbstractItemModel):
return
elif modifiers == QtCore.Qt.ShiftModifier:
# Copy Object Name
# Copy Object Name
if key == QtCore.Qt.Key_C:
self.app.on_copy_name()
# Toggle axis
if key == QtCore.Qt.Key_G:
if self.toggle_axis is False:
@ -286,12 +295,65 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.appplotcanvas.redraw()
self.app.toggle_axis = False
# Open Preferences Window
if key == QtCore.Qt.Key_P:
self.app.on_preferences()
return
# Rotate Object by 90 degree CCW
if key == QtCore.Qt.Key_R:
self.app.on_rotate(silent=True, preset=-90)
return
# Run a Script
if key == QtCore.Qt.Key_S:
self.app.on_filerunscript()
return
# Toggle Workspace
if key == QtCore.Qt.Key_W:
self.app.on_workspace_menu()
return
# Skew on X axis
if key == QtCore.Qt.Key_X:
self.app.on_skewx()
return
# Skew on Y axis
if key == QtCore.Qt.Key_Y:
self.app.on_skewy()
return
elif modifiers == QtCore.Qt.AltModifier:
# 2-Sided PCB Tool
if key == QtCore.Qt.Key_D:
self.app.dblsidedtool.run()
return
# Non-Copper Clear Tool
if key == QtCore.Qt.Key_N:
self.app.ncclear_tool.run()
return
# Transformation Tool
if key == QtCore.Qt.Key_R:
self.app.transform_tool.run()
return
# Cutout Tool
if key == QtCore.Qt.Key_U:
self.app.cutout_tool.run()
return
else:
# Open Manual
if key == QtCore.Qt.Key_F1:
webbrowser.open(self.app.manual_url)
# Open Video Help
if key == QtCore.Qt.Key_F2:
webbrowser.open(self.app.video_url)
# Zoom Fit
if key == QtCore.Qt.Key_1:
self.app.on_zoom_fit(None)
@ -316,10 +378,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
select.ui.plot_cb.toggle()
self.app.delete_selection_shape()
# Copy Object Name
if key == QtCore.Qt.Key_C:
self.app.on_copy_name()
# Copy Object Name
if key == QtCore.Qt.Key_E:
self.app.object2editor()
@ -332,6 +390,10 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_J:
self.app.on_jump_to()
# New Excellon
if key == QtCore.Qt.Key_L:
self.app.new_excellon_object()
# Move tool toggle
if key == QtCore.Qt.Key_M:
self.app.move_tool.toggle()
@ -340,12 +402,22 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_N:
self.app.on_new_geometry()
# Set Origin
if key == QtCore.Qt.Key_O:
self.app.on_set_origin()
return
# Set Origin
if key == QtCore.Qt.Key_P:
self.app.properties_tool.run()
return
# Change Units
if key == QtCore.Qt.Key_Q:
if self.app.options["units"] == 'MM':
self.app.general_options_form.general_group.units_radio.set_value("IN")
self.app.general_options_form.general_app_group.units_radio.set_value("IN")
else:
self.app.general_options_form.general_group.units_radio.set_value("MM")
self.app.general_options_form.general_app_group.units_radio.set_value("MM")
self.app.on_toggle_units()
# Rotate Object by 90 degree CW

View File

@ -970,8 +970,9 @@ class GeometryObjectUI(ObjectUI):
# Spindlespeed
spdlabel = QtWidgets.QLabel('Spindle speed:')
spdlabel.setToolTip(
"Speed of the spindle\n"
"in RPM (optional)"
"Speed of the spindle in RPM (optional).\n"
"If LASER postprocessor is used,\n"
"this value is the power of laser."
)
self.grid3.addWidget(spdlabel, 14, 0)
self.cncspindlespeed_entry = IntEntry(allow_empty=True)

View File

@ -65,7 +65,7 @@ class PlotCanvas(QtCore.QObject):
self.draw_workspace()
# if self.app.defaults['global_workspace'] is True:
# if self.app.general_options_form.general_group.units_radio.get_value().upper() == 'MM':
# if self.app.general_options_form.general_app_group.units_radio.get_value().upper() == 'MM':
# self.wkspace_t = Line(pos=)
self.shape_collections = []
@ -92,7 +92,7 @@ class PlotCanvas(QtCore.QObject):
a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
if self.app.general_options_form.general_group.units_radio.get_value().upper() == 'MM':
if self.app.general_options_form.general_app_group.units_radio.get_value().upper() == 'MM':
if self.app.defaults['global_workspaceT'] == 'A4P':
a = a4p_mm
elif self.app.defaults['global_workspaceT'] == 'A4L':

View File

@ -9,6 +9,49 @@ CAD program, and create G-Code for Isolation routing.
=================================================
29.01.2019
- fixed issue in Tool Calculators when a float value was entered starting only with the dot.
- added protection for entering incorrect values in Offset and Scale fields for Gerber and Geometry objects (in Selected Tab)
- added more shortcut keys in the Geometry Editor and in Excellon Editor; activated also the zoom (fit, in, out) shortcut keys ('1' , '2', '3') for the editors
- disabled the context menu in tools table on Paint Tool in case that the painting method is single.
- added protection when trying to do Intersection in Geometry Editor without having selected Geometry items.
- fixed the scale, mirror, rotate, skew functions to work with Geometry Objects of multi-geometry type.
- added a GUI for Excellon Search time for OR-TOOLS path optimization in Edit -> Preferences -> Excellon General -> Optimization Time
- more changes in Edit -> Preferences -> Geometry, Gerber and in CNCJob
- added new option for Cutout Tool Freeform Gaps in Edit -> Preferences -> Tools
- fixed Freeform Cutout gaps issue (it was double than the value set)
- added protection so the Cutout (either Freeform or Rectangular) cannot be done on a multigeo Geometry
- added 2Sided Tool default values in Edit -> Preferences -> Tools
- optimized the FlatCAMCNCJob.on_plot_cb_click_table() plot function and solved a bug regarding having tools numbers not in sync with the cnc tool table
28.01.2018
- fixed the FlatCAMGerber.merge() function
- added a new menu entry for the Gerber Join function: Edit -> Conversions -> "Join Gerber(s) to Gerber" allowing joining Gerber objects into a final Gerber object
- moved Paint Tool defaults from Geometry section to the Tools section in Edit -> Preferences
- added key shortcuts for Open Manual = F1 and for Open Online VideoHelp = F2
27.01.2018
- added more key shortcuts into the application; they are now displayed in the GUI menu's
- reorganized the Edit -> Preferences -> Global
- redesigned the messagebox that is showed when quiting ot creating a New Project: now it has an option ('Cancel') to abort the process returning to the app
- added options for trace segmentation that can be useful for auto-levelling (code snippet from Lei Zheng from a rejected pull request on FlatCAM https://bitbucket.org/realthunder/ )
- added shortcut key 'L' for creating 'New Excellon'
- added shortcut key combo 'SHIFT+S' for Running a Script.
- modified grbl_laser postprocessor file so it includes a Sxxxx command on the line with M03 (laser active) whenever a value is enter in the Spindlespeed entry field
- remade the EDIT -> PREFERENCES window, the Excellon and Gerber sections. Created a new section named TOOLS
26.01.2019
- fixed grbl_11 postprocessor in linear_code() function
- added icons to the Project Tab context menu
- added new entries to the Canvas context menu (Copy, Delete, Edit/Save, Move, New Excellon, New Geometry, New Project)
- fixed grbl_laser postprocessor file
- updated function for copy of an Excellon object for the case when the object has slots
- updated FlatCAMExcellon.merge() function to work in case some (or all) of the merged objects have slots
25.01.2019
- deleted junk folders

115
camlib.py
View File

@ -1354,11 +1354,17 @@ class Geometry(object):
return affinity.scale(obj, xscale, yscale, origin=(px,py))
try:
self.solid_geometry = mirror_geom(self.solid_geometry)
if self.multigeo is True:
for tool in self.tools:
self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = mirror_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was mirrored ...')
except AttributeError:
self.app.inform.emit("[error_notcl] Failed to mirror. No object selected")
def rotate(self, angle, point):
"""
Rotate an object by an angle (in degrees) around the provided coordinates.
@ -1388,7 +1394,11 @@ class Geometry(object):
return affinity.rotate(obj, angle, origin=(px, py))
try:
self.solid_geometry = rotate_geom(self.solid_geometry)
if self.multigeo is True:
for tool in self.tools:
self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = rotate_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was rotated ...')
except AttributeError:
self.app.inform.emit("[error_notcl] Failed to rotate. No object selected")
@ -1420,7 +1430,11 @@ class Geometry(object):
return affinity.skew(obj, angle_x, angle_y, origin=(px, py))
try:
self.solid_geometry = skew_geom(self.solid_geometry)
if self.multigeo is True:
for tool in self.tools:
self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
else:
self.solid_geometry = skew_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was skewed ...')
except AttributeError:
self.app.inform.emit("[error_notcl] Failed to skew. No object selected")
@ -2965,7 +2979,7 @@ class Gerber (Geometry):
return 0, 0, 0, 0
def bounds_rec(obj):
if type(obj) is list:
if type(obj) is list and type(obj) is not MultiPolygon:
minx = Inf
miny = Inf
maxx = -Inf
@ -2980,7 +2994,12 @@ class Gerber (Geometry):
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
else:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
try:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
except Exception as e:
log.debug("camlib.Geometry.bounds() --> %s" % str(e))
return
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
@ -3013,8 +3032,20 @@ class Gerber (Geometry):
:rtype : None
"""
try:
xfactor = float(xfactor)
except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.")
return
if yfactor is None:
yfactor = xfactor
else:
try:
yfactor = float(yfactor)
except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.")
return
if point is None:
px = 0
@ -3033,6 +3064,7 @@ class Gerber (Geometry):
yfactor, origin=(px, py))
self.solid_geometry = scale_geom(self.solid_geometry)
self.app.inform.emit("[success]Gerber Scale done.")
## solid_geometry ???
# It's a cascaded union of objects.
@ -3061,8 +3093,12 @@ class Gerber (Geometry):
:type vect: tuple
:return: None
"""
dx, dy = vect
try:
dx, dy = vect
except TypeError:
self.app.inform.emit("[error_notcl]An (x,y) pair of values are needed. "
"Probable you entered only one value in the Offset field.")
return
def offset_geom(obj):
if type(obj) is list:
@ -3076,6 +3112,7 @@ class Gerber (Geometry):
## Solid geometry
# self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
self.solid_geometry = offset_geom(self.solid_geometry)
self.app.inform.emit("[success]Gerber Offset done.")
def mirror(self, axis, point):
"""
@ -4332,6 +4369,8 @@ class CNCjob(Geometry):
spindlespeed=None, dwell=True, dwelltime=1000,
toolchangez=0.787402,
endz=2.0,
segx=None,
segy=None,
steps_per_circle=None):
# Used when parsing G-code arcs
@ -4376,6 +4415,9 @@ class CNCjob(Geometry):
self.dwell = dwell
self.dwelltime = dwelltime
self.segx = float(segx) if segx is not None else 0.0
self.segy = float(segy) if segy is not None else 0.0
self.input_geometry_bounds = None
# Attributes to be included in serialization
@ -5456,6 +5498,61 @@ class CNCjob(Geometry):
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
return self.solid_geometry
# code snippet added by Lei Zheng in a rejected pull request on FlatCAM https://bitbucket.org/realthunder/
def segment(self, coords):
"""
break long linear lines to make it more auto level friendly
"""
if len(coords) < 2 or self.segx <= 0 and self.segy <= 0:
return list(coords)
path = [coords[0]]
# break the line in either x or y dimension only
def linebreak_single(line, dim, dmax):
if dmax <= 0:
return None
if line[1][dim] > line[0][dim]:
sign = 1.0
d = line[1][dim] - line[0][dim]
else:
sign = -1.0
d = line[0][dim] - line[1][dim]
if d > dmax:
# make sure we don't make any new lines too short
if d > dmax * 2:
dd = dmax
else:
dd = d / 2
other = dim ^ 1
return (line[0][dim] + dd * sign, line[0][other] + \
dd * (line[1][other] - line[0][other]) / d)
return None
# recursively breaks down a given line until it is within the
# required step size
def linebreak(line):
pt_new = linebreak_single(line, 0, self.segx)
if pt_new is None:
pt_new2 = linebreak_single(line, 1, self.segy)
else:
pt_new2 = linebreak_single((line[0], pt_new), 1, self.segy)
if pt_new2 is not None:
pt_new = pt_new2[::-1]
if pt_new is None:
path.append(line[1])
else:
path.append(pt_new)
linebreak((pt_new, line[1]))
for pt in coords[1:]:
linebreak((path[-1], pt))
return path
def linear2gcode(self, linear, tolerance=0, down=True, up=True,
z_cut=None, z_move=None, zdownrate=None,
feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False):
@ -5500,7 +5597,9 @@ class CNCjob(Geometry):
gcode = ""
path = list(target_linear.coords)
# path = list(target_linear.coords)
path = self.segment(target_linear.coords)
p = self.pp_geometry
# Move fast to 1st point

View File

@ -141,6 +141,9 @@ class ToolCalculator(FlatCAMTool):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Calc. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
def on_calculate_tool_dia(self):
# Calculation:
# Manufacturer gives total angle of the the tip but we need only half of it
@ -154,7 +157,7 @@ class ToolCalculator(FlatCAMTool):
tip_diameter = float(self.tipDia_entry.get_value())
half_tip_angle = float(self.tipAngle_entry.get_value()) / 2
cut_depth = float(self.cutDepth_entry.get_value())
except TypeError:
except:
return
tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))

View File

@ -9,7 +9,7 @@ from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
class ToolCutout(FlatCAMTool):
toolName = "Cutout PCB Tool"
toolName = "Cutout PCB"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
@ -101,10 +101,9 @@ class ToolCutout(FlatCAMTool):
# 8 - 2*left + 2*right +2*top + 2*bottom
# Gaps
self.gaps = FCEntry()
self.gaps_label = QtWidgets.QLabel("Type of gaps: ")
self.gaps_label.setToolTip(
"Number of gaps used for the cutout.\n"
gaps_ff_label = QtWidgets.QLabel('Gaps FF: ')
gaps_ff_label.setToolTip(
"Number of gaps used for the FreeForm cutout.\n"
"There can be maximum 8 bridges/gaps.\n"
"The choices are:\n"
"- lr - left + right\n"
@ -114,7 +113,13 @@ class ToolCutout(FlatCAMTool):
"- 2tb - 2*top + 2*bottom\n"
"- 8 - 2*left + 2*right +2*top + 2*bottom"
)
form_layout_2.addRow(self.gaps_label, self.gaps)
self.gaps = FCComboBox()
gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
for it in gaps_items:
self.gaps.addItem(it)
self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
form_layout_2.addRow(gaps_ff_label, self.gaps)
## Buttons
hlay = QtWidgets.QHBoxLayout()
@ -146,8 +151,8 @@ class ToolCutout(FlatCAMTool):
"- one gap Left / one gap Right\n"
"- one gap on each of the 4 sides."
)
self.gaps_rect_radio = RadioSet([{'label': 'T/B', 'value': 'tb'},
{'label': 'L/R', 'value': 'lr'},
self.gaps_rect_radio = RadioSet([{'label': '2(T/B)', 'value': 'tb'},
{'label': '2(L/R)', 'value': 'lr'},
{'label': '4', 'value': '4'}])
form_layout_3.addRow(gapslabel_rect, self.gaps_rect_radio)
@ -186,8 +191,19 @@ class ToolCutout(FlatCAMTool):
def run(self):
FlatCAMTool.run(self)
self.set_ui()
self.app.ui.notebook.setTabText(2, "Cutout Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+U', **kwargs)
def set_ui(self):
self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"]))
self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
self.gaps.set_value(4)
self.gaps_rect_radio.set_value(str(self.app.defaults["tools_gaps_rect"]))
def on_freeform_cutout(self):
def subtract_rectangle(obj_, x0, y0, x1, y1):
@ -204,7 +220,8 @@ class ToolCutout(FlatCAMTool):
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit("[error_notcl]Object not found: %s" % cutout_obj)
self.app.inform.emit("[error_notcl]There is no object selected for Cutout.\nSelect one and try again.")
return
try:
dia = float(self.dia.get_value())
@ -236,6 +253,12 @@ class ToolCutout(FlatCAMTool):
"Fill in a correct value and retry. ")
return
if cutout_obj.multigeo is True:
self.app.inform.emit("[error]Cutout operation cannot be done on a multi-geo Geometry.\n"
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
"and after that perform Cutout.")
return
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) + margin
@ -243,7 +266,7 @@ class ToolCutout(FlatCAMTool):
lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) + (margin * 2)
gapsize = gapsize + (dia / 2)
gapsize = gapsize / 2 + (dia / 2)
if isinstance(cutout_obj,FlatCAMGeometry):
# rename the obj name so it can be identified as cutout
@ -346,6 +369,12 @@ class ToolCutout(FlatCAMTool):
self.app.inform.emit("[error_notcl]Tool Diameter is zero value. Change it to a positive integer.")
return "Tool Diameter is zero value. Change it to a positive integer."
if cutout_obj.multigeo is True:
self.app.inform.emit("[error]Cutout operation cannot be done on a multi-geo Geometry.\n"
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
"and after that perform Cutout.")
return
def geo_init(geo_obj, app_obj):
real_margin = margin + (dia / 2)
real_gap_size = gapsize + dia

View File

@ -8,7 +8,7 @@ from PyQt5 import QtCore
class DblSidedTool(FlatCAMTool):
toolName = "Double-Sided PCB Tool"
toolName = "2-Sided PCB"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
@ -249,10 +249,26 @@ class DblSidedTool(FlatCAMTool):
self.drill_values = ""
self.set_ui()
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **kwargs)
def run(self):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "2-Sided Tool")
self.reset_fields()
self.set_ui()
def set_ui(self):
## Initialize form
self.mirror_axis.set_value('X')
self.axis_location.set_value('point')
self.drill_dia.set_value(1)
self.point_entry.set_value("")
self.alignment_holes.set_value("")
self.mirror_axis.set_value(self.app.defaults["tools_2sided_mirror_axis"])
self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
def on_combo_box_type(self):
obj_type = self.box_combo_type.currentIndex()
@ -454,14 +470,6 @@ class DblSidedTool(FlatCAMTool):
self.drill_values = ""
self.point_entry.set_value("")
self.alignment_holes.set_value("")
## Initialize form
self.mirror_axis.set_value('X')
self.axis_location.set_value('point')
self.drill_dia.set_value(1)
def run(self):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "2-Sided Tool")
self.reset_fields()

View File

@ -6,7 +6,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
class Film(FlatCAMTool):
toolName = "Film PCB Tool"
toolName = "Film PCB"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
@ -154,6 +154,9 @@ class Film(FlatCAMTool):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Film Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+L', **kwargs)
def on_film_creation(self):
try:
name = self.tf_object_combo.currentText()

View File

@ -7,12 +7,12 @@ from math import sqrt
class Measurement(FlatCAMTool):
toolName = "Measurement Tool"
toolName = "Measurement"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
self.units = self.app.general_options_form.general_group.units_radio.get_value().lower()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().lower()
## Title
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
@ -165,10 +165,13 @@ class Measurement(FlatCAMTool):
# Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
self.units = self.app.general_options_form.general_group.units_radio.get_value().lower()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().lower()
self.show()
self.app.ui.notebook.setTabText(2, "Meas. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
def on_key_release_meas(self, event):
if event.key == 'escape':
# abort the measurement action
@ -217,7 +220,7 @@ class Measurement(FlatCAMTool):
else:
# ENABLE the Measuring TOOL
self.active = True
self.units = self.app.general_options_form.general_group.units_radio.get_value().lower()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().lower()
# we disconnect the mouse/key handlers from wherever the measurement tool was called
if self.app.call_source == 'app':

View File

@ -31,7 +31,7 @@ class ToolMove(FlatCAMTool):
self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, **kwargs)
FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs)
def run(self):
if self.app.tool_tab_locked is True:

View File

@ -7,7 +7,7 @@ import time
class NonCopperClear(FlatCAMTool, Gerber):
toolName = "Non-Copper Clearing Tool"
toolName = "Non-Copper Clearing"
def __init__(self, app):
self.app = app
@ -234,7 +234,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.generate_ncc_button.clicked.connect(self.on_ncc)
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, **kwargs)
FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
def run(self):
FlatCAMTool.run(self)
@ -244,12 +244,12 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.ui.notebook.setTabText(2, "NCC Tool")
def set_ui(self):
self.ncc_overlap_entry.set_value(self.app.defaults["gerber_nccoverlap"])
self.ncc_margin_entry.set_value(self.app.defaults["gerber_nccmargin"])
self.ncc_method_radio.set_value(self.app.defaults["gerber_nccmethod"])
self.ncc_connect_cb.set_value(self.app.defaults["gerber_nccconnect"])
self.ncc_contour_cb.set_value(self.app.defaults["gerber_ncccontour"])
self.ncc_rest_cb.set_value(self.app.defaults["gerber_nccrest"])
self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"])
self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"])
self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"])
self.ncc_connect_cb.set_value(self.app.defaults["tools_nccconnect"])
self.ncc_contour_cb.set_value(self.app.defaults["tools_ncccontour"])
self.ncc_rest_cb.set_value(self.app.defaults["tools_nccrest"])
self.tools_table.setupContextMenu()
self.tools_table.addContextMenu(
@ -263,7 +263,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.default_data.update({
"name": '_ncc',
"plot": self.app.defaults["geometry_plot"],
"tooldia": self.app.defaults["geometry_painttooldia"],
"cutz": self.app.defaults["geometry_cutz"],
"vtipdia": 0.1,
"vtipangle": 30,
@ -283,24 +282,27 @@ class NonCopperClear(FlatCAMTool, Gerber):
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"startz": self.app.defaults["geometry_startz"],
"paintmargin": self.app.defaults["geometry_paintmargin"],
"paintmethod": self.app.defaults["geometry_paintmethod"],
"selectmethod": self.app.defaults["geometry_selectmethod"],
"pathconnect": self.app.defaults["geometry_pathconnect"],
"paintcontour": self.app.defaults["geometry_paintcontour"],
"paintoverlap": self.app.defaults["geometry_paintoverlap"],
"nccoverlap": self.app.defaults["gerber_nccoverlap"],
"nccmargin": self.app.defaults["gerber_nccmargin"],
"nccmethod": self.app.defaults["gerber_nccmethod"],
"nccconnect": self.app.defaults["gerber_nccconnect"],
"ncccontour": self.app.defaults["gerber_ncccontour"],
"nccrest": self.app.defaults["gerber_nccrest"]
"tooldia": self.app.defaults["tools_painttooldia"],
"paintmargin": self.app.defaults["tools_paintmargin"],
"paintmethod": self.app.defaults["tools_paintmethod"],
"selectmethod": self.app.defaults["tools_selectmethod"],
"pathconnect": self.app.defaults["tools_pathconnect"],
"paintcontour": self.app.defaults["tools_paintcontour"],
"paintoverlap": self.app.defaults["tools_paintoverlap"],
"nccoverlap": self.app.defaults["tools_nccoverlap"],
"nccmargin": self.app.defaults["tools_nccmargin"],
"nccmethod": self.app.defaults["tools_nccmethod"],
"nccconnect": self.app.defaults["tools_nccconnect"],
"ncccontour": self.app.defaults["tools_ncccontour"],
"nccrest": self.app.defaults["tools_nccrest"]
})
try:
dias = [float(eval(dia)) for dia in self.app.defaults["gerber_ncctools"].split(",")]
dias = [float(eval(dia)) for dia in self.app.defaults["tools_ncctools"].split(",")]
except:
log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Gerber Object -> NCC Tools.")
log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> TOOLS -> NCC Tools.")
return
self.tooluid = 0
@ -322,13 +324,13 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.obj_name = ""
self.ncc_obj = None
self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
def build_ui(self):
self.ui_disconnect()
# updated units
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
if self.units == "IN":
self.addtool_entry.set_value(0.039)
@ -550,22 +552,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
def on_ncc(self):
over = self.ncc_overlap_entry.get_value()
over = over if over else self.app.defaults["gerber_nccoverlap"]
over = over if over else self.app.defaults["tools_nccoverlap"]
margin = self.ncc_margin_entry.get_value()
margin = margin if margin else self.app.defaults["gerber_nccmargin"]
margin = margin if margin else self.app.defaults["tools_nccmargin"]
connect = self.ncc_connect_cb.get_value()
connect = connect if connect else self.app.defaults["gerber_nccconnect"]
connect = connect if connect else self.app.defaults["tools_nccconnect"]
contour = self.ncc_contour_cb.get_value()
contour = contour if contour else self.app.defaults["gerber_ncccontour"]
contour = contour if contour else self.app.defaults["tools_ncccontour"]
clearing_method = self.ncc_rest_cb.get_value()
clearing_method = clearing_method if clearing_method else self.app.defaults["gerber_nccrest"]
clearing_method = clearing_method if clearing_method else self.app.defaults["tools_nccrest"]
pol_method = self.ncc_method_radio.get_value()
pol_method = pol_method if pol_method else self.app.defaults["gerber_nccmethod"]
pol_method = pol_method if pol_method else self.app.defaults["tools_nccmethod"]
self.obj_name = self.object_combo.currentText()
# Get source object.

View File

@ -5,7 +5,7 @@ from ObjectCollection import *
class ToolPaint(FlatCAMTool, Gerber):
toolName = "Paint Area Tool"
toolName = "Paint Area"
def __init__(self, app):
self.app = app
@ -250,7 +250,6 @@ class ToolPaint(FlatCAMTool, Gerber):
self.default_data.update({
"name": '_paint',
"plot": self.app.defaults["geometry_plot"],
"tooldia": self.app.defaults["geometry_painttooldia"],
"cutz": self.app.defaults["geometry_cutz"],
"vtipdia": 0.1,
"vtipangle": 30,
@ -270,12 +269,14 @@ class ToolPaint(FlatCAMTool, Gerber):
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"startz": self.app.defaults["geometry_startz"],
"paintmargin": self.app.defaults["geometry_paintmargin"],
"paintmethod": self.app.defaults["geometry_paintmethod"],
"selectmethod": self.app.defaults["geometry_selectmethod"],
"pathconnect": self.app.defaults["geometry_pathconnect"],
"paintcontour": self.app.defaults["geometry_paintcontour"],
"paintoverlap": self.app.defaults["geometry_paintoverlap"]
"tooldia": self.app.defaults["tools_painttooldia"],
"paintmargin": self.app.defaults["tools_paintmargin"],
"paintmethod": self.app.defaults["tools_paintmethod"],
"selectmethod": self.app.defaults["tools_selectmethod"],
"pathconnect": self.app.defaults["tools_pathconnect"],
"paintcontour": self.app.defaults["tools_paintcontour"],
"paintoverlap": self.app.defaults["tools_paintoverlap"]
})
self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
@ -290,7 +291,7 @@ class ToolPaint(FlatCAMTool, Gerber):
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, **kwargs)
FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs)
def run(self):
FlatCAMTool.run(self)
@ -311,11 +312,13 @@ class ToolPaint(FlatCAMTool, Gerber):
self.addtool_entry.setDisabled(True)
self.addtool_btn.setDisabled(True)
self.deltool_btn.setDisabled(True)
self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
else:
self.rest_cb.setDisabled(False)
self.addtool_entry.setDisabled(False)
self.addtool_btn.setDisabled(False)
self.deltool_btn.setDisabled(False)
self.tools_table.setContextMenuPolicy(Qt.ActionsContextMenu)
def set_ui(self):
## Init the GUI interface
@ -327,7 +330,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.paintoverlap_entry.set_value(self.default_data["paintoverlap"])
# updated units
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
if self.units == "IN":
self.addtool_entry.set_value(0.039)
@ -349,7 +352,6 @@ class ToolPaint(FlatCAMTool, Gerber):
self.default_data.update({
"name": '_paint',
"plot": self.app.defaults["geometry_plot"],
"tooldia": self.app.defaults["geometry_painttooldia"],
"cutz": self.app.defaults["geometry_cutz"],
"vtipdia": 0.1,
"vtipangle": 30,
@ -369,17 +371,23 @@ class ToolPaint(FlatCAMTool, Gerber):
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"startz": self.app.defaults["geometry_startz"],
"paintmargin": self.app.defaults["geometry_paintmargin"],
"paintmethod": self.app.defaults["geometry_paintmethod"],
"selectmethod": self.app.defaults["geometry_selectmethod"],
"pathconnect": self.app.defaults["geometry_pathconnect"],
"paintcontour": self.app.defaults["geometry_paintcontour"],
"paintoverlap": self.app.defaults["geometry_paintoverlap"]
"tooldia": self.app.defaults["tools_painttooldia"],
"paintmargin": self.app.defaults["tools_paintmargin"],
"paintmethod": self.app.defaults["tools_paintmethod"],
"selectmethod": self.app.defaults["tools_selectmethod"],
"pathconnect": self.app.defaults["tools_pathconnect"],
"paintcontour": self.app.defaults["tools_paintcontour"],
"paintoverlap": self.app.defaults["tools_paintoverlap"]
})
# call on self.on_tool_add() counts as an call to self.build_ui()
# through this, we add a initial row / tool in the tool_table
self.on_tool_add(self.app.defaults["geometry_painttooldia"], muted=True)
self.on_tool_add(self.app.defaults["tools_painttooldia"], muted=True)
# if the Paint Method is "Single" disable the tool table context menu
if self.default_data["selectmethod"] == "single":
self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
def build_ui(self):
@ -390,7 +398,7 @@ class ToolPaint(FlatCAMTool, Gerber):
pass
# updated units
self.units = self.app.general_options_form.general_group.units_radio.get_value().upper()
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
sorted_tools = []
for k, v in self.paint_tools.items():

View File

@ -6,7 +6,7 @@ import time
class Panelize(FlatCAMTool):
toolName = "Panelize PCB Tool"
toolName = "Panelize PCB"
def __init__(self, app):
super(Panelize, self).__init__(self)
@ -197,6 +197,9 @@ class Panelize(FlatCAMTool):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Panel. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+Z', **kwargs)
def on_panelize(self):
name = self.object_combo.currentText()

View File

@ -53,6 +53,9 @@ class Properties(FlatCAMTool):
FlatCAMTool.run(self)
self.properties()
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='P', **kwargs)
def properties(self):
obj_list = self.app.collection.get_selected()
if not obj_list:
@ -86,10 +89,10 @@ class Properties(FlatCAMTool):
width = abs(ymax - ymin)
self.addChild(dims, ['Length:', '%.4f %s' % (
length, self.app.general_options_form.general_group.units_radio.get_value().lower())], True)
length, self.app.general_options_form.general_app_group.units_radio.get_value().lower())], True)
self.addChild(dims, ['Width:', '%.4f %s' % (
width, self.app.general_options_form.general_group.units_radio.get_value().lower())], True)
if self.app.general_options_form.general_group.units_radio.get_value().lower() == 'mm':
width, self.app.general_options_form.general_app_group.units_radio.get_value().lower())], True)
if self.app.general_options_form.general_app_group.units_radio.get_value().lower() == 'mm':
area = (length * width) / 100
self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'cm2')], True)
else:

View File

@ -370,6 +370,9 @@ class ToolTransform(FlatCAMTool):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Transform Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+R', **kwargs)
def on_rotate(self):
try:
value = float(self.rotate_entry.get_value())

View File

@ -105,7 +105,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
return ('G00 ' + self.position_code(p)).format(**p)
def linear_code(self, p):
return ('G01 ' + self.position_code(p)).format(**p) + " " + self.feedrate_code(p)
return ('G01 ' + self.position_code(p)).format(**p) + \
' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
def end_code(self, p):
coords_xy = p['toolchange_xy']

View File

@ -36,7 +36,10 @@ class grbl_laser(FlatCAMPostProc):
return 'M05'
def down_code(self, p):
return 'M03'
if p.spindlespeed:
return 'M03 S%d' % p.spindlespeed
else:
return 'M03'
def toolchange_code(self, p):
return ''
@ -52,7 +55,8 @@ class grbl_laser(FlatCAMPostProc):
return ('G00 ' + self.position_code(p)).format(**p)
def linear_code(self, p):
return ('G01 ' + self.position_code(p)).format(**p) + " " + self.feedrate_code(p)
return ('G01 ' + self.position_code(p)).format(**p) + \
' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
def end_code(self, p):
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
@ -66,10 +70,7 @@ class grbl_laser(FlatCAMPostProc):
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
def spindle_code(self, p):
if p.spindlespeed:
return 'S%d' % p.spindlespeed
else:
return ''
return ''
def dwell_code(self, p):
return ''

BIN
share/toggle_units16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

BIN
share/toggle_units32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B