jpcgt/flatcam/Beta слито с Beta

This commit is contained in:
Camellan 2019-12-14 22:55:51 +04:00
commit f6dedfea49
41 changed files with 19390 additions and 18234 deletions

View File

@ -23,8 +23,14 @@ from stat import S_IREAD, S_IRGRP, S_IROTH
import subprocess import subprocess
import ctypes import ctypes
import tkinter as tk # import tkinter as tk
from PyQt5 import QtPrintSupport # from PyQt5 import QtPrintSupport
from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas
from reportlab.graphics import renderPM
from reportlab.lib.units import inch, mm
from reportlab.lib.pagesizes import landscape, portrait
from contextlib import contextmanager from contextlib import contextmanager
import gc import gc
@ -57,6 +63,8 @@ from flatcamEditors.FlatCAMExcEditor import FlatCAMExcEditor
from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor
from flatcamEditors.FlatCAMTextEditor import TextEditor from flatcamEditors.FlatCAMTextEditor import TextEditor
from flatcamParsers.ParseHPGL2 import HPGL2
from FlatCAMProcess import * from FlatCAMProcess import *
from FlatCAMWorkerStack import WorkerStack from FlatCAMWorkerStack import WorkerStack
# from flatcamGUI.VisPyVisuals import Color # from flatcamGUI.VisPyVisuals import Color
@ -133,7 +141,7 @@ class App(QtCore.QObject):
# ################## Version and VERSION DATE ############################## # ################## Version and VERSION DATE ##############################
# ########################################################################## # ##########################################################################
version = 8.99 version = 8.99
version_date = "2019/12/12" version_date = "2019/12/15"
beta = True beta = True
engine = '3D' engine = '3D'
@ -591,7 +599,7 @@ class App(QtCore.QObject):
"excellon_travelz": 2, "excellon_travelz": 2,
"excellon_endz": 0.5, "excellon_endz": 0.5,
"excellon_feedrate": 300, "excellon_feedrate": 300,
"excellon_spindlespeed": None, "excellon_spindlespeed": 0,
"excellon_dwell": False, "excellon_dwell": False,
"excellon_dwelltime": 1, "excellon_dwelltime": 1,
"excellon_toolchange": False, "excellon_toolchange": False,
@ -658,7 +666,7 @@ class App(QtCore.QObject):
"geometry_endz": 15.0, "geometry_endz": 15.0,
"geometry_feedrate": 120, "geometry_feedrate": 120,
"geometry_feedrate_z": 60, "geometry_feedrate_z": 60,
"geometry_spindlespeed": None, "geometry_spindlespeed": 0,
"geometry_dwell": False, "geometry_dwell": False,
"geometry_dwelltime": 1, "geometry_dwelltime": 1,
"geometry_ppname_g": 'default', "geometry_ppname_g": 'default',
@ -668,6 +676,7 @@ class App(QtCore.QObject):
"geometry_startz": None, "geometry_startz": None,
"geometry_feedrate_rapid": 1500, "geometry_feedrate_rapid": 1500,
"geometry_extracut": False, "geometry_extracut": False,
"geometry_extracut_length": 0.1,
"geometry_z_pdepth": -0.02, "geometry_z_pdepth": -0.02,
"geometry_f_plunge": False, "geometry_f_plunge": False,
"geometry_spindledir": 'CW', "geometry_spindledir": 'CW',
@ -901,12 +910,14 @@ class App(QtCore.QObject):
"tools_cal_verz": 0.1, "tools_cal_verz": 0.1,
"tools_cal_zeroz": False, "tools_cal_zeroz": False,
"tools_cal_toolchangez": 15, "tools_cal_toolchangez": 15,
"tools_cal_toolchange_xy": '',
"tools_cal_sec_point": 'tl',
# Utilities # Utilities
# file associations # file associations
"fa_excellon": 'drd, drl, exc, ncd, tap, xln', "fa_excellon": 'drd, drl, exc, ncd, tap, xln',
"fa_gcode": 'cnc, din, dnc, ecs, eia, fan, fgc, fnc, gc, gcd, gcode, h, hnc, i, min, mpf, mpr, nc, ncc, ' "fa_gcode": 'cnc, din, dnc, ecs, eia, fan, fgc, fnc, gc, gcd, gcode, h, hnc, i, min, mpf, mpr, nc, ncc, '
'ncg, ncp, ngc, out, plt, ply, rol, sbp, tap, xpi', 'ncg, ncp, ngc, out, ply, rol, sbp, tap, xpi',
"fa_gerber": 'art, bot, bsm, cmp, crc, crs, dim, gb0, gb1, gb2, gb3, gb4, gb5, gb6, gb7, gb8, gb9, gbd, ' "fa_gerber": 'art, bot, bsm, cmp, crc, crs, dim, gb0, gb1, gb2, gb3, gb4, gb5, gb6, gb7, gb8, gb9, gbd, '
'gbl, gbo, gbp, gbr, gbs, gdo, ger, gko, gm1, gm2, gm3, grb, gtl, gto, gtp, gts, ly15, ly2, ' 'gbl, gbo, gbp, gbr, gbs, gdo, ger, gko, gm1, gm2, gm3, grb, gtl, gto, gtp, gts, ly15, ly2, '
'mil, pho, plc, pls, smb, smt, sol, spb, spt, ssb, sst, stc, sts, top, tsm', 'mil, pho, plc, pls, smb, smt, sol, spb, spt, ssb, sst, stc, sts, top, tsm',
@ -1260,6 +1271,7 @@ class App(QtCore.QObject):
"geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry, "geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry,
"geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.cncfeedrate_rapid_entry, "geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.cncfeedrate_rapid_entry,
"geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb, "geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb,
"geometry_extracut_length": self.ui.geometry_defaults_form.geometry_adv_opt_group.e_cut_entry,
"geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry, "geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry,
"geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry, "geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry,
"geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio, "geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio,
@ -1482,6 +1494,8 @@ class App(QtCore.QObject):
"tools_cal_verz": self.ui.tools2_defaults_form.tools2_cal_group.verz_entry, "tools_cal_verz": self.ui.tools2_defaults_form.tools2_cal_group.verz_entry,
"tools_cal_zeroz": self.ui.tools2_defaults_form.tools2_cal_group.zeroz_cb, "tools_cal_zeroz": self.ui.tools2_defaults_form.tools2_cal_group.zeroz_cb,
"tools_cal_toolchangez": self.ui.tools2_defaults_form.tools2_cal_group.toolchangez_entry, "tools_cal_toolchangez": self.ui.tools2_defaults_form.tools2_cal_group.toolchangez_entry,
"tools_cal_toolchange_xy": self.ui.tools2_defaults_form.tools2_cal_group.toolchange_xy_entry,
"tools_cal_sec_point": self.ui.tools2_defaults_form.tools2_cal_group.second_point_radio,
# Utilities # Utilities
# File associations # File associations
@ -1759,7 +1773,7 @@ class App(QtCore.QObject):
self.ui.menufileimportdxf.triggered.connect(lambda: self.on_file_importdxf("geometry")) self.ui.menufileimportdxf.triggered.connect(lambda: self.on_file_importdxf("geometry"))
self.ui.menufileimportdxf_as_gerber.triggered.connect(lambda: self.on_file_importdxf("gerber")) self.ui.menufileimportdxf_as_gerber.triggered.connect(lambda: self.on_file_importdxf("gerber"))
self.ui.menufileimport_hpgl2_as_geo.triggered.connect(self.on_fileopenhpgl2)
self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg) self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng) self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng)
self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon) self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon)
@ -1770,6 +1784,7 @@ class App(QtCore.QObject):
self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject) self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject)
self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas) self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True)) self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
self.ui.menufilesave_object_pdf.triggered.connect(self.on_file_save_object_pdf)
self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults) self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults)
self.ui.menufileexportpref.triggered.connect(self.on_export_preferences) self.ui.menufileexportpref.triggered.connect(self.on_export_preferences)
@ -2476,6 +2491,7 @@ class App(QtCore.QObject):
# variable to store coordinates # variable to store coordinates
self.pos = (0, 0) self.pos = (0, 0)
self.pos_canvas = (0, 0)
self.pos_jump = (0, 0) self.pos_jump = (0, 0)
# variable to store mouse coordinates # variable to store mouse coordinates
@ -2545,7 +2561,7 @@ class App(QtCore.QObject):
self.exc_list = ['drd', 'drl', 'exc', 'ncd', 'tap', 'txt', 'xln'] self.exc_list = ['drd', 'drl', 'exc', 'ncd', 'tap', 'txt', 'xln']
self.gcode_list = ['cnc', 'din', 'dnc', 'ecs', 'eia', 'fan', 'fgc', 'fnc', 'gc', 'gcd', 'gcode', 'h', 'hnc', self.gcode_list = ['cnc', 'din', 'dnc', 'ecs', 'eia', 'fan', 'fgc', 'fnc', 'gc', 'gcd', 'gcode', 'h', 'hnc',
'i', 'min', 'mpf', 'mpr', 'nc', 'ncc', 'ncg', 'ngc', 'ncp', 'out', 'plt', 'ply', 'rol', 'i', 'min', 'mpf', 'mpr', 'nc', 'ncc', 'ncg', 'ngc', 'ncp', 'out', 'ply', 'rol',
'sbp', 'tap', 'xpi'] 'sbp', 'tap', 'xpi']
self.svg_list = ['svg'] self.svg_list = ['svg']
self.dxf_list = ['dxf'] self.dxf_list = ['dxf']
@ -2979,7 +2995,7 @@ class App(QtCore.QObject):
self.dblsidedtool.install(icon=QtGui.QIcon('share/doubleside16.png'), separator=True) self.dblsidedtool.install(icon=QtGui.QIcon('share/doubleside16.png'), separator=True)
self.cal_exc_tool = ToolCalibration(self) self.cal_exc_tool = ToolCalibration(self)
self.cal_exc_tool.install(icon=QtGui.QIcon('share/drill16.png'), pos=self.ui.menutool, self.cal_exc_tool.install(icon=QtGui.QIcon('share/calibrate_16.png'), pos=self.ui.menutool,
before=self.dblsidedtool.menuAction, before=self.dblsidedtool.menuAction,
separator=False) separator=False)
self.distance_tool = Distance(self) self.distance_tool = Distance(self)
@ -3148,6 +3164,7 @@ class App(QtCore.QObject):
# Tools Toolbar Signals # Tools Toolbar Signals
self.ui.dblsided_btn.triggered.connect(lambda: self.dblsidedtool.run(toggle=True)) self.ui.dblsided_btn.triggered.connect(lambda: self.dblsidedtool.run(toggle=True))
self.ui.cal_btn.triggered.connect(lambda: self.cal_exc_tool.run(toggle=True))
self.ui.cutout_btn.triggered.connect(lambda: self.cutout_tool.run(toggle=True)) self.ui.cutout_btn.triggered.connect(lambda: self.cutout_tool.run(toggle=True))
self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True)) self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True))
self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True)) self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True))
@ -4237,7 +4254,7 @@ class App(QtCore.QObject):
commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \ commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
"AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n"\ "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n"\
"# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \ "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
"ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n"\ "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n"\
"# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \ "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
"ListSys, MillDrills,\n"\ "ListSys, MillDrills,\n"\
"# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \ "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
@ -4297,23 +4314,41 @@ class App(QtCore.QObject):
# self.inform.emit('[selected] %s created & selected: %s' % # self.inform.emit('[selected] %s created & selected: %s' %
# (str(obj.kind).capitalize(), str(obj.options['name']))) # (str(obj.kind).capitalize(), str(obj.options['name'])))
if obj.kind == 'gerber': if obj.kind == 'gerber':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='green', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='green',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'excellon': elif obj.kind == 'excellon':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='brown', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='brown',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'cncjob': elif obj.kind == 'cncjob':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='blue', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='blue',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'geometry': elif obj.kind == 'geometry':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='red', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='red',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'script': elif obj.kind == 'script':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='orange', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='orange',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'document': elif obj.kind == 'document':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format( self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(), color='darkCyan', name=str(obj.options['name']))) kind=obj.kind.capitalize(),
color='darkCyan',
name=str(obj.options['name']), tx=_("created/selected"))
)
# update the SHELL auto-completer model with the name of the new object # update the SHELL auto-completer model with the name of the new object
self.shell._edit.set_model_data(self.myKeywords) self.shell._edit.set_model_data(self.myKeywords)
@ -4468,10 +4503,11 @@ class App(QtCore.QObject):
attributions_label = QtWidgets.QLabel( attributions_label = QtWidgets.QLabel(
_( _(
'Some of the icons used are from the following sources:<br>' 'Some of the icons used are from the following sources:<br>'
'<div>Icons made by <a href="https://www.flaticon.com/authors/freepik" ' '<div>Icons by <a href="https://www.flaticon.com/authors/freepik" '
'title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" ' 'title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" '
'title="Flaticon">www.flaticon.com</a></div>' 'title="Flaticon">www.flaticon.com</a></div>'
'Icons by <a target="_blank" href="https://icons8.com">Icons8</a>' '<div>Icons by <a target="_blank" href="https://icons8.com">Icons8</a></div>'
'Icons by <a href="http://www.onlinewebfonts.com">oNline Web Fonts</a>'
) )
) )
@ -7346,7 +7382,11 @@ class App(QtCore.QObject):
canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0)) canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
jump_loc = self.plotcanvas.translate_coords_2((cal_location[0], cal_location[1])) jump_loc = self.plotcanvas.translate_coords_2((cal_location[0], cal_location[1]))
j_pos = (canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
j_pos = (
int(canvas_origin.x() + round(jump_loc[0])),
int(canvas_origin.y() + round(jump_loc[1]))
)
cursor.setPos(j_pos[0], j_pos[1]) cursor.setPos(j_pos[0], j_pos[1])
else: else:
# find the canvas origin which is in the top left corner # find the canvas origin which is in the top left corner
@ -7358,10 +7398,15 @@ class App(QtCore.QObject):
# in pixels where the origin 0,0 is in the lowest left point of the display window (in our case is the # in pixels where the origin 0,0 is in the lowest left point of the display window (in our case is the
# canvas) and the point (width, height) is in the top-right location # canvas) and the point (width, height) is in the top-right location
loc = self.plotcanvas.axes.transData.transform_point(location) loc = self.plotcanvas.axes.transData.transform_point(location)
j_pos = (x0 + loc[0], y0 - loc[1]) j_pos = (
int(x0 + loc[0]),
int(y0 - loc[1])
)
cursor.setPos(j_pos[0], j_pos[1]) cursor.setPos(j_pos[0], j_pos[1])
self.plotcanvas.mouse = [location[0], location[1]]
self.plotcanvas.draw_cursor(x_pos=location[0], y_pos=location[1])
if self.grid_status() == True: if self.grid_status():
# Update cursor # Update cursor
self.app_cursor.set_data(np.asarray([(location[0], location[1])]), self.app_cursor.set_data(np.asarray([(location[0], location[1])]),
symbol='++', edge_color=self.cursor_color_3D, symbol='++', edge_color=self.cursor_color_3D,
@ -7376,8 +7421,7 @@ class App(QtCore.QObject):
self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: " self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy)) "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
self.inform.emit('[success] %s' % self.inform.emit('[success] %s' % _("Done."))
_("Done."))
return location return location
def on_copy_object(self): def on_copy_object(self):
@ -8431,23 +8475,17 @@ class App(QtCore.QObject):
was clicked, the pixel coordinates and the axes coordinates. was clicked, the pixel coordinates and the axes coordinates.
:return: None :return: None
""" """
self.pos = [] self.pos = list()
if self.is_legacy is False: if self.is_legacy is False:
event_pos = event.pos event_pos = event.pos
if self.defaults["global_pan_button"] == '2': pan_button = 2 if self.defaults["global_pan_button"] == '2'else 3
pan_button = 2
else:
pan_button = 3
# Set the mouse button for panning # Set the mouse button for panning
self.plotcanvas.view.camera.pan_button_setting = pan_button self.plotcanvas.view.camera.pan_button_setting = pan_button
else: else:
event_pos = (event.xdata, event.ydata) event_pos = (event.xdata, event.ydata)
# Matplotlib has the middle and right buttons mapped in reverse compared with VisPy # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
if self.defaults["global_pan_button"] == '2': pan_button = 3 if self.defaults["global_pan_button"] == '2'else 2
pan_button = 3
else:
pan_button = 2
# So it can receive key presses # So it can receive key presses
self.plotcanvas.native.setFocus() self.plotcanvas.native.setFocus()
@ -8836,17 +8874,29 @@ class App(QtCore.QObject):
def selected_message(self, curr_sel_obj): def selected_message(self, curr_sel_obj):
if curr_sel_obj: if curr_sel_obj:
if curr_sel_obj.kind == 'gerber': if curr_sel_obj.kind == 'gerber':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='green', name=str(curr_sel_obj.options['name']))) color='green',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'excellon': elif curr_sel_obj.kind == 'excellon':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='brown', name=str(curr_sel_obj.options['name']))) color='brown',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'cncjob': elif curr_sel_obj.kind == 'cncjob':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='blue', name=str(curr_sel_obj.options['name']))) color='blue',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'geometry': elif curr_sel_obj.kind == 'geometry':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='red', name=str(curr_sel_obj.options['name']))) color='red',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
def delete_hover_shape(self): def delete_hover_shape(self):
self.hover_shapes.clear() self.hover_shapes.clear()
@ -9217,7 +9267,7 @@ class App(QtCore.QObject):
# https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions # https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions
_filter_ = "G-Code Files (*.txt *.nc *.ncc *.tap *.gcode *.cnc *.ecs *.fnc *.dnc *.ncg *.gc *.fan *.fgc" \ _filter_ = "G-Code Files (*.txt *.nc *.ncc *.tap *.gcode *.cnc *.ecs *.fnc *.dnc *.ncg *.gc *.fan *.fgc" \
" *.din *.xpi *.hnc *.h *.i *.ncp *.min *.gcd *.rol *.mpr *.ply *.out *.eia *.plt *.sbp *.mpf);;" \ " *.din *.xpi *.hnc *.h *.i *.ncp *.min *.gcd *.rol *.mpr *.ply *.out *.eia *.sbp *.mpf);;" \
"All Files (*.*)" "All Files (*.*)"
if name is None: if name is None:
@ -9277,6 +9327,44 @@ class App(QtCore.QObject):
# thread safe. The new_project() # thread safe. The new_project()
self.open_project(filename) self.open_project(filename)
def on_fileopenhpgl2(self, signal: bool = None, name=None):
"""
File menu callback for opening a HPGL2.
:param signal: required because clicking the entry will generate a checked signal which needs a container
:return: None
"""
self.report_usage("on_fileopenhpgl2")
App.log.debug("on_fileopenhpgl2()")
_filter_ = "HPGL2 Files (*.plt);;" \
"All Files (*.*)"
if name is None:
try:
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"),
directory=self.get_last_folder(),
filter=_filter_)
except TypeError:
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), filter=_filter_)
filenames = [str(filename) for filename in filenames]
else:
filenames = [name]
self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
"Canvas initialization finished in"), '%.2f' % self.used_time,
_("Opening HPGL2 file.")),
alignment=Qt.AlignBottom | Qt.AlignLeft,
color=QtGui.QColor("gray"))
if len(filenames) == 0:
self.inform.emit('[WARNING_NOTCL] %s' % _("Open HPGL2 file cancelled."))
else:
for filename in filenames:
if filename != '':
self.worker_task.emit({'fcn': self.open_hpgl2, 'params': [filename]})
def on_file_openconfig(self, signal: bool = None): def on_file_openconfig(self, signal: bool = None):
""" """
File menu callback for opening a config file. File menu callback for opening a config file.
@ -10108,23 +10196,25 @@ class App(QtCore.QObject):
try: try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName( filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Save Project As ..."), caption=_("Save Project As ..."),
directory=_('{l_save}/Project_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date), directory=('{l_save}/{proj}_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date,
filter=filter_) proj=_("Project")),
filter=filter_
)
except TypeError: except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Project As ..."), filter=filter_) filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Project As ..."), filter=filter_)
filename = str(filename) filename = str(filename)
if filename == '': if filename == '':
self.inform.emit('[WARNING_NOTCL] %s' % self.inform.emit('[WARNING_NOTCL] %s' % _("Save Project cancelled."))
_("Save Project cancelled."))
return return
try: try:
f = open(filename, 'r') f = open(filename, 'r')
f.close() f.close()
except IOError: except IOError:
pass self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application."))
return
if use_thread is True: if use_thread is True:
self.worker_task.emit({'fcn': self.save_project, self.worker_task.emit({'fcn': self.save_project,
@ -10143,6 +10233,50 @@ class App(QtCore.QObject):
self.set_ui_title(name=self.project_filename) self.set_ui_title(name=self.project_filename)
self.should_we_save = False self.should_we_save = False
def on_file_save_object_pdf(self, use_thread=True):
self.date = str(datetime.today()).rpartition('.')[0]
self.date = ''.join(c for c in self.date if c not in ':-')
self.date = self.date.replace(' ', '_')
try:
obj_active = self.collection.get_active()
obj_name = _(str(obj_active.options['name']))
except AttributeError as err:
log.debug("App.on_file_save_object_pdf() --> %s" % str(err))
self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
return
filter_ = "PDF File (*.PDF);; All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Save Object as PDF ..."),
directory=('{l_save}/{obj_name}_{date}').format(l_save=str(self.get_last_save_folder()),
obj_name=obj_name,
date=self.date),
filter=filter_
)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Object as PDF ..."), filter=filter_)
filename = str(filename)
if filename == '':
self.inform.emit('[WARNING_NOTCL] %s' % _("Save Object PDF cancelled."))
return
if use_thread is True:
self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name]})
else:
self.save_pdf(filename, obj_name)
# self.save_project(filename)
if self.defaults["global_open_style"] is False:
self.file_opened.emit("pdf", filename)
self.file_saved.emit("pdf", filename)
def save_pdf(self, file_name, obj_name):
self.film_tool.export_positive(obj_name=obj_name, box_name=obj_name, filename=file_name, ftype='pdf')
def export_svg(self, obj_name, filename, scale_stroke_factor=0.00): def export_svg(self, obj_name, filename, scale_stroke_factor=0.00):
""" """
Exports a Geometry Object to an SVG file. Exports a Geometry Object to an SVG file.
@ -10870,6 +11004,73 @@ class App(QtCore.QObject):
self.inform.emit('[success] %s: %s' % self.inform.emit('[success] %s: %s' %
(_("Opened"), filename)) (_("Opened"), filename))
def open_hpgl2(self, filename, outname=None):
"""
Opens a HPGL2 file, parses it and creates a new object for
it in the program. Thread-safe.
:param outname: Name of the resulting object. None causes the
name to be that of the file.
:param filename: HPGL2 file filename
:type filename: str
:return: None
"""
filename = filename
# How the object should be initialized
def obj_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry), \
"Expected to initialize a FlatCAMGeometry but got %s" % type(geo_obj)
# Opening the file happens here
obj = HPGL2(self)
try:
HPGL2.parse_file(obj, filename)
except IOError:
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename))
return "fail"
except ParseError as err:
app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(err)))
app_obj.log.error(str(err))
return "fail"
except Exception as e:
log.debug("App.open_hpgl2() --> %s" % str(e))
msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
msg += traceback.format_exc()
app_obj.inform.emit(msg)
return "fail"
geo_obj.multigeo = True
geo_obj.solid_geometry = deepcopy(obj.solid_geometry)
geo_obj.tools = deepcopy(obj.tools)
geo_obj.source_file = deepcopy(obj.source_file)
del obj
if not geo_obj.solid_geometry:
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("Object is not HPGL2 file or empty. Aborting object creation."))
return "fail"
App.log.debug("open_hpgl2()")
with self.proc_container.new(_("Opening HPGL2")) as proc:
# Object name
name = outname or filename.split('/')[-1].split('\\')[-1]
# # ## Object creation # ##
ret = self.new_object("geometry", name, obj_init, autoselected=False)
if ret == 'fail':
self.inform.emit('[ERROR_NOTCL]%s' % _(' Open HPGL2 failed. Probable not a HPGL2 file.'))
return 'fail'
# Register recent file
self.file_opened.emit("geometry", filename)
# GUI feedback
self.inform.emit('[success] %s: %s' % (_("Opened"), filename))
def open_script(self, filename, outname=None, silent=False): def open_script(self, filename, outname=None, silent=False):
""" """
Opens a Script file, parses it and creates a new object for Opens a Script file, parses it and creates a new object for

View File

@ -12,7 +12,7 @@
# ########################################################## # ##########################################################
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner
from camlib import to_dict from camlib import to_dict
import sys import sys
@ -505,7 +505,7 @@ class ToolsDB(QtWidgets.QWidget):
self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
table_hlay.addWidget(self.table_widget) table_hlay.addWidget(self.table_widget)
self.table_widget.setColumnCount(26) self.table_widget.setColumnCount(27)
# self.table_widget.setColumnWidth(0, 20) # self.table_widget.setColumnWidth(0, 20)
self.table_widget.setHorizontalHeaderLabels( self.table_widget.setHorizontalHeaderLabels(
[ [
@ -530,6 +530,7 @@ class ToolsDB(QtWidgets.QWidget):
_("Dwelltime"), _("Dwelltime"),
_("Preprocessor"), _("Preprocessor"),
_("ExtraCut"), _("ExtraCut"),
_("E-Cut Length"),
_("Toolchange"), _("Toolchange"),
_("Toolchange XY"), _("Toolchange XY"),
_("Toolchange Z"), _("Toolchange Z"),
@ -620,23 +621,30 @@ class ToolsDB(QtWidgets.QWidget):
"such as that this point is covered by this extra cut to\n" "such as that this point is covered by this extra cut to\n"
"ensure a complete isolation.")) "ensure a complete isolation."))
self.table_widget.horizontalHeaderItem(21).setToolTip( self.table_widget.horizontalHeaderItem(21).setToolTip(
_("Extra Cut length.\n"
"If checked, after a isolation is finished an extra cut\n"
"will be added where the start and end of isolation meet\n"
"such as that this point is covered by this extra cut to\n"
"ensure a complete isolation. This is the length of\n"
"the extra cut."))
self.table_widget.horizontalHeaderItem(22).setToolTip(
_("Toolchange.\n" _("Toolchange.\n"
"It will create a toolchange event.\n" "It will create a toolchange event.\n"
"The kind of toolchange is determined by\n" "The kind of toolchange is determined by\n"
"the preprocessor file.")) "the preprocessor file."))
self.table_widget.horizontalHeaderItem(22).setToolTip( self.table_widget.horizontalHeaderItem(23).setToolTip(
_("Toolchange XY.\n" _("Toolchange XY.\n"
"A set of coordinates in the format (x, y).\n" "A set of coordinates in the format (x, y).\n"
"Will determine the cartesian position of the point\n" "Will determine the cartesian position of the point\n"
"where the tool change event take place.")) "where the tool change event take place."))
self.table_widget.horizontalHeaderItem(23).setToolTip( self.table_widget.horizontalHeaderItem(24).setToolTip(
_("Toolchange Z.\n" _("Toolchange Z.\n"
"The position on Z plane where the tool change event take place.")) "The position on Z plane where the tool change event take place."))
self.table_widget.horizontalHeaderItem(24).setToolTip( self.table_widget.horizontalHeaderItem(25).setToolTip(
_("Start Z.\n" _("Start Z.\n"
"If it's left empty it will not be used.\n" "If it's left empty it will not be used.\n"
"A position on Z plane to move immediately after job start.")) "A position on Z plane to move immediately after job start."))
self.table_widget.horizontalHeaderItem(25).setToolTip( self.table_widget.horizontalHeaderItem(26).setToolTip(
_("End Z.\n" _("End Z.\n"
"A position on Z plane to move immediately after job stop.")) "A position on Z plane to move immediately after job stop."))
@ -840,6 +848,16 @@ class ToolsDB(QtWidgets.QWidget):
multidepth_item.set_value(data['multidepth']) multidepth_item.set_value(data['multidepth'])
widget.setCellWidget(row, 8, multidepth_item) widget.setCellWidget(row, 8, multidepth_item)
# to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild()
# multidepth_item = QtWidgets.QWidget()
# cb = FCCheckBox()
# cb.set_value(data['multidepth'])
# qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item)
# qhboxlayout.addWidget(cb)
# qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
# qhboxlayout.setContentsMargins(0, 0, 0, 0)
# widget.setCellWidget(row, 8, multidepth_item)
depth_per_pass_item = FCDoubleSpinner() depth_per_pass_item = FCDoubleSpinner()
depth_per_pass_item.set_precision(self.decimals) depth_per_pass_item.set_precision(self.decimals)
depth_per_pass_item.setSingleStep(0.1) depth_per_pass_item.setSingleStep(0.1)
@ -890,8 +908,11 @@ class ToolsDB(QtWidgets.QWidget):
frrapids_item.set_value(float(data['feedrate_rapid'])) frrapids_item.set_value(float(data['feedrate_rapid']))
widget.setCellWidget(row, 15, frrapids_item) widget.setCellWidget(row, 15, frrapids_item)
spindlespeed_item = QtWidgets.QTableWidgetItem(str(data['spindlespeed']) if data['spindlespeed'] else '') spindlespeed_item = FCSpinner()
widget.setItem(row, 16, spindlespeed_item) spindlespeed_item.set_range(0, 1000000)
spindlespeed_item.set_value(int(data['spindlespeed']))
spindlespeed_item.setSingleStep(100)
widget.setCellWidget(row, 16, spindlespeed_item)
dwell_item = FCCheckBox() dwell_item = FCCheckBox()
dwell_item.set_value(data['dwell']) dwell_item.set_value(data['dwell'])
@ -913,12 +934,18 @@ class ToolsDB(QtWidgets.QWidget):
ecut_item.set_value(data['extracut']) ecut_item.set_value(data['extracut'])
widget.setCellWidget(row, 20, ecut_item) widget.setCellWidget(row, 20, ecut_item)
ecut_length_item = FCDoubleSpinner()
ecut_length_item.set_precision(self.decimals)
ecut_length_item.set_range(0.0, 9999.9999)
ecut_length_item.set_value(data['extracut_length'])
widget.setCellWidget(row, 21, ecut_length_item)
toolchange_item = FCCheckBox() toolchange_item = FCCheckBox()
toolchange_item.set_value(data['toolchange']) toolchange_item.set_value(data['toolchange'])
widget.setCellWidget(row, 21, toolchange_item) widget.setCellWidget(row, 22, toolchange_item)
toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '') toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '')
widget.setItem(row, 22, toolchangexy_item) widget.setItem(row, 23, toolchangexy_item)
toolchangez_item = FCDoubleSpinner() toolchangez_item = FCDoubleSpinner()
toolchangez_item.set_precision(self.decimals) toolchangez_item.set_precision(self.decimals)
@ -929,10 +956,10 @@ class ToolsDB(QtWidgets.QWidget):
toolchangez_item.set_range(0.0000, 9999.9999) toolchangez_item.set_range(0.0000, 9999.9999)
toolchangez_item.set_value(float(data['toolchangez'])) toolchangez_item.set_value(float(data['toolchangez']))
widget.setCellWidget(row, 23, toolchangez_item) widget.setCellWidget(row, 24, toolchangez_item)
startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '') startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '')
widget.setItem(row, 24, startz_item) widget.setItem(row, 25, startz_item)
endz_item = FCDoubleSpinner() endz_item = FCDoubleSpinner()
endz_item.set_precision(self.decimals) endz_item.set_precision(self.decimals)
@ -943,7 +970,7 @@ class ToolsDB(QtWidgets.QWidget):
endz_item.set_range(0.0000, 9999.9999) endz_item.set_range(0.0000, 9999.9999)
endz_item.set_value(float(data['endz'])) endz_item.set_value(float(data['endz']))
widget.setCellWidget(row, 25, endz_item) widget.setCellWidget(row, 26, endz_item)
def on_tool_add(self): def on_tool_add(self):
""" """
@ -970,6 +997,7 @@ class ToolsDB(QtWidgets.QWidget):
"dwelltime": float(self.app.defaults["geometry_dwelltime"]), "dwelltime": float(self.app.defaults["geometry_dwelltime"]),
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"], "toolchangexy": self.app.defaults["geometry_toolchangexy"],
"toolchangez": float(self.app.defaults["geometry_toolchangez"]), "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
@ -1257,8 +1285,7 @@ class ToolsDB(QtWidgets.QWidget):
elif column_header_text == 'FR Rapids': elif column_header_text == 'FR Rapids':
default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value() default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Spindle Speed': elif column_header_text == 'Spindle Speed':
default_data['spindlespeed'] = float(self.table_widget.item(row, col).text()) \ default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
if self.table_widget.item(row, col).text() is not '' else None
elif column_header_text == 'Dwell': elif column_header_text == 'Dwell':
default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value() default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Dwelltime': elif column_header_text == 'Dwelltime':
@ -1267,6 +1294,8 @@ class ToolsDB(QtWidgets.QWidget):
default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value() default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'ExtraCut': elif column_header_text == 'ExtraCut':
default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value() default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == "E-Cut Length":
default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Toolchange': elif column_header_text == 'Toolchange':
default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value() default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Toolchange XY': elif column_header_text == 'Toolchange XY':

View File

@ -195,10 +195,11 @@ class FlatCAMObj(QtCore.QObject):
pass pass
# Creates problems on focusOut # Creates problems on focusOut
# try: try:
# self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
# except (TypeError, AttributeError): except (TypeError, AttributeError):
# pass pass
# self.ui.skew_button.clicked.connect(self.on_skew_button_click) # self.ui.skew_button.clicked.connect(self.on_skew_button_click)
def build_ui(self): def build_ui(self):
@ -267,9 +268,19 @@ class FlatCAMObj(QtCore.QObject):
def on_scale_button_click(self): def on_scale_button_click(self):
self.read_form() self.read_form()
factor = self.ui.scale_entry.get_value() try:
factor = float(eval(self.ui.scale_entry.get_value()))
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
return
if type(factor) != float:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
# if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0 # if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0
if factor == 1.0: if factor == 1.0:
self.app.inform.emit('[success] %s' % _("Scale done."))
return return
log.debug("FlatCAMObj.on_scale_button_click()") log.debug("FlatCAMObj.on_scale_button_click()")
@ -277,6 +288,8 @@ class FlatCAMObj(QtCore.QObject):
def worker_task(): def worker_task():
with self.app.proc_container.new(_("Scaling...")): with self.app.proc_container.new(_("Scaling...")):
self.scale(factor) self.scale(factor)
self.app.inform.emit('[success] %s' % _("Scale done."))
self.app.proc_container.update_view_text('') self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")): with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot() self.plot()
@ -625,6 +638,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# Mouse events # Mouse events
self.mr = None self.mr = None
self.mm = None
self.mp = None
# dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly
self.poly_dict = dict() self.poly_dict = dict()
@ -1066,11 +1081,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if self.app.is_legacy is False: if self.app.is_legacy is False:
event_pos = event.pos event_pos = event.pos
right_button = 2 right_button = 2
event_is_dragging = self.app.event_is_dragging self.app.event_is_dragging = self.app.event_is_dragging
else: else:
event_pos = (event.xdata, event.ydata) event_pos = (event.xdata, event.ydata)
right_button = 3 right_button = 3
event_is_dragging = self.app.ui.popMenu.mouse_is_panning self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
try: try:
x = float(event_pos[0]) x = float(event_pos[0])
@ -1080,11 +1095,18 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
event_pos = (x, y) event_pos = (x, y)
curr_pos = self.app.plotcanvas.translate_coords(event_pos) curr_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
else:
curr_pos = (curr_pos[0], curr_pos[1])
if event.button == 1: if event.button == 1:
clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1])) clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
if clicked_poly: if self.app.selection_type is not None:
self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
self.app.selection_type = None
elif clicked_poly:
if clicked_poly not in self.poly_dict.values(): if clicked_poly not in self.poly_dict.values():
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly, shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
color=self.app.defaults['global_sel_draw_color'] + 'AF', color=self.app.defaults['global_sel_draw_color'] + 'AF',
@ -1113,8 +1135,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.app.tool_shapes.redraw() self.app.tool_shapes.redraw()
else: else:
self.app.inform.emit(_("No polygon detected under click position.")) self.app.inform.emit(_("No polygon detected under click position."))
elif event.button == right_button and self.app.event_is_dragging is False:
elif event.button == right_button and event_is_dragging is False:
# restore the Grid snapping if it was active before # restore the Grid snapping if it was active before
if self.grid_status_memory is True: if self.grid_status_memory is True:
self.app.ui.grid_snap_btn.trigger() self.app.ui.grid_snap_btn.trigger()
@ -1136,6 +1157,75 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting.")) self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
def selection_area_handler(self, start_pos, end_pos, sel_type):
"""
:param start_pos: mouse position when the selection LMB click was done
:param end_pos: mouse position when the left mouse button is released
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
:return:
"""
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
# delete previous selection shape
self.app.delete_selection_shape()
added_poly_count = 0
try:
for geo in self.solid_geometry:
if geo not in self.poly_dict.values():
if sel_type is True:
if geo.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
else:
if poly_selection.intersects(geo):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
except TypeError:
if self.solid_geometry not in self.poly_dict.values():
if sel_type is True:
if self.solid_geometry.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
else:
if poly_selection.intersects(self.solid_geometry):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
if added_poly_count > 0:
self.app.tool_shapes.redraw()
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"),
int(added_poly_count),
_("Click to add next polygon or right click to start isolation."))
)
else:
self.app.inform.emit(_("No polygon in selection."))
def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None, def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
milling_type=None, follow=None, plot=True): milling_type=None, follow=None, plot=True):
""" """
@ -1242,6 +1332,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"ppname_g": self.app.defaults['geometry_ppname_g'], "ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'], "depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'], "extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'], "toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'], "toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'], "endz": self.app.defaults['geometry_endz'],
@ -1718,18 +1809,25 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:param aperture: string; aperture for which to clear the mark shapes :param aperture: string; aperture for which to clear the mark shapes
:return: :return:
""" """
try:
if self.mark_shapes:
if aperture == 'all': if aperture == 'all':
for apid in list(self.apertures.keys()): for apid in list(self.apertures.keys()):
if self.app.is_legacy is True: try:
self.mark_shapes[apid].clear(update=False) if self.app.is_legacy is True:
else: self.mark_shapes[apid].clear(update=False)
self.mark_shapes[apid].clear(update=True) else:
self.mark_shapes[apid].clear(update=True)
except Exception as e:
log.debug("FlatCAMGerber.clear_plot_apertures() 'all' --> %s" % str(e))
else: else:
self.mark_shapes[aperture].clear(update=True) try:
except Exception as e: if self.app.is_legacy is True:
log.debug("FlatCAMGerber.clear_plot_apertures() --> %s" % str(e)) self.mark_shapes[aperture].clear(update=False)
else:
self.mark_shapes[aperture].clear(update=True)
except Exception as e:
log.debug("FlatCAMGerber.clear_plot_apertures() 'aperture' --> %s" % str(e))
def clear_mark_all(self): def clear_mark_all(self):
self.ui.mark_all_cb.set_value(False) self.ui.mark_all_cb.set_value(False)
@ -2117,7 +2215,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
"toolchangexy": "0.0, 0.0", "toolchangexy": "0.0, 0.0",
"endz": 2.0, "endz": 2.0,
"startz": None, "startz": None,
"spindlespeed": None, "spindlespeed": 0,
"dwell": True, "dwell": True,
"dwelltime": 1000, "dwelltime": 1000,
"ppname_e": 'defaults', "ppname_e": 'defaults',
@ -2128,10 +2226,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
}) })
# TODO: Document this. # TODO: Document this.
self.tool_cbs = {} self.tool_cbs = dict()
# dict to hold the tool number as key and tool offset as value # dict to hold the tool number as key and tool offset as value
self.tool_offset = {} self.tool_offset = dict()
# variable to store the total amount of drills per job # variable to store the total amount of drills per job
self.tot_drill_cnt = 0 self.tot_drill_cnt = 0
@ -3175,7 +3273,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
job_obj.feedrate = float(self.options["feedrate"]) job_obj.feedrate = float(self.options["feedrate"])
job_obj.feedrate_rapid = float(self.options["feedrate_rapid"]) job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] != 0 else None
job_obj.spindledir = self.app.defaults['excellon_spindledir'] job_obj.spindledir = self.app.defaults['excellon_spindledir']
job_obj.dwell = self.options["dwell"] job_obj.dwell = self.options["dwell"]
job_obj.dwelltime = float(self.options["dwelltime"]) job_obj.dwelltime = float(self.options["dwelltime"])
@ -3254,30 +3352,31 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def convert_units(self, units): def convert_units(self, units):
log.debug("FlatCAMObj.FlatCAMExcellon.convert_units()") log.debug("FlatCAMObj.FlatCAMExcellon.convert_units()")
factor = Excellon.convert_units(self, units) Excellon.convert_units(self, units)
self.options['drillz'] = float(self.options['drillz']) * factor # factor = Excellon.convert_units(self, units)
self.options['travelz'] = float(self.options['travelz']) * factor # self.options['drillz'] = float(self.options['drillz']) * factor
self.options['feedrate'] = float(self.options['feedrate']) * factor # self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor # self.options['feedrate'] = float(self.options['feedrate']) * factor
self.options['toolchangez'] = float(self.options['toolchangez']) * factor # self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
# self.options['toolchangez'] = float(self.options['toolchangez']) * factor
if self.app.defaults["excellon_toolchangexy"] == '': #
self.options['toolchangexy'] = "0.0, 0.0" # if self.app.defaults["excellon_toolchangexy"] == '':
else: # self.options['toolchangexy'] = "0.0, 0.0"
coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")] # else:
if len(coords_xy) < 2: # coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")]
self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be " # if len(coords_xy) < 2:
"in the format (x, y) \n" # self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be "
"but now there is only one value, not two. ")) # "in the format (x, y) \n"
return 'fail' # "but now there is only one value, not two. "))
coords_xy[0] *= factor # return 'fail'
coords_xy[1] *= factor # coords_xy[0] *= factor
self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) # coords_xy[1] *= factor
# self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
if self.options['startz'] is not None: #
self.options['startz'] = float(self.options['startz']) * factor # if self.options['startz'] is not None:
self.options['endz'] = float(self.options['endz']) * factor # self.options['startz'] = float(self.options['startz']) * factor
# self.options['endz'] = float(self.options['endz']) * factor
def on_solid_cb_click(self, *args): def on_solid_cb_click(self, *args):
if self.muted_ui: if self.muted_ui:
@ -3439,12 +3538,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"feedrate": 5.0, "feedrate": 5.0,
"feedrate_z": 5.0, "feedrate_z": 5.0,
"feedrate_rapid": 5.0, "feedrate_rapid": 5.0,
"spindlespeed": None, "spindlespeed": 0,
"dwell": True, "dwell": True,
"dwelltime": 1000, "dwelltime": 1000,
"multidepth": False, "multidepth": False,
"depthperpass": 0.002, "depthperpass": 0.002,
"extracut": False, "extracut": False,
"extracut_length": 0.1,
"endz": 2.0, "endz": 2.0,
"startz": None, "startz": None,
"toolchange": False, "toolchange": False,
@ -3684,6 +3784,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"feedrate_probe": self.ui.feedrate_probe_entry, "feedrate_probe": self.ui.feedrate_probe_entry,
"depthperpass": self.ui.maxdepth_entry, "depthperpass": self.ui.maxdepth_entry,
"extracut": self.ui.extracut_cb, "extracut": self.ui.extracut_cb,
"extracut_length": self.ui.e_cut_entry,
"toolchange": self.ui.toolchangeg_cb, "toolchange": self.ui.toolchangeg_cb,
"toolchangez": self.ui.toolchangez_entry, "toolchangez": self.ui.toolchangez_entry,
"endz": self.ui.gendz_entry, "endz": self.ui.gendz_entry,
@ -3722,10 +3823,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"ppname_g": None, "ppname_g": None,
"depthperpass": None, "depthperpass": None,
"extracut": None, "extracut": None,
"extracut_length": None,
"toolchange": None, "toolchange": None,
"toolchangez": None, "toolchangez": None,
"endz": None, "endz": None,
"spindlespeed": None, "spindlespeed": 0,
"toolchangexy": None, "toolchangexy": None,
"startz": None "startz": None
}) })
@ -3814,6 +3916,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.fr_rapidlabel.hide() self.ui.fr_rapidlabel.hide()
self.ui.cncfeedrate_rapid_entry.hide() self.ui.cncfeedrate_rapid_entry.hide()
self.ui.extracut_cb.hide() self.ui.extracut_cb.hide()
self.ui.e_cut_entry.hide()
self.ui.pdepth_label.hide() self.ui.pdepth_label.hide()
self.ui.pdepth_entry.hide() self.ui.pdepth_entry.hide()
self.ui.feedrate_probe_label.hide() self.ui.feedrate_probe_label.hide()
@ -3821,9 +3924,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else: else:
self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced')) self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
self.ui.e_cut_entry.setDisabled(True)
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click) 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.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed) self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
self.ui.addtool_entry.returnPressed.connect(lambda: self.on_tool_add()) self.ui.addtool_entry.returnPressed.connect(lambda: self.on_tool_add())
@ -4975,6 +5081,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"]
multidepth = tools_dict[tooluid_key]['data']["multidepth"] multidepth = tools_dict[tooluid_key]['data']["multidepth"]
extracut = tools_dict[tooluid_key]['data']["extracut"] extracut = tools_dict[tooluid_key]['data']["extracut"]
extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
toolchange = tools_dict[tooluid_key]['data']["toolchange"] toolchange = tools_dict[tooluid_key]['data']["toolchange"]
toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] toolchangez = tools_dict[tooluid_key]['data']["toolchangez"]
@ -5006,7 +5113,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
multidepth=multidepth, depthpercut=depthpercut, multidepth=multidepth, depthpercut=depthpercut,
extracut=extracut, startz=startz, endz=endz, extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
pp_geometry_name=pp_geometry_name, pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt) tool_no=tool_cnt)
@ -5127,6 +5234,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"]
multidepth = tools_dict[tooluid_key]['data']["multidepth"] multidepth = tools_dict[tooluid_key]['data']["multidepth"]
extracut = tools_dict[tooluid_key]['data']["extracut"] extracut = tools_dict[tooluid_key]['data']["extracut"]
extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
toolchange = tools_dict[tooluid_key]['data']["toolchange"] toolchange = tools_dict[tooluid_key]['data']["toolchange"]
toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] toolchangez = tools_dict[tooluid_key]['data']["toolchangez"]
@ -5158,7 +5266,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
multidepth=multidepth, depthpercut=depthpercut, multidepth=multidepth, depthpercut=depthpercut,
extracut=extracut, startz=startz, endz=endz, extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
pp_geometry_name=pp_geometry_name, pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt) tool_no=tool_cnt)
@ -5226,7 +5334,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
spindlespeed=None, dwell=None, dwelltime=None, spindlespeed=None, dwell=None, dwelltime=None,
multidepth=None, depthperpass=None, multidepth=None, depthperpass=None,
toolchange=None, toolchangez=None, toolchangexy=None, toolchange=None, toolchangez=None, toolchangexy=None,
extracut=None, startz=None, endz=None, extracut=None, extracut_length=None, startz=None, endz=None,
pp=None, pp=None,
segx=None, segy=None, segx=None, segy=None,
use_thread=True, use_thread=True,
@ -5266,6 +5374,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
extracut = extracut if extracut is not None else float(self.options["extracut"]) extracut = extracut if extracut is not None else float(self.options["extracut"])
extracut_length = extracut_length if extracut_length is not None else float(self.options["extracut_length"])
startz = startz if startz is not None else self.options["startz"] startz = startz if startz is not None else self.options["startz"]
endz = endz if endz is not None else float(self.options["endz"]) endz = endz if endz is not None else float(self.options["endz"])
@ -5320,7 +5430,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime, spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
multidepth=multidepth, depthpercut=depthperpass, multidepth=multidepth, depthpercut=depthperpass,
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
extracut=extracut, startz=startz, endz=endz, extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
pp_geometry_name=ppname_g pp_geometry_name=ppname_g
) )
@ -5625,7 +5735,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
tooldia = self.ui.addtool_entry.get_value() tooldia = self.ui.addtool_entry.get_value()
if tooldia: if tooldia:
tooldia *= factor tooldia *= factor
# limit the decimals to 2 for METRIC and 3 for INCH
tooldia = float('%.*f' % (self.decimals, tooldia)) tooldia = float('%.*f' % (self.decimals, tooldia))
self.ui.addtool_entry.set_value(tooldia) self.ui.addtool_entry.set_value(tooldia)
@ -5635,7 +5744,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def plot_element(self, element, color='#FF0000FF', visible=None): def plot_element(self, element, color='#FF0000FF', visible=None):
visible = visible if visible else self.options['plot'] visible = visible if visible else self.options['plot']
try: try:
for sub_el in element: for sub_el in element:
self.plot_element(sub_el) self.plot_element(sub_el)
@ -5951,15 +6059,22 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.ui_disconnect() self.ui_disconnect()
FlatCAMObj.build_ui(self) FlatCAMObj.build_ui(self)
# if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it
if self.cnc_tools:
self.ui.cnc_tools_table.show()
else:
self.ui.cnc_tools_table.hide()
self.units = self.app.defaults['units'].upper() self.units = self.app.defaults['units'].upper()
# if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it
self.ui.cnc_tools_table.hide()
if self.cnc_tools:
self.ui.cnc_tools_table.show()
self.build_cnc_tools_table()
self.ui.exc_cnc_tools_table.hide()
if self.exc_cnc_tools:
self.ui.exc_cnc_tools_table.show()
self.build_excellon_cnc_tools()
#
self.ui_connect()
def build_cnc_tools_table(self):
offset = 0 offset = 0
tool_idx = 0 tool_idx = 0
@ -6058,7 +6173,90 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight()) self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight())
self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight()) self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight())
self.ui_connect() def build_excellon_cnc_tools(self):
tool_idx = 0
n = len(self.exc_cnc_tools)
self.ui.exc_cnc_tools_table.setRowCount(n)
for tooldia_key, dia_value in self.exc_cnc_tools.items():
tool_idx += 1
row_no = tool_idx - 1
id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx))
dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia_key)))
nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_drills']))
nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_slots']))
cutz_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['offset_z']) + self.z_cut))
id.setFlags(QtCore.Qt.ItemIsEnabled)
dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled)
nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled)
cutz_item.setFlags(QtCore.Qt.ItemIsEnabled)
# hack so the checkbox stay centered in the table cell
# used this:
# https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row
# plot_item = QtWidgets.QWidget()
# checkbox = FCCheckBox()
# checkbox.setCheckState(QtCore.Qt.Checked)
# qhboxlayout = QtWidgets.QHBoxLayout(plot_item)
# qhboxlayout.addWidget(checkbox)
# qhboxlayout.setAlignment(QtCore.Qt.AlignCenter)
# qhboxlayout.setContentsMargins(0, 0, 0, 0)
plot_item = FCCheckBox()
plot_item.setLayoutDirection(QtCore.Qt.RightToLeft)
tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool']))
if self.ui.plot_cb.isChecked():
plot_item.setChecked(True)
# TODO until the feature of individual plot for an Excellon tool is implemented
plot_item.setDisabled(True)
self.ui.exc_cnc_tools_table.setItem(row_no, 0, id) # Tool name/id
self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter
self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item) # Nr of drills
self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item) # Nr of slots
# ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item) # Tool unique ID)
self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item)
self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item)
for row in range(tool_idx):
self.ui.exc_cnc_tools_table.item(row, 0).setFlags(
self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable)
self.ui.exc_cnc_tools_table.resizeColumnsToContents()
self.ui.exc_cnc_tools_table.resizeRowsToContents()
vertical_header = self.ui.exc_cnc_tools_table.verticalHeader()
vertical_header.hide()
self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader()
horizontal_header.setMinimumSectionSize(10)
horizontal_header.setDefaultSectionSize(70)
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
horizontal_header.resizeSection(0, 20)
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents)
horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
# horizontal_header.setStretchLastSection(True)
self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.ui.exc_cnc_tools_table.setColumnWidth(0, 20)
self.ui.exc_cnc_tools_table.setColumnWidth(6, 17)
self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight())
self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight())
def set_ui(self, ui): def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui) FlatCAMObj.set_ui(self, ui)
@ -6645,10 +6843,20 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.plot2(dia_plot, obj=self, visible=visible, kind=kind) self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
else: else:
# multiple tools usage # multiple tools usage
for tooluid_key in self.cnc_tools: if self.cnc_tools:
tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia']))) for tooluid_key in self.cnc_tools:
gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed'] tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind) gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
# TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
if self.exc_cnc_tools:
for tooldia_key in self.exc_cnc_tools:
tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
# gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
gcode_parsed = self.gcode_parsed
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
self.shapes.redraw() self.shapes.redraw()
except (ObjectDeleted, AttributeError): except (ObjectDeleted, AttributeError):
self.shapes.clear(update=True) self.shapes.clear(update=True)

View File

@ -785,23 +785,41 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.item_selected.emit(obj.options['name']) self.item_selected.emit(obj.options['name'])
if obj.kind == 'gerber': if obj.kind == 'gerber':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='green', name=str(obj.options['name']))) color='green',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'excellon': elif obj.kind == 'excellon':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='brown', name=str(obj.options['name']))) color='brown',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'cncjob': elif obj.kind == 'cncjob':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='blue', name=str(obj.options['name']))) color='blue',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'geometry': elif obj.kind == 'geometry':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='red', name=str(obj.options['name']))) color='red',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'script': elif obj.kind == 'script':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='orange', name=str(obj.options['name']))) color='orange',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'document': elif obj.kind == 'document':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format( self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='darkCyan', name=str(obj.options['name']))) color='darkCyan',
name=str(obj.options['name']),
tx=_("selected"))
)
except IndexError: except IndexError:
self.item_selected.emit('none') self.item_selected.emit('none')
# FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)") # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")

View File

@ -9,19 +9,63 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
14.12.2019
- finished the strings update in the Google-translated Spanish
13.12.2019
- HPGL2 import: added support for circles, arcs and 3-point arcs. Everything works only for absolute coordinates.
- removed the .plt extension from Gcode extensions
- some strings updated; update on the Romanian translate
- more strings updated; finished the Romanian translation update
- some work in updating the Spanish Google-translation
- small updates (Google Translate) in Russian and Brazilian-PT languages
12.12.2019
- finished the Calibration Tool
- changed the Scale Entry in Object UI to FCEntry() GUI element in order to allow expressions to be entered. E.g: 1/25.4
- some small changes in the Scale button handler in FlatCAMObj() class
- added option to save objects as PDF files in File -> Save menu
- optimized the FlatCAMGerber.clear_plot_apertures() method
- some changes in the ObjectUI and for the Geometry UI
- finished a very rough and limited HPGL2 file import
11.12.2019
- started work in HPGL2 parser
- some more work in Calibration Tool
10.12.2019
- small changes in the Geometry UI
- now extracut option in the Geometry Object will recut as many points as many they are within the specified re-cut length
- if extracut_length is zero then the extracut will cut up until the first point in path no matter what the distance is
- in Gerber isolation, when selection mode is checked, now area selection works too
- in CNCJob UI, now the CNCJob objects made out of Excellon objects will display their CNC tools (drill bits)
- fixed a cumulative error when using the Tool Offset for Excellon objects
- added the display of the real depth of cut (cut z + offset_z) for CNC tools made out of an Excellon object
- for OpenGL graphic mode added a fit_view() execution on canvas initialization
- fixed Excellon scaling the UI values
- replaced the SpindleSpeed entry with a FCSpinner() GUI element; if speed is set to 0 it will amount to None
9.12.2019 9.12.2019
- updated the border for fit view on OpenGL graphic mode - updated the border for fit view on OpenGL graphic mode
- Calibration Tool - added preferences values - Calibration Tool - added preferences values
- Calibration Tool - more work on it - Calibration Tool - more work on it
- reverted this change: "selected object in Project used to ask twice for UI build" because it will not build the UI when a tab is closed for Document object and the object is selected - reverted this change: "selected object in Project used to ask twice for UI build" because it will not build the UI when a tab is closed for Document object and the object is selected
- fixed issue after Geometry object edit; the GCode made from and edited object did not reflect the changes in the object - fixed issue after Geometry object edit; the GCode made from an edited object did not reflect the changes in the object
- in Object UI, the Scale FCDoubleSpinner will no longer work for Return key press due of issues of unwanted scaling on focusOut event - in Object UI, the Scale FCDoubleSpinner will no longer work for Return key press due of issues of unwanted scaling on focusOut event
- in FlatCAMGeometry fixed the scale and offset methods to always process the self.solid_geometry - in FlatCAMGeometry fixed the scale and offset methods to always process the self.solid_geometry
- Calibration Tool - finished the calibrated object creation method - Calibration Tool - finished the calibrated object creation method
- updated the POT file - updated the POT file
- fixed an error in the German PO file - fixed an error in the German PO file
- updated the languages PO files - updated the languages PO files
- some fixes on the app.jump_to() method
- made sure that the ToolFilm will not start saving a file if there are no objects loaded
- some fixes on the app.jump_to() method for the Legacy(2D) graphic mode
8.12.2019 8.12.2019

105
camlib.py
View File

@ -459,7 +459,7 @@ class Geometry(object):
defaults = { defaults = {
"units": 'in', "units": 'in',
"geo_steps_per_circle": 128 "geo_steps_per_circle": 64
} }
def __init__(self, geo_steps_per_circle=None): def __init__(self, geo_steps_per_circle=None):
@ -1824,7 +1824,7 @@ class Geometry(object):
""" """
# Make sure we see a Shapely Geometry class and not a list # Make sure we see a Shapely Geometry class and not a list
if str(type(self)) == "<class 'FlatCAMObj.FlatCAMGeometry'>": if self.kind.lower() == 'geometry':
flat_geo = [] flat_geo = []
if self.multigeo: if self.multigeo:
for tool in self.tools: for tool in self.tools:
@ -2158,7 +2158,7 @@ class CNCjob(Geometry):
self.units = units self.units = units
self.z_cut = z_cut self.z_cut = z_cut
self.tool_offset = {} self.tool_offset = dict()
self.z_move = z_move self.z_move = z_move
@ -2359,7 +2359,9 @@ class CNCjob(Geometry):
self.exc_drills = deepcopy(exobj.drills) self.exc_drills = deepcopy(exobj.drills)
self.exc_tools = deepcopy(exobj.tools) self.exc_tools = deepcopy(exobj.tools)
self.z_cut = drillz self.z_cut = deepcopy(drillz)
old_zcut = deepcopy(drillz)
if self.machinist_setting == 0: if self.machinist_setting == 0:
if drillz > 0: if drillz > 0:
self.app.inform.emit('[WARNING] %s' % self.app.inform.emit('[WARNING] %s' %
@ -2441,10 +2443,16 @@ class CNCjob(Geometry):
LineString([start, stop]).buffer((it[1] / 2.0), resolution=self.geo_steps_per_circle) LineString([start, stop]).buffer((it[1] / 2.0), resolution=self.geo_steps_per_circle)
) )
try:
z_off = float(self.tool_offset[it[1]]) * (-1)
except KeyError:
z_off = 0
self.exc_cnc_tools[it[1]] = dict() self.exc_cnc_tools[it[1]] = dict()
self.exc_cnc_tools[it[1]]['tool'] = it[0] self.exc_cnc_tools[it[1]]['tool'] = it[0]
self.exc_cnc_tools[it[1]]['nr_drills'] = drill_no self.exc_cnc_tools[it[1]]['nr_drills'] = drill_no
self.exc_cnc_tools[it[1]]['nr_slots'] = slot_no self.exc_cnc_tools[it[1]]['nr_slots'] = slot_no
self.exc_cnc_tools[it[1]]['offset_z'] = z_off
self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo) self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo)
self.app.inform.emit(_("Creating a list of points to drill...")) self.app.inform.emit(_("Creating a list of points to drill..."))
@ -2635,7 +2643,7 @@ class CNCjob(Geometry):
z_offset = float(self.tool_offset[current_tooldia]) * (-1) z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError: except KeyError:
z_offset = 0 z_offset = 0
self.z_cut += z_offset self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"] self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90": if self.coordinates_type == "G90":
@ -2682,11 +2690,11 @@ class CNCjob(Geometry):
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented')) self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented'))
return 'fail' return 'fail'
self.z_cut = deepcopy(old_zcut)
else: else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...") "The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL] %s...' % self.app.inform.emit('[ERROR_NOTCL] %s...' % _('The loaded Excellon file has no drills'))
_('The loaded Excellon file has no drills'))
return 'fail' return 'fail'
log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance)) log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
@ -2778,7 +2786,7 @@ class CNCjob(Geometry):
z_offset = float(self.tool_offset[current_tooldia]) * (-1) z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError: except KeyError:
z_offset = 0 z_offset = 0
self.z_cut += z_offset self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"] self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90": if self.coordinates_type == "G90":
@ -2825,6 +2833,7 @@ class CNCjob(Geometry):
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented')) self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented'))
return 'fail' return 'fail'
self.z_cut = deepcopy(old_zcut)
else: else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...") "The loaded Excellon file has no drills ...")
@ -2879,7 +2888,7 @@ class CNCjob(Geometry):
z_offset = float(self.tool_offset[current_tooldia]) * (-1) z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError: except KeyError:
z_offset = 0 z_offset = 0
self.z_cut += z_offset self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"] self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90": if self.coordinates_type == "G90":
@ -2933,6 +2942,7 @@ class CNCjob(Geometry):
self.app.inform.emit('[ERROR_NOTCL] %s...' % self.app.inform.emit('[ERROR_NOTCL] %s...' %
_('The loaded Excellon file has no drills')) _('The loaded Excellon file has no drills'))
return 'fail' return 'fail'
self.z_cut = deepcopy(old_zcut)
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance)) log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
gcode += self.doformat(p.spindle_stop_code) # Spindle stop gcode += self.doformat(p.spindle_stop_code) # Spindle stop
@ -2962,7 +2972,7 @@ class CNCjob(Geometry):
feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30, feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0, spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
multidepth=False, depthpercut=None, multidepth=False, depthpercut=None,
toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", extracut=False, toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
startz=None, endz=2.0, pp_geometry_name=None, tool_no=1): startz=None, endz=2.0, pp_geometry_name=None, tool_no=1):
""" """
Algorithm to generate from multitool Geometry. Algorithm to generate from multitool Geometry.
@ -2992,6 +3002,7 @@ class CNCjob(Geometry):
:param toolchangexy: :param toolchangexy:
:param extracut: Adds (or not) an extra cut at the end of each path overlapping the :param extracut: Adds (or not) an extra cut at the end of each path overlapping the
first point in path to ensure complete copper removal first point in path to ensure complete copper removal
:param extracut_length: Extra cut legth at the end of the path
:param startz: :param startz:
:param endz: :param endz:
:param pp_geometry_name: :param pp_geometry_name:
@ -3025,7 +3036,7 @@ class CNCjob(Geometry):
self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None
self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
self.spindlespeed = int(spindlespeed) if spindlespeed else None self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
self.spindledir = spindledir self.spindledir = spindledir
self.dwell = dwell self.dwell = dwell
self.dwelltime = float(dwelltime) if dwelltime else None self.dwelltime = float(dwelltime) if dwelltime else None
@ -3213,7 +3224,8 @@ class CNCjob(Geometry):
# calculate the cut distance # calculate the cut distance
total_cut = total_cut + geo.length total_cut = total_cut + geo.length
self.gcode += self.create_gcode_single_pass(geo, extracut, tolerance, old_point=current_pt) self.gcode += self.create_gcode_single_pass(geo, extracut, extracut_length, tolerance,
old_point=current_pt)
# --------- Multi-pass --------- # --------- Multi-pass ---------
else: else:
@ -3227,7 +3239,7 @@ class CNCjob(Geometry):
total_cut += (geo.length * nr_cuts) total_cut += (geo.length * nr_cuts)
self.gcode += self.create_gcode_multi_pass(geo, extracut, tolerance, self.gcode += self.create_gcode_multi_pass(geo, extracut, extracut_length, tolerance,
postproc=p, old_point=current_pt) postproc=p, old_point=current_pt)
# calculate the total distance # calculate the total distance
@ -3270,7 +3282,7 @@ class CNCjob(Geometry):
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0, spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
multidepth=False, depthpercut=None, multidepth=False, depthpercut=None,
toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0",
extracut=False, startz=None, endz=2.0, extracut=False, extracut_length=0.1, startz=None, endz=2.0,
pp_geometry_name=None, tool_no=1): pp_geometry_name=None, tool_no=1):
""" """
Second algorithm to generate from Geometry. Second algorithm to generate from Geometry.
@ -3288,6 +3300,7 @@ class CNCjob(Geometry):
:param depthpercut: Maximum depth in each pass. :param depthpercut: Maximum depth in each pass.
:param extracut: Adds (or not) an extra cut at the end of each path :param extracut: Adds (or not) an extra cut at the end of each path
overlapping the first point in path to ensure complete copper removal overlapping the first point in path to ensure complete copper removal
:param extracut_length: The extra cut length
:return: None :return: None
""" """
@ -3375,7 +3388,7 @@ class CNCjob(Geometry):
self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None
self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
self.spindlespeed = int(spindlespeed) if spindlespeed else None self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
self.spindledir = spindledir self.spindledir = spindledir
self.dwell = dwell self.dwell = dwell
self.dwelltime = float(dwelltime) if dwelltime else None self.dwelltime = float(dwelltime) if dwelltime else None
@ -3559,7 +3572,8 @@ class CNCjob(Geometry):
if not multidepth: if not multidepth:
# calculate the cut distance # calculate the cut distance
total_cut += geo.length total_cut += geo.length
self.gcode += self.create_gcode_single_pass(geo, extracut, tolerance, old_point=current_pt) self.gcode += self.create_gcode_single_pass(geo, extracut, extracut_length, tolerance,
old_point=current_pt)
# --------- Multi-pass --------- # --------- Multi-pass ---------
else: else:
@ -3573,7 +3587,7 @@ class CNCjob(Geometry):
total_cut += (geo.length * nr_cuts) total_cut += (geo.length * nr_cuts)
self.gcode += self.create_gcode_multi_pass(geo, extracut, tolerance, self.gcode += self.create_gcode_multi_pass(geo, extracut, extracut_length, tolerance,
postproc=p, old_point=current_pt) postproc=p, old_point=current_pt)
# calculate the travel distance # calculate the travel distance
@ -3798,7 +3812,7 @@ class CNCjob(Geometry):
gcode += self.doformat(p.lift_code) gcode += self.doformat(p.lift_code)
return gcode return gcode
def create_gcode_single_pass(self, geometry, extracut, tolerance, old_point=(0, 0)): def create_gcode_single_pass(self, geometry, extracut, extracut_length, tolerance, old_point=(0, 0)):
# G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time. # G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time.
gcode_single_pass = '' gcode_single_pass = ''
@ -3807,7 +3821,8 @@ class CNCjob(Geometry):
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point) gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point)
else: else:
if geometry.is_ring: if geometry.is_ring:
gcode_single_pass = self.linear2gcode_extra(geometry, tolerance=tolerance, old_point=old_point) gcode_single_pass = self.linear2gcode_extra(geometry, extracut_length, tolerance=tolerance,
old_point=old_point)
else: else:
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point) gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point)
elif type(geometry) == Point: elif type(geometry) == Point:
@ -3818,7 +3833,7 @@ class CNCjob(Geometry):
return gcode_single_pass return gcode_single_pass
def create_gcode_multi_pass(self, geometry, extracut, tolerance, postproc, old_point=(0, 0)): def create_gcode_multi_pass(self, geometry, extracut, extracut_length, tolerance, postproc, old_point=(0, 0)):
gcode_multi_pass = '' gcode_multi_pass = ''
@ -3851,8 +3866,8 @@ class CNCjob(Geometry):
old_point=old_point) old_point=old_point)
else: else:
if geometry.is_ring: if geometry.is_ring:
gcode_multi_pass += self.linear2gcode_extra(geometry, tolerance=tolerance, z_cut=depth, gcode_multi_pass += self.linear2gcode_extra(geometry, extracut_length, tolerance=tolerance,
up=False, old_point=old_point) z_cut=depth, up=False, old_point=old_point)
else: else:
gcode_multi_pass += self.linear2gcode(geometry, tolerance=tolerance, z_cut=depth, up=False, gcode_multi_pass += self.linear2gcode(geometry, tolerance=tolerance, z_cut=depth, up=False,
old_point=old_point) old_point=old_point)
@ -4513,13 +4528,14 @@ class CNCjob(Geometry):
gcode += self.doformat(p.lift_code, x=prev_x, y=prev_y, z_move=z_move) # Stop cutting gcode += self.doformat(p.lift_code, x=prev_x, y=prev_y, z_move=z_move) # Stop cutting
return gcode return gcode
def linear2gcode_extra(self, linear, tolerance=0, down=True, up=True, def linear2gcode_extra(self, linear, extracut_length, tolerance=0, down=True, up=True,
z_cut=None, z_move=None, zdownrate=None, z_cut=None, z_move=None, zdownrate=None,
feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)): feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)):
""" """
Generates G-code to cut along the linear feature. Generates G-code to cut along the linear feature.
:param linear: The path to cut along. :param linear: The path to cut along.
:param extracut_length: how much to cut extra over the first point at the end of the path
:type: Shapely.LinearRing or Shapely.Linear String :type: Shapely.LinearRing or Shapely.Linear String
:param tolerance: All points in the simplified object will be within the :param tolerance: All points in the simplified object will be within the
tolerance distance of the original geometry. tolerance distance of the original geometry.
@ -4602,8 +4618,7 @@ class CNCjob(Geometry):
# For Incremental coordinates type G91 # For Incremental coordinates type G91
# next_x = pt[0] - prev_x # next_x = pt[0] - prev_x
# next_y = pt[1] - prev_y # next_y = pt[1] - prev_y
self.app.inform.emit('[ERROR_NOTCL] %s' % self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
_('G91 coordinates not implemented ...'))
next_x = pt[0] next_x = pt[0]
next_y = pt[1] next_y = pt[1]
@ -4614,19 +4629,41 @@ class CNCjob(Geometry):
# this line is added to create an extra cut over the first point in patch # this line is added to create an extra cut over the first point in patch
# to make sure that we remove the copper leftovers # to make sure that we remove the copper leftovers
# Linear motion to the 1st point in the cut path # Linear motion to the 1st point in the cut path
if self.coordinates_type == "G90": # if self.coordinates_type == "G90":
# For Absolute coordinates type G90 # # For Absolute coordinates type G90
last_x = path[1][0] # last_x = path[1][0]
last_y = path[1][1] # last_y = path[1][1]
# else:
# # For Incremental coordinates type G91
# last_x = path[1][0] - first_x
# last_y = path[1][1] - first_y
# gcode += self.doformat(p.linear_code, x=last_x, y=last_y)
# the first point for extracut is always mandatory if the extracut is enabled. But if the length of distance
# between point 0 and point 1 is more than the distance we set for the extra cut then make an interpolation
# along the path and find the point at the distance extracut_length
if extracut_length == 0.0:
gcode += self.doformat(p.linear_code, x=path[1][0], y=path[1][1])
last_pt = path[1]
else: else:
# For Incremental coordinates type G91 if abs(distance(path[1], path[0])) > extracut_length:
last_x = path[1][0] - first_x i_point = LineString([path[0], path[1]]).interpolate(extracut_length)
last_y = path[1][1] - first_y gcode += self.doformat(p.linear_code, x=i_point.x, y=i_point.y)
gcode += self.doformat(p.linear_code, x=last_x, y=last_y) last_pt = (i_point.x, i_point.y)
else:
last_pt = path[0]
for pt in path[1:]:
extracut_distance = abs(distance(pt, last_pt))
if extracut_distance <= extracut_length:
gcode += self.doformat(p.linear_code, x=pt[0], y=pt[1])
last_pt = pt
else:
break
# Up to travelling height. # Up to travelling height.
if up: if up:
gcode += self.doformat(p.lift_code, x=last_x, y=last_y, z_move=z_move) # Stop cutting gcode += self.doformat(p.lift_code, x=last_pt[0], y=last_pt[1], z_move=z_move) # Stop cutting
return gcode return gcode

View File

@ -455,8 +455,6 @@ class PaintOptionsTool(FlatCAMTool):
ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate')) ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
ovlabel.setToolTip( ovlabel.setToolTip(
_("How much (fraction) of the tool width to overlap each tool pass.\n" _("How much (fraction) of the tool width to overlap each tool pass.\n"
"Example:\n"
"A value here of 0.25 means 25%% from the tool diameter found above.\n\n"
"Adjust the value starting with lower values\n" "Adjust the value starting with lower values\n"
"and increasing it if areas that should be painted are still \n" "and increasing it if areas that should be painted are still \n"
"not painted.\n" "not painted.\n"

View File

@ -168,6 +168,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('&DXF as Gerber Object ...'), self) _('&DXF as Gerber Object ...'), self)
self.menufileimport.addAction(self.menufileimportdxf_as_gerber) self.menufileimport.addAction(self.menufileimportdxf_as_gerber)
self.menufileimport.addSeparator() self.menufileimport.addSeparator()
self.menufileimport_hpgl2_as_geo = QtWidgets.QAction(QtGui.QIcon('share/dxf16.png'),
_('HPGL2 as Geometry Object ...'), self)
self.menufileimport.addAction(self.menufileimport_hpgl2_as_geo)
self.menufileimport.addSeparator()
# Export ... # Export ...
self.menufileexport = self.menufile.addMenu(QtGui.QIcon('share/export.png'), _('Export')) self.menufileexport = self.menufile.addMenu(QtGui.QIcon('share/export.png'), _('Export'))
@ -250,6 +254,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('Save Project C&opy ...'), self) _('Save Project C&opy ...'), self)
self.menufile_save.addAction(self.menufilesaveprojectcopy) self.menufile_save.addAction(self.menufilesaveprojectcopy)
self.menufile_save.addSeparator()
# Save Object PDF
self.menufilesave_object_pdf = QtWidgets.QAction(QtGui.QIcon('share/pdf32.png'),
_('Save Object as PDF ...'), self)
self.menufile_save.addAction(self.menufilesave_object_pdf)
# Separator # Separator
self.menufile.addSeparator() self.menufile.addSeparator()
@ -754,6 +765,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_("Copper Thieving Tool")) _("Copper Thieving Tool"))
self.fiducials_btn = self.toolbartools.addAction(QtGui.QIcon('share/fiducials_32.png'), _("Fiducials Tool")) self.fiducials_btn = self.toolbartools.addAction(QtGui.QIcon('share/fiducials_32.png'), _("Fiducials Tool"))
self.cal_btn = self.toolbartools.addAction(QtGui.QIcon('share/calibrate_32.png'), _("Calibration Tool"))
# ######################################################################## # ########################################################################
# ########################## Excellon Editor Toolbar# #################### # ########################## Excellon Editor Toolbar# ####################
@ -2198,6 +2210,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_("Copper Thieving Tool")) _("Copper Thieving Tool"))
self.fiducials_btn = self.toolbartools.addAction(QtGui.QIcon('share/fiducials_32.png'), _("Fiducials Tool")) self.fiducials_btn = self.toolbartools.addAction(QtGui.QIcon('share/fiducials_32.png'), _("Fiducials Tool"))
self.cal_btn = self.toolbartools.addAction(QtGui.QIcon('share/calibrate_32.png'), _("Calibration Tool"))
# ## Excellon Editor Toolbar # ## # ## Excellon Editor Toolbar # ##
self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), _("Select")) self.select_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), _("Select"))
@ -2530,7 +2543,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.dblsidedtool.run(toggle=True) self.app.dblsidedtool.run(toggle=True)
return return
# Calibrate Tool # Calibration Tool
if key == QtCore.Qt.Key_E: if key == QtCore.Qt.Key_E:
self.app.cal_exc_tool.run(toggle=True) self.app.cal_exc_tool.run(toggle=True)
return return

View File

@ -100,13 +100,10 @@ class ObjectUI(QtWidgets.QWidget):
faclabel = QtWidgets.QLabel('%s:' % _('Factor')) faclabel = QtWidgets.QLabel('%s:' % _('Factor'))
faclabel.setToolTip( faclabel.setToolTip(
_("Factor by which to multiply\n" _("Factor by which to multiply\n"
"geometric features of this object.") "geometric features of this object.\n"
"Expressions are allowed. E.g: 1/25.4")
) )
self.scale_entry = FCDoubleSpinner() self.scale_entry = FCEntry()
self.scale_entry.set_precision(self.decimals)
self.scale_entry.setRange(0.0, 9999.9999)
self.scale_entry.setSingleStep(0.1)
self.scale_entry.set_value(1.0) self.scale_entry.set_value(1.0)
# GO Button # GO Button
@ -131,7 +128,8 @@ class ObjectUI(QtWidgets.QWidget):
self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector')) self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector'))
self.offset_vectorlabel.setToolTip( self.offset_vectorlabel.setToolTip(
_("Amount by which to move the object\n" _("Amount by which to move the object\n"
"in the x and y axes in (x, y) format.") "in the x and y axes in (x, y) format.\n"
"Expressions are allowed. E.g: (1/3.2, 0.5*3)")
) )
self.offsetvector_entry = EvalEntry2() self.offsetvector_entry = EvalEntry2()
self.offsetvector_entry.setText("(0.0, 0.0)") self.offsetvector_entry.setText("(0.0, 0.0)")
@ -158,6 +156,8 @@ class GerberObjectUI(ObjectUI):
ObjectUI.__init__(self, title=_('Gerber Object'), parent=parent, decimals=decimals) ObjectUI.__init__(self, title=_('Gerber Object'), parent=parent, decimals=decimals)
self.decimals = decimals self.decimals = decimals
self.custom_box.addWidget(QtWidgets.QLabel(''))
# Plot options # Plot options
grid0 = QtWidgets.QGridLayout() grid0 = QtWidgets.QGridLayout()
grid0.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) grid0.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
@ -165,10 +165,20 @@ class GerberObjectUI(ObjectUI):
grid0.setColumnStretch(0, 0) grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1) grid0.setColumnStretch(1, 1)
# Plot CB
self.plot_cb = FCCheckBox()
self.plot_cb.setToolTip(
_("Plot (show) this object.")
)
plot_label = QtWidgets.QLabel('<b>%s:</b>' % _("Plot"))
grid0.addWidget(plot_label, 0, 0)
grid0.addWidget(self.plot_cb, 0, 1)
self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options")) self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
self.plot_options_label.setMinimumWidth(90) self.plot_options_label.setMinimumWidth(90)
grid0.addWidget(self.plot_options_label, 0, 0) grid0.addWidget(self.plot_options_label, 1, 0)
# Solid CB # Solid CB
self.solid_cb = FCCheckBox(label=_('Solid')) self.solid_cb = FCCheckBox(label=_('Solid'))
@ -176,23 +186,15 @@ class GerberObjectUI(ObjectUI):
_("Solid color polygons.") _("Solid color polygons.")
) )
self.solid_cb.setMinimumWidth(50) self.solid_cb.setMinimumWidth(50)
grid0.addWidget(self.solid_cb, 0, 1) grid0.addWidget(self.solid_cb, 1, 1)
# Multicolored CB # Multicolored CB
self.multicolored_cb = FCCheckBox(label=_('M-Color')) self.multicolored_cb = FCCheckBox(label=_('Multi-Color'))
self.multicolored_cb.setToolTip( self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.") _("Draw polygons in different colors.")
) )
self.multicolored_cb.setMinimumWidth(55) self.multicolored_cb.setMinimumWidth(55)
grid0.addWidget(self.multicolored_cb, 0, 2) grid0.addWidget(self.multicolored_cb, 1, 2)
# Plot CB
self.plot_cb = FCCheckBox(_('Plot'))
self.plot_cb.setToolTip(
_("Plot (show) this object.")
)
self.plot_cb.setMinimumWidth(59)
grid0.addWidget(self.plot_cb, 0, 3)
# ## Object name # ## Object name
self.name_hlay = QtWidgets.QHBoxLayout() self.name_hlay = QtWidgets.QHBoxLayout()
@ -264,7 +266,10 @@ class GerberObjectUI(ObjectUI):
# start with apertures table hidden # start with apertures table hidden
self.apertures_table.setVisible(False) self.apertures_table.setVisible(False)
self.custom_box.addWidget(QtWidgets.QLabel('')) separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.custom_box.addWidget(separator_line)
# Isolation Routing # Isolation Routing
self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing")) self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing"))
@ -552,6 +557,12 @@ class GerberObjectUI(ObjectUI):
_("Create the Geometry Object\n" _("Create the Geometry Object\n"
"for non-copper routing.") "for non-copper routing.")
) )
self.generate_ncc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.clearcopper_label, 1, 0) grid2.addWidget(self.clearcopper_label, 1, 0)
grid2.addWidget(self.generate_ncc_button, 1, 1) grid2.addWidget(self.generate_ncc_button, 1, 1)
@ -568,6 +579,12 @@ class GerberObjectUI(ObjectUI):
_("Generate the geometry for\n" _("Generate the geometry for\n"
"the board cutout.") "the board cutout.")
) )
self.generate_cutout_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.board_cutout_label, 2, 0) grid2.addWidget(self.board_cutout_label, 2, 0)
grid2.addWidget(self.generate_cutout_button, 2, 1) grid2.addWidget(self.generate_cutout_button, 2, 1)
@ -913,7 +930,9 @@ class ExcellonObjectUI(ObjectUI):
"in RPM (optional)") "in RPM (optional)")
) )
grid1.addWidget(spdlabel, 8, 0) grid1.addWidget(spdlabel, 8, 0)
self.spindlespeed_entry = IntEntry(allow_empty=True) self.spindlespeed_entry = FCSpinner()
self.spindlespeed_entry.set_range(0, 1000000)
self.spindlespeed_entry.setSingleStep(100)
grid1.addWidget(self.spindlespeed_entry, 8, 1) grid1.addWidget(self.spindlespeed_entry, 8, 1)
# Dwell # Dwell
@ -1011,6 +1030,12 @@ class ExcellonObjectUI(ObjectUI):
self.generate_cnc_button.setToolTip( self.generate_cnc_button.setToolTip(
_("Generate the CNC Job.") _("Generate the CNC Job.")
) )
self.generate_cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.generate_cnc_button, 2, 0, 1, 3) grid2.addWidget(self.generate_cnc_button, 2, 0, 1, 3)
# ### Milling Holes Drills #### # ### Milling Holes Drills ####
@ -1036,6 +1061,12 @@ class ExcellonObjectUI(ObjectUI):
_("Create the Geometry Object\n" _("Create the Geometry Object\n"
"for milling DRILLS toolpaths.") "for milling DRILLS toolpaths.")
) )
self.generate_milling_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.tdlabel, 4, 0) grid2.addWidget(self.tdlabel, 4, 0)
grid2.addWidget(self.tooldia_entry, 4, 1) grid2.addWidget(self.tooldia_entry, 4, 1)
@ -1057,6 +1088,12 @@ class ExcellonObjectUI(ObjectUI):
_("Create the Geometry Object\n" _("Create the Geometry Object\n"
"for milling SLOTS toolpaths.") "for milling SLOTS toolpaths.")
) )
self.generate_milling_slots_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.stdlabel, 5, 0) grid2.addWidget(self.stdlabel, 5, 0)
grid2.addWidget(self.slot_tooldia_entry, 5, 1) grid2.addWidget(self.slot_tooldia_entry, 5, 1)
@ -1501,14 +1538,29 @@ class GeometryObjectUI(ObjectUI):
self.cncfeedrate_rapid_entry.hide() self.cncfeedrate_rapid_entry.hide()
# Cut over 1st point in path # Cut over 1st point in path
self.extracut_cb = FCCheckBox('%s' % _('Re-cut 1st pt.')) self.extracut_cb = FCCheckBox('%s' % _('Re-cut'))
self.extracut_cb.setToolTip( self.extracut_cb.setToolTip(
_("In order to remove possible\n" _("In order to remove possible\n"
"copper leftovers where first cut\n" "copper leftovers where first cut\n"
"meet with last cut, we generate an\n" "meet with last cut, we generate an\n"
"extended cut over the first cut section.") "extended cut over the first cut section.")
) )
self.e_cut_entry = FCDoubleSpinner()
self.e_cut_entry.set_range(0, 99999)
self.e_cut_entry.set_precision(self.decimals)
self.e_cut_entry.setSingleStep(0.1)
self.e_cut_entry.setWrapping(True)
self.e_cut_entry.setToolTip(
_("In order to remove possible\n"
"copper leftovers where first cut\n"
"meet with last cut, we generate an\n"
"extended cut over the first cut section.")
)
self.grid3.addWidget(self.extracut_cb, 13, 0) self.grid3.addWidget(self.extracut_cb, 13, 0)
self.grid3.addWidget(self.e_cut_entry, 13, 1)
self.ois_e_cut = OptionalInputSection(self.extracut_cb, [self.e_cut_entry])
# Spindlespeed # Spindlespeed
spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed')) spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
@ -1519,7 +1571,9 @@ class GeometryObjectUI(ObjectUI):
"this value is the power of laser." "this value is the power of laser."
) )
) )
self.cncspindlespeed_entry = IntEntry(allow_empty=True) self.cncspindlespeed_entry = FCSpinner()
self.cncspindlespeed_entry.set_range(0, 1000000)
self.cncspindlespeed_entry.setSingleStep(100)
self.grid3.addWidget(spdlabel, 14, 0) self.grid3.addWidget(spdlabel, 14, 0)
self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1) self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
@ -1617,13 +1671,28 @@ class GeometryObjectUI(ObjectUI):
self.generate_cnc_button.setToolTip( self.generate_cnc_button.setToolTip(
_("Generate the CNC Job object.") _("Generate the CNC Job object.")
) )
self.geo_param_box.addWidget(self.generate_cnc_button) self.generate_cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.grid3.addWidget(self.generate_cnc_button, 23, 0, 1, 2)
self.grid3.addWidget(QtWidgets.QLabel(''), 24, 0, 1, 2)
# ############## # ##############
# Paint area ## # Paint area ##
# ############## # ##############
self.paint_label = QtWidgets.QLabel('<b>%s</b>' % _('Paint Area')) self.tools_label = QtWidgets.QLabel('<b>%s</b>' % _('TOOLS'))
self.paint_label.setToolTip( self.tools_label.setToolTip(
_("Launch Paint Tool in Tools Tab.")
)
self.grid3.addWidget(self.tools_label, 25, 0, 1, 2)
# Paint Button
self.paint_tool_button = QtWidgets.QPushButton(_('Paint Tool'))
self.paint_tool_button.setToolTip(
_( _(
"Creates tool paths to cover the\n" "Creates tool paths to cover the\n"
"whole area of a polygon (remove\n" "whole area of a polygon (remove\n"
@ -1631,14 +1700,27 @@ class GeometryObjectUI(ObjectUI):
"to click on the desired polygon." "to click on the desired polygon."
) )
) )
self.geo_tools_box.addWidget(self.paint_label) self.paint_tool_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.grid3.addWidget(self.paint_tool_button, 26, 0, 1, 2)
# GO Button # NCC Tool
self.paint_tool_button = QtWidgets.QPushButton(_('Paint Tool')) self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
self.paint_tool_button.setToolTip( self.generate_ncc_button.setToolTip(
_("Launch Paint Tool in Tools Tab.") _("Create the Geometry Object\n"
"for non-copper routing.")
) )
self.geo_tools_box.addWidget(self.paint_tool_button) self.generate_ncc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.grid3.addWidget(self.generate_ncc_button, 27, 0, 1, 2)
class CNCObjectUI(ObjectUI): class CNCObjectUI(ObjectUI):
@ -1781,12 +1863,20 @@ class CNCObjectUI(ObjectUI):
self.cnc_tools_table.setColumnCount(7) self.cnc_tools_table.setColumnCount(7)
self.cnc_tools_table.setColumnWidth(0, 20) self.cnc_tools_table.setColumnWidth(0, 20)
self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '', self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '', _('P')])
_('P')])
self.cnc_tools_table.setColumnHidden(5, True) self.cnc_tools_table.setColumnHidden(5, True)
# stylesheet = "::section{Background-color:rgb(239,239,245)}" # stylesheet = "::section{Background-color:rgb(239,239,245)}"
# self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet) # self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet)
self.exc_cnc_tools_table = FCTable()
self.custom_box.addWidget(self.exc_cnc_tools_table)
self.exc_cnc_tools_table.setColumnCount(7)
self.exc_cnc_tools_table.setColumnWidth(0, 20)
self.exc_cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Drills'), _('Slots'), '', _("Cut Z"),
_('P')])
self.exc_cnc_tools_table.setColumnHidden(4, True)
self.tooldia_entry = FCDoubleSpinner() self.tooldia_entry = FCDoubleSpinner()
self.tooldia_entry.set_range(0, 9999.9999) self.tooldia_entry.set_range(0, 9999.9999)
self.tooldia_entry.set_precision(self.decimals) self.tooldia_entry.set_precision(self.decimals)
@ -1820,7 +1910,7 @@ class CNCObjectUI(ObjectUI):
self.prepend_text = FCTextArea() self.prepend_text = FCTextArea()
self.prepend_text.setPlaceholderText( self.prepend_text.setPlaceholderText(
_("Type here any G-Code commands you would " _("Type here any G-Code commands you would\n"
"like to add at the beginning of the G-Code file.") "like to add at the beginning of the G-Code file.")
) )
self.custom_box.addWidget(self.prepend_text) self.custom_box.addWidget(self.prepend_text)
@ -1836,8 +1926,8 @@ class CNCObjectUI(ObjectUI):
self.append_text = FCTextArea() self.append_text = FCTextArea()
self.append_text.setPlaceholderText( self.append_text.setPlaceholderText(
_("Type here any G-Code commands you would " _("Type here any G-Code commands you would\n"
"like to append to the generated file. " "like to append to the generated file.\n"
"I.e.: M2 (End of program)") "I.e.: M2 (End of program)")
) )
self.custom_box.addWidget(self.append_text) self.custom_box.addWidget(self.append_text)
@ -1868,12 +1958,12 @@ class CNCObjectUI(ObjectUI):
self.toolchange_text = FCTextArea() self.toolchange_text = FCTextArea()
self.toolchange_text.setPlaceholderText( self.toolchange_text.setPlaceholderText(
_( _(
"Type here any G-Code commands you would " "Type here any G-Code commands you would\n"
"like to be executed when Toolchange event is encountered. " "like to be executed when Toolchange event is encountered.\n"
"This will constitute a Custom Toolchange GCode, " "This will constitute a Custom Toolchange GCode,\n"
"or a Toolchange Macro. " "or a Toolchange Macro.\n"
"The FlatCAM variables are surrounded by '%' symbol. \n" "The FlatCAM variables are surrounded by '%' symbol.\n"
"WARNING: it can be used only with a preprocessor file " "WARNING: it can be used only with a preprocessor file\n"
"that has 'toolchange_custom' in it's name." "that has 'toolchange_custom' in it's name."
) )
) )

View File

@ -156,7 +156,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
self.big_cursor = None self.big_cursor = None
# Keep VisPy canvas happy by letting it be "frozen" again. # Keep VisPy canvas happy by letting it be "frozen" again.
self.freeze() self.freeze()
self.fit_view()
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll) self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
def draw_workspace(self, workspace_size): def draw_workspace(self, workspace_size):
@ -303,7 +303,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
p2 = np.array(curr_pos)[:2] p2 = np.array(curr_pos)[:2]
self.view.camera.pan(p2 - p1) self.view.camera.pan(p2 - p1)
if self.fcapp.grid_status() == True: if self.fcapp.grid_status():
pos_canvas = self.translate_coords(curr_pos) pos_canvas = self.translate_coords(curr_pos)
pos = self.fcapp.geo_editor.snap(pos_canvas[0], pos_canvas[1]) pos = self.fcapp.geo_editor.snap(pos_canvas[0], pos_canvas[1])

View File

@ -2430,7 +2430,9 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
"in RPM (optional)") "in RPM (optional)")
) )
grid2.addWidget(spdlabel, 6, 0) grid2.addWidget(spdlabel, 6, 0)
self.spindlespeed_entry = IntEntry(allow_empty=True) self.spindlespeed_entry = FCSpinner()
self.spindlespeed_entry.set_range(0, 1000000)
self.spindlespeed_entry.setSingleStep(100)
grid2.addWidget(self.spindlespeed_entry, 6, 1) grid2.addWidget(self.spindlespeed_entry, 6, 1)
# Dwell # Dwell
@ -3341,7 +3343,10 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
) )
) )
grid1.addWidget(spdlabel, 9, 0) grid1.addWidget(spdlabel, 9, 0)
self.cncspindlespeed_entry = IntEntry(allow_empty=True) self.cncspindlespeed_entry = FCSpinner()
self.cncspindlespeed_entry.set_range(0, 1000000)
self.cncspindlespeed_entry.setSingleStep(100)
grid1.addWidget(self.cncspindlespeed_entry, 9, 1) grid1.addWidget(self.cncspindlespeed_entry, 9, 1)
# Dwell # Dwell
@ -3440,14 +3445,27 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
grid1.addWidget(self.cncfeedrate_rapid_entry, 4, 1) grid1.addWidget(self.cncfeedrate_rapid_entry, 4, 1)
# End move extra cut # End move extra cut
self.extracut_cb = FCCheckBox(label='%s' % _('Re-cut 1st pt.')) self.extracut_cb = FCCheckBox('%s' % _('Re-cut'))
self.extracut_cb.setToolTip( self.extracut_cb.setToolTip(
_("In order to remove possible\n" _("In order to remove possible\n"
"copper leftovers where first cut\n" "copper leftovers where first cut\n"
"meet with last cut, we generate an\n" "meet with last cut, we generate an\n"
"extended cut over the first cut section.") "extended cut over the first cut section.")
) )
self.e_cut_entry = FCDoubleSpinner()
self.e_cut_entry.set_range(0, 99999)
self.e_cut_entry.set_precision(self.decimals)
self.e_cut_entry.setSingleStep(0.1)
self.e_cut_entry.setWrapping(True)
self.e_cut_entry.setToolTip(
_("In order to remove possible\n"
"copper leftovers where first cut\n"
"meet with last cut, we generate an\n"
"extended cut over the first cut section.")
)
grid1.addWidget(self.extracut_cb, 5, 0) grid1.addWidget(self.extracut_cb, 5, 0)
grid1.addWidget(self.e_cut_entry, 5, 1)
# Probe depth # Probe depth
self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth")) self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
@ -3762,7 +3780,7 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
self.prepend_text = FCTextArea() self.prepend_text = FCTextArea()
self.prepend_text.setPlaceholderText( self.prepend_text.setPlaceholderText(
_("Type here any G-Code commands you would " _("Type here any G-Code commands you would\n"
"like to add at the beginning of the G-Code file.") "like to add at the beginning of the G-Code file.")
) )
self.layout.addWidget(self.prepend_text) self.layout.addWidget(self.prepend_text)
@ -3779,8 +3797,8 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
self.append_text = FCTextArea() self.append_text = FCTextArea()
self.append_text.setPlaceholderText( self.append_text.setPlaceholderText(
_("Type here any G-Code commands you would " _("Type here any G-Code commands you would\n"
"like to append to the generated file. " "like to append to the generated file.\n"
"I.e.: M2 (End of program)") "I.e.: M2 (End of program)")
) )
self.layout.addWidget(self.append_text) self.layout.addWidget(self.append_text)
@ -3832,12 +3850,12 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
self.toolchange_text = FCTextArea() self.toolchange_text = FCTextArea()
self.toolchange_text.setPlaceholderText( self.toolchange_text.setPlaceholderText(
_( _(
"Type here any G-Code commands you would " "Type here any G-Code commands you would\n"
"like to be executed when Toolchange event is encountered. " "like to be executed when Toolchange event is encountered.\n"
"This will constitute a Custom Toolchange GCode, " "This will constitute a Custom Toolchange GCode,\n"
"or a Toolchange Macro. " "or a Toolchange Macro.\n"
"The FlatCAM variables are surrounded by '%' symbol. \n" "The FlatCAM variables are surrounded by '%' symbol.\n"
"WARNING: it can be used only with a preprocessor file " "WARNING: it can be used only with a preprocessor file\n"
"that has 'toolchange_custom' in it's name." "that has 'toolchange_custom' in it's name."
) )
) )
@ -4834,7 +4852,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation")) self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
self.orientation_label.setToolTip(_("Can be:\n" self.orientation_label.setToolTip(_("Can be:\n"
"- Portrait\n" "- Portrait\n"
"- Lanscape")) "- Landscape"))
self.orientation_radio = RadioSet([{'label': _('Portrait'), 'value': 'p'}, self.orientation_radio = RadioSet([{'label': _('Portrait'), 'value': 'p'},
{'label': _('Landscape'), 'value': 'l'}, {'label': _('Landscape'), 'value': 'l'},
@ -6105,7 +6123,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], orientation='vertical', stretch=False) ], orientation='vertical', stretch=False)
self.reference_label = QtWidgets.QLabel(_("Reference:")) self.reference_label = QtWidgets.QLabel(_("Reference:"))
self.reference_label.setToolTip( self.reference_label.setToolTip(
_("- 'Itself' - the copper Thieving extent is based on the object that is copper cleared.\n " _("- 'Itself' - the copper Thieving extent is based on the object extent.\n"
"- 'Area Selection' - left mouse click to start selection of the area to be filled.\n" "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
"- 'Reference Object' - will do copper thieving within the area specified by another object.") "- 'Reference Object' - will do copper thieving within the area specified by another object.")
) )
@ -6119,7 +6137,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], stretch=False) ], stretch=False)
self.bbox_type_label = QtWidgets.QLabel(_("Box Type:")) self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
self.bbox_type_label.setToolTip( self.bbox_type_label.setToolTip(
_("- 'Rectangular' - the bounding box will be of rectangular shape.\n " _("- 'Rectangular' - the bounding box will be of rectangular shape.\n"
"- 'Minimal' - the bounding box will be the convex hull shape.") "- 'Minimal' - the bounding box will be the convex hull shape.")
) )
grid_lay.addWidget(self.bbox_type_label, 5, 0) grid_lay.addWidget(self.bbox_type_label, 5, 0)
@ -6139,7 +6157,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], orientation='vertical', stretch=False) ], orientation='vertical', stretch=False)
self.fill_type_label = QtWidgets.QLabel(_("Fill Type:")) self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
self.fill_type_label.setToolTip( self.fill_type_label.setToolTip(
_("- 'Solid' - copper thieving will be a solid polygon.\n " _("- 'Solid' - copper thieving will be a solid polygon.\n"
"- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n" "- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n"
"- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n" "- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n"
"- 'Lines Grid' - the empty area will be filled with a pattern of lines.") "- 'Lines Grid' - the empty area will be filled with a pattern of lines.")
@ -6346,7 +6364,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
], stretch=False) ], stretch=False)
self.mode_label = QtWidgets.QLabel(_("Mode:")) self.mode_label = QtWidgets.QLabel(_("Mode:"))
self.mode_label.setToolTip( self.mode_label.setToolTip(
_("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n " _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n"
"- 'Manual' - manual placement of fiducials.") "- 'Manual' - manual placement of fiducials.")
) )
grid_lay.addWidget(self.mode_label, 3, 0) grid_lay.addWidget(self.mode_label, 3, 0)
@ -6361,7 +6379,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial")) self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial"))
self.pos_label.setToolTip( self.pos_label.setToolTip(
_("The position for the second fiducial.\n" _("The position for the second fiducial.\n"
"- 'Up' - the order is: bottom-left, top-left, top-right.\n " "- 'Up' - the order is: bottom-left, top-left, top-right.\n"
"- 'Down' - the order is: bottom-left, bottom-right, top-right.\n" "- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
"- 'None' - there is no second fiducial. The order is: bottom-left, top-right.") "- 'None' - there is no second fiducial. The order is: bottom-left, top-right.")
) )
@ -6495,6 +6513,33 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
grid_lay.addWidget(toolchangez_lbl, 6, 0) grid_lay.addWidget(toolchangez_lbl, 6, 0)
grid_lay.addWidget(self.toolchangez_entry, 6, 1, 1, 2) grid_lay.addWidget(self.toolchangez_entry, 6, 1, 1, 2)
# Toolchange X-Y entry
toolchangexy_lbl = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
toolchangexy_lbl.setToolTip(
_("Toolchange X,Y position.\n"
"If no value is entered then the current\n"
"(x, y) point will be used,")
)
self.toolchange_xy_entry = FCEntry()
grid_lay.addWidget(toolchangexy_lbl, 7, 0)
grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)
# Second point choice
second_point_lbl = QtWidgets.QLabel('%s:' % _("Second point"))
second_point_lbl.setToolTip(
_("Second point in the Gcode verification can be:\n"
"- top-left -> the user will align the PCB vertically\n"
"- bottom-right -> the user will align the PCB horizontally")
)
self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
{'label': _('Bottom-Right'), 'value': 'br'}],
orientation='vertical')
grid_lay.addWidget(second_point_lbl, 8, 0)
grid_lay.addWidget(self.second_point_radio, 8, 1, 1, 2)
self.layout.addStretch() self.layout.addStretch()

View File

@ -108,10 +108,17 @@ class VisPyCanvas(scene.SceneCanvas):
# self.measure_fps() # self.measure_fps()
def translate_coords(self, pos): def translate_coords(self, pos):
"""
Translate pixels to FlatCAM units.
"""
tr = self.grid.get_transform('canvas', 'visual') tr = self.grid.get_transform('canvas', 'visual')
return tr.map(pos) return tr.map(pos)
def translate_coords_2(self, pos): def translate_coords_2(self, pos):
"""
Translate FlatCAM units to pixels.
"""
tr = self.grid.get_transform('visual', 'document') tr = self.grid.get_transform('visual', 'document')
return tr.map(pos) return tr.map(pos)

View File

@ -94,11 +94,11 @@ class Excellon(Geometry):
Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle)) Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle))
# dictionary to store tools, see above for description # dictionary to store tools, see above for description
self.tools = {} self.tools = dict()
# list to store the drills, see above for description # list to store the drills, see above for description
self.drills = [] self.drills = list()
# self.slots (list) to store the slots; each is a dictionary # self.slots (list) to store the slots; each is a dictionary
self.slots = [] self.slots = list()
self.source_file = '' self.source_file = ''
@ -109,8 +109,8 @@ class Excellon(Geometry):
self.match_routing_start = None self.match_routing_start = None
self.match_routing_stop = None self.match_routing_stop = None
self.num_tools = [] # List for keeping the tools sorted self.num_tools = list() # List for keeping the tools sorted
self.index_per_tool = {} # Dictionary to store the indexed points for each tool self.index_per_tool = dict() # Dictionary to store the indexed points for each tool
# ## IN|MM -> Units are inherited from Geometry # ## IN|MM -> Units are inherited from Geometry
self.units = self.app.defaults['units'] self.units = self.app.defaults['units']
@ -118,8 +118,8 @@ class Excellon(Geometry):
# Trailing "T" or leading "L" (default) # Trailing "T" or leading "L" (default)
# self.zeros = "T" # self.zeros = "T"
self.zeros = zeros or self.defaults["zeros"] self.zeros = zeros or self.defaults["zeros"]
self.zeros_found = self.zeros self.zeros_found = deepcopy(self.zeros)
self.units_found = self.units self.units_found = deepcopy(self.units)
# this will serve as a default if the Excellon file has no info regarding of tool diameters (this info may be # this will serve as a default if the Excellon file has no info regarding of tool diameters (this info may be
# in another file like for PCB WIzard ECAD software # in another file like for PCB WIzard ECAD software
@ -790,7 +790,7 @@ class Excellon(Geometry):
# ## Units and number format # ## # ## Units and number format # ##
match = self.units_re.match(eline) match = self.units_re.match(eline)
if match: if match:
self.units = self.units = {"METRIC": "MM", "INCH": "IN"}[match.group(1)] self.units = {"METRIC": "MM", "INCH": "IN"}[match.group(1)]
self.zeros = match.group(2) # "T" or "L". Might be empty self.zeros = match.group(2) # "T" or "L". Might be empty
self.excellon_format = match.group(3) self.excellon_format = match.group(3)
if self.excellon_format: if self.excellon_format:
@ -884,8 +884,9 @@ class Excellon(Geometry):
log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline)) log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
msg = '[ERROR_NOTCL] %s' % \ msg = '[ERROR_NOTCL] %s' % \
_("An internal error has ocurred. See shell.\n") _("An internal error has ocurred. See shell.\n")
msg += _('{e_code} Excellon Parser error.\nParsing Failed. Line {l_nr}: {line}\n').format( msg += ('{e_code} {tx} {l_nr}: {line}\n').format(
e_code='[ERROR]', e_code='[ERROR]',
tx=_("Excellon Parser error.\nParsing Failed. Line"),
l_nr=line_num, l_nr=line_num,
line=eline) line=eline)
msg += traceback.format_exc() msg += traceback.format_exc()

View File

@ -0,0 +1,423 @@
# ############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# File Author: Marius Adrian Stanciu (c) #
# Date: 12/12/2019 #
# MIT Licence #
# ############################################################
from camlib import arc, three_point_circle
import FlatCAMApp
import numpy as np
import re
import logging
import traceback
from copy import deepcopy
import sys
from shapely.ops import unary_union
from shapely.geometry import LineString, Point
import FlatCAMTranslation as fcTranslate
import gettext
import builtins
if '_' not in builtins.__dict__:
_ = gettext.gettext
log = logging.getLogger('base')
class HPGL2:
"""
HPGL2 parsing.
"""
def __init__(self, app):
"""
The constructor takes FlatCAMApp.App as parameter.
"""
self.app = app
# How to approximate a circle with lines.
self.steps_per_circle = int(self.app.defaults["geometry_circle_steps"])
self.decimals = self.app.decimals
# store the file units here
self.units = 'MM'
# storage for the tools
self.tools = dict()
self.default_data = dict()
self.default_data.update({
"name": '_ncc',
"plot": self.app.defaults["geometry_plot"],
"cutz": self.app.defaults["geometry_cutz"],
"vtipdia": self.app.defaults["geometry_vtipdia"],
"vtipangle": self.app.defaults["geometry_vtipangle"],
"travelz": self.app.defaults["geometry_travelz"],
"feedrate": self.app.defaults["geometry_feedrate"],
"feedrate_z": self.app.defaults["geometry_feedrate_z"],
"feedrate_rapid": self.app.defaults["geometry_feedrate_rapid"],
"dwell": self.app.defaults["geometry_dwell"],
"dwelltime": self.app.defaults["geometry_dwelltime"],
"multidepth": self.app.defaults["geometry_multidepth"],
"ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"],
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"startz": self.app.defaults["geometry_startz"],
"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"]
})
# will store the geometry here for compatibility reason
self.solid_geometry = None
self.source_file = ''
# ### Parser patterns ## ##
# comment
self.comment_re = re.compile(r"^CO\s*[\"']([a-zA-Z0-9\s]*)[\"'];?$")
# select pen
self.sp_re = re.compile(r'SP(\d);?$')
# pen position
self.pen_re = re.compile(r"^(P[U|D]);?$")
# Initialize
self.initialize_re = re.compile(r'^(IN);?$')
# Absolute linear interpolation
self.abs_move_re = re.compile(r"^PA\s*(-?\d+\.?\d+?),?\s*(-?\d+\.?\d+?)*;?$")
# Relative linear interpolation
self.rel_move_re = re.compile(r"^PR\s*(-?\d+\.\d+?),?\s*(-?\d+\.\d+?)*;?$")
# Circular interpolation with radius
self.circ_re = re.compile(r"^CI\s*(\+?\d+\.?\d+?)?\s*;?\s*$")
# Arc interpolation with radius
self.arc_re = re.compile(r"^AA\s*([+-]?\d+),?\s*([+-]?\d+),?\s*([+-]?\d+);?$")
# Arc interpolation with 3 points
self.arc_3pt_re = re.compile(r"^AT\s*([+-]?\d+),?\s*([+-]?\d+),?\s*([+-]?\d+),?\s*([+-]?\d+);?$")
self.init_done = None
def parse_file(self, filename):
"""
Creates a list of lines from the HPGL2 file and send it to the main parser.
:param filename: HPGL2 file to parse.
:type filename: str
:return: None
"""
with open(filename, 'r') as gfile:
glines = [line.rstrip('\n') for line in gfile]
self.parse_lines(glines=glines)
def parse_lines(self, glines):
"""
Main HPGL2 parser.
:param glines: HPGL2 code as list of strings, each element being
one line of the source file.
:type glines: list
:return: None
:rtype: None
"""
# Coordinates of the current path, each is [x, y]
path = list()
geo_buffer = []
# Current coordinates
current_x = None
current_y = None
# Found coordinates
linear_x = None
linear_y = None
# store the pen (tool) status
pen_status = 'up'
# store the current tool here
current_tool = None
# ### Parsing starts here ## ##
line_num = 0
gline = ""
self.app.inform.emit('%s %d %s.' % (_("HPGL2 processing. Parsing"), len(glines), _("lines")))
try:
for gline in glines:
if self.app.abort_flag:
# graceful abort requested by the user
raise FlatCAMApp.GracefulException
line_num += 1
self.source_file += gline + '\n'
# Cleanup #
gline = gline.strip(' \r\n')
# log.debug("Line=%3s %s" % (line_num, gline))
# ###################
# Ignored lines #####
# Comments #####
# ###################
match = self.comment_re.search(gline)
if match:
log.debug(str(match.group(1)))
continue
# search for the initialization
match = self.initialize_re.search(gline)
if match:
self.init_done = True
continue
if self.init_done is True:
# tools detection
match = self.sp_re.search(gline)
if match:
tool = match.group(1)
# self.tools[tool] = dict()
self.tools.update({
tool: {
'tooldia': float('%.*f' %
(
self.decimals,
float(self.app.defaults['geometry_cnctooldia'])
)
),
'offset': 'Path',
'offset_value': 0.0,
'type': 'Iso',
'tool_type': 'C1',
'data': deepcopy(self.default_data),
'solid_geometry': list()
}
})
if current_tool:
if path:
geo = LineString(path)
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
path[:] = []
current_tool = tool
continue
# pen status detection
match = self.pen_re.search(gline)
if match:
pen_status = {'PU': 'up', 'PD': 'down'}[match.group(1)]
continue
# Linear interpolation
match = self.abs_move_re.search(gline)
if match:
# Parse coordinates
if match.group(1) is not None:
linear_x = parse_number(match.group(1))
current_x = linear_x
else:
linear_x = current_x
if match.group(2) is not None:
linear_y = parse_number(match.group(2))
current_y = linear_y
else:
linear_y = current_y
# Pen down: add segment
if pen_status == 'down':
# if linear_x or linear_y are None, ignore those
if current_x is not None and current_y is not None:
# only add the point if it's a new one otherwise skip it (harder to process)
if path[-1] != [current_x, current_y]:
path.append([current_x, current_y])
else:
self.app.inform.emit('[WARNING] %s: %s' %
(_("Coordinates missing, line ignored"), str(gline)))
elif pen_status == 'up':
if len(path) > 1:
geo = LineString(path)
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
path[:] = []
# if linear_x or linear_y are None, ignore those
if linear_x is not None and linear_y is not None:
path = [[linear_x, linear_y]] # Start new path
else:
self.app.inform.emit('[WARNING] %s: %s' %
(_("Coordinates missing, line ignored"), str(gline)))
# log.debug("Line_number=%3s X=%s Y=%s (%s)" % (line_num, linear_x, linear_y, gline))
continue
# Circular interpolation
match = self.circ_re.search(gline)
if match:
if len(path) > 1:
geo = LineString(path)
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
path[:] = []
# if linear_x or linear_y are None, ignore those
if linear_x is not None and linear_y is not None:
path = [[linear_x, linear_y]] # Start new path
else:
self.app.inform.emit('[WARNING] %s: %s' %
(_("Coordinates missing, line ignored"), str(gline)))
if current_x is not None and current_y is not None:
radius = match.group(1)
geo = Point((current_x, current_y)).buffer(radius, int(self.steps_per_circle))
geo_line = geo.exterior
self.tools[current_tool]['solid_geometry'].append(geo_line)
geo_buffer.append(geo_line)
continue
# Arc interpolation with radius
match = self.arc_re.search(gline)
if match:
if len(path) > 1:
geo = LineString(path)
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
path[:] = []
# if linear_x or linear_y are None, ignore those
if linear_x is not None and linear_y is not None:
path = [[linear_x, linear_y]] # Start new path
else:
self.app.inform.emit('[WARNING] %s: %s' %
(_("Coordinates missing, line ignored"), str(gline)))
if current_x is not None and current_y is not None:
center = [parse_number(match.group(1)), parse_number(match.group(2))]
angle = np.deg2rad(float(match.group(3)))
p1 = [current_x, current_y]
arcdir = "ccw" if angle >= 0.0 else "cw"
radius = np.sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = startangle + angle
geo = LineString(arc(center, radius, startangle, stopangle, arcdir, self.steps_per_circle))
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
line_coords = list(geo.coords)
current_x = line_coords[0]
current_y = line_coords[1]
continue
# Arc interpolation with 3 points
match = self.arc_3pt_re.search(gline)
if match:
if len(path) > 1:
geo = LineString(path)
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
path[:] = []
# if linear_x or linear_y are None, ignore those
if linear_x is not None and linear_y is not None:
path = [[linear_x, linear_y]] # Start new path
else:
self.app.inform.emit('[WARNING] %s: %s' %
(_("Coordinates missing, line ignored"), str(gline)))
if current_x is not None and current_y is not None:
p1 = [current_x, current_y]
p3 = [parse_number(match.group(1)), parse_number(match.group(2))]
p2 = [parse_number(match.group(3)), parse_number(match.group(4))]
try:
center, radius, t = three_point_circle(p1, p2, p3)
except TypeError:
return
direction = 'cw' if np.sign(t) > 0 else 'ccw'
startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
stopangle = np.arctan2(p3[1] - center[1], p3[0] - center[0])
geo = LineString(arc(center, radius, startangle, stopangle,
direction, self.steps_per_circle))
self.tools[current_tool]['solid_geometry'].append(geo)
geo_buffer.append(geo)
# p2 is the end point for the 3-pt circle
current_x = p2[0]
current_y = p2[1]
continue
# ## Line did not match any pattern. Warn user.
log.warning("Line ignored (%d): %s" % (line_num, gline))
if not geo_buffer and not self.solid_geometry:
log.error("Object is not HPGL2 file or empty. Aborting Object creation.")
return 'fail'
log.warning("Joining %d polygons." % len(geo_buffer))
self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), len(geo_buffer)))
new_poly = unary_union(geo_buffer)
self.solid_geometry = new_poly
except Exception as err:
ex_type, ex, tb = sys.exc_info()
traceback.print_tb(tb)
print(traceback.format_exc())
log.error("HPGL2 PARSING FAILED. Line %d: %s" % (line_num, gline))
loc = '%s #%d %s: %s\n' % (_("HPGL2 Line"), line_num, _("HPGL2 Line Content"), gline) + repr(err)
self.app.inform.emit('[ERROR] %s\n%s:' % (_("HPGL2 Parser ERROR"), loc))
def parse_number(strnumber):
"""
Parse a single number of HPGL2 coordinates.
:param strnumber: String containing a number
from a coordinate data block, possibly with a leading sign.
:type strnumber: str
:return: The number in floating point.
:rtype: float
"""
return float(strnumber) / 40.0 # in milimeters

View File

@ -8,12 +8,13 @@
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from FlatCAMTool import FlatCAMTool from FlatCAMTool import FlatCAMTool
from flatcamGUI.GUIElements import FCDoubleSpinner, EvalEntry, FCCheckBox, OptionalInputSection from flatcamGUI.GUIElements import FCDoubleSpinner, EvalEntry, FCCheckBox, OptionalInputSection, FCEntry
from flatcamGUI.GUIElements import FCTable, FCComboBox, RadioSet from flatcamGUI.GUIElements import FCTable, FCComboBox, RadioSet
from flatcamEditors.FlatCAMTextEditor import TextEditor from flatcamEditors.FlatCAMTextEditor import TextEditor
from shapely.geometry import Point from shapely.geometry import Point
from shapely.geometry.base import * from shapely.geometry.base import *
from shapely.affinity import scale, skew
import math import math
from datetime import datetime from datetime import datetime
@ -63,13 +64,119 @@ class ToolCalibration(FlatCAMTool):
grid_lay.setColumnStretch(1, 1) grid_lay.setColumnStretch(1, 1)
grid_lay.setColumnStretch(2, 0) grid_lay.setColumnStretch(2, 0)
self.gcode_title_label = QtWidgets.QLabel('<b>%s</b>' % _('GCode Parameters'))
self.gcode_title_label.setToolTip(
_("Parameters used when creating the GCode in this tool.")
)
grid_lay.addWidget(self.gcode_title_label, 0, 0, 1, 3)
# Travel Z entry
travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
travelz_lbl.setToolTip(
_("Height (Z) for travelling between the points.")
)
self.travelz_entry = FCDoubleSpinner()
self.travelz_entry.set_range(-9999.9999, 9999.9999)
self.travelz_entry.set_precision(self.decimals)
self.travelz_entry.setSingleStep(0.1)
grid_lay.addWidget(travelz_lbl, 1, 0)
grid_lay.addWidget(self.travelz_entry, 1, 1, 1, 2)
# Verification Z entry
verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
verz_lbl.setToolTip(
_("Height (Z) for checking the point.")
)
self.verz_entry = FCDoubleSpinner()
self.verz_entry.set_range(-9999.9999, 9999.9999)
self.verz_entry.set_precision(self.decimals)
self.verz_entry.setSingleStep(0.1)
grid_lay.addWidget(verz_lbl, 2, 0)
grid_lay.addWidget(self.verz_entry, 2, 1, 1, 2)
# Zero the Z of the verification tool
self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
self.zeroz_cb.setToolTip(
_("Include a sequence to zero the height (Z)\n"
"of the verification tool.")
)
grid_lay.addWidget(self.zeroz_cb, 3, 0, 1, 3)
# Toolchange Z entry
toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
toolchangez_lbl.setToolTip(
_("Height (Z) for mounting the verification probe.")
)
self.toolchangez_entry = FCDoubleSpinner()
self.toolchangez_entry.set_range(0.0000, 9999.9999)
self.toolchangez_entry.set_precision(self.decimals)
self.toolchangez_entry.setSingleStep(0.1)
grid_lay.addWidget(toolchangez_lbl, 4, 0)
grid_lay.addWidget(self.toolchangez_entry, 4, 1, 1, 2)
# Toolchange X-Y entry
toolchangexy_lbl = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
toolchangexy_lbl.setToolTip(
_("Toolchange X,Y position.\n"
"If no value is entered then the current\n"
"(x, y) point will be used,")
)
self.toolchange_xy_entry = FCEntry()
grid_lay.addWidget(toolchangexy_lbl, 5, 0)
grid_lay.addWidget(self.toolchange_xy_entry, 5, 1, 1, 2)
self.z_ois = OptionalInputSection(
self.zeroz_cb,
[
toolchangez_lbl,
self.toolchangez_entry,
toolchangexy_lbl,
self.toolchange_xy_entry
]
)
separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line1, 6, 0, 1, 3)
# Second point choice
second_point_lbl = QtWidgets.QLabel('%s:' % _("Second point"))
second_point_lbl.setToolTip(
_("Second point in the Gcode verification can be:\n"
"- top-left -> the user will align the PCB vertically\n"
"- bottom-right -> the user will align the PCB horizontally")
)
self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
{'label': _('Bottom-Right'), 'value': 'br'}],
orientation='vertical')
grid_lay.addWidget(second_point_lbl, 7, 0)
grid_lay.addWidget(self.second_point_radio, 7, 1, 1, 2)
separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line1, 8, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 3)
step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1: Acquire Calibration Points")) step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1: Acquire Calibration Points"))
step_1.setToolTip( step_1.setToolTip(
_("Pick four points by clicking inside the drill holes.\n" _("Pick four points by clicking on canvas.\n"
"Those four points should be in the four\n" "Those four points should be in the four\n"
"(as much as possible) corners of the Excellon object.") "(as much as possible) corners of the object.")
) )
grid_lay.addWidget(step_1, 0, 0, 1, 3) grid_lay.addWidget(step_1, 10, 0, 1, 3)
self.cal_source_lbl = QtWidgets.QLabel("<b>%s:</b>" % _("Source Type")) self.cal_source_lbl = QtWidgets.QLabel("<b>%s:</b>" % _("Source Type"))
self.cal_source_lbl.setToolTip(_("The source of calibration points.\n" self.cal_source_lbl.setToolTip(_("The source of calibration points.\n"
@ -80,8 +187,8 @@ class ToolCalibration(FlatCAMTool):
{'label': _('Free'), 'value': 'free'}], {'label': _('Free'), 'value': 'free'}],
stretch=False) stretch=False)
grid_lay.addWidget(self.cal_source_lbl, 1, 0) grid_lay.addWidget(self.cal_source_lbl, 11, 0)
grid_lay.addWidget(self.cal_source_radio, 1, 1, 1, 2) grid_lay.addWidget(self.cal_source_radio, 11, 1, 1, 2)
self.obj_type_label = QtWidgets.QLabel("%s:" % _("Object Type")) self.obj_type_label = QtWidgets.QLabel("%s:" % _("Object Type"))
@ -90,8 +197,11 @@ class ToolCalibration(FlatCAMTool):
self.obj_type_combo.addItem(_("Excellon")) self.obj_type_combo.addItem(_("Excellon"))
self.obj_type_combo.setCurrentIndex(1) self.obj_type_combo.setCurrentIndex(1)
grid_lay.addWidget(self.obj_type_label, 2, 0) self.obj_type_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
grid_lay.addWidget(self.obj_type_combo, 2, 1, 1, 2) self.obj_type_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
grid_lay.addWidget(self.obj_type_label, 12, 0)
grid_lay.addWidget(self.obj_type_combo, 12, 1, 1, 2)
self.object_combo = FCComboBox() self.object_combo = FCComboBox()
self.object_combo.setModel(self.app.collection) self.object_combo.setModel(self.app.collection)
@ -103,20 +213,20 @@ class ToolCalibration(FlatCAMTool):
_("FlatCAM Object to be used as a source for reference points.") _("FlatCAM Object to be used as a source for reference points.")
) )
grid_lay.addWidget(self.object_label, 3, 0, 1, 3) grid_lay.addWidget(self.object_label, 13, 0, 1, 3)
grid_lay.addWidget(self.object_combo, 4, 0, 1, 3) grid_lay.addWidget(self.object_combo, 14, 0, 1, 3)
self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points')) self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points'))
self.points_table_label.setToolTip( self.points_table_label.setToolTip(
_("Contain the expected calibration points and the\n" _("Contain the expected calibration points and the\n"
"ones measured.") "ones measured.")
) )
grid_lay.addWidget(self.points_table_label, 5, 0, 1, 3) grid_lay.addWidget(self.points_table_label, 15, 0, 1, 3)
self.points_table = FCTable() self.points_table = FCTable()
self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) # self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
grid_lay.addWidget(self.points_table, 6, 0, 1, 3) grid_lay.addWidget(self.points_table, 16, 0, 1, 3)
self.points_table.setColumnCount(4) self.points_table.setColumnCount(4)
self.points_table.setHorizontalHeaderLabels( self.points_table.setHorizontalHeaderLabels(
@ -220,6 +330,7 @@ class ToolCalibration(FlatCAMTool):
self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt) self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
self.top_right_coordx_tgt.setReadOnly(True) self.top_right_coordx_tgt.setReadOnly(True)
self.top_right_coordx_found = EvalEntry() self.top_right_coordx_found = EvalEntry()
self.top_right_coordx_found.setDisabled(True)
self.points_table.setCellWidget(row, 3, self.top_right_coordx_found) self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
row += 1 row += 1
@ -229,6 +340,7 @@ class ToolCalibration(FlatCAMTool):
self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt) self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
self.top_right_coordy_tgt.setReadOnly(True) self.top_right_coordy_tgt.setReadOnly(True)
self.top_right_coordy_found = EvalEntry() self.top_right_coordy_found = EvalEntry()
self.top_right_coordy_found.setDisabled(True)
self.points_table.setCellWidget(row, 3, self.top_right_coordy_found) self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
vertical_header = self.points_table.verticalHeader() vertical_header = self.points_table.verticalHeader()
@ -268,87 +380,38 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.start_button, 7, 0, 1, 3) grid_lay.addWidget(self.start_button, 17, 0, 1, 3)
separator_line = QtWidgets.QFrame() separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line, 8, 0, 1, 3) grid_lay.addWidget(separator_line, 18, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0) grid_lay.addWidget(QtWidgets.QLabel(''), 19, 0)
# STEP 2 # # STEP 2 #
step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode")) step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode"))
step_2.setToolTip( step_2.setToolTip(
_("Generate GCode file to locate and align the PCB by using\n" _("Generate GCode file to locate and align the PCB by using\n"
"the four points acquired above.") "the four points acquired above.\n"
"The points sequence is:\n"
"- first point -> set the origin\n"
"- second point -> alignment point. Can be: top-left or bottom-right.\n"
"- third point -> check point. Can be: top-left or bottom-right.\n"
"- forth point -> final verification point. Just for evaluation.")
) )
grid_lay.addWidget(step_2, 10, 0, 1, 3) grid_lay.addWidget(step_2, 20, 0, 1, 3)
self.gcode_title_label = QtWidgets.QLabel('<b>%s</b>' % _('GCode Parameters'))
self.gcode_title_label.setToolTip(
_("Parameters used when creating the GCode in this tool.")
)
grid_lay.addWidget(self.gcode_title_label, 11, 0, 1, 3)
# Travel Z entry
travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
travelz_lbl.setToolTip(
_("Height (Z) for travelling between the points.")
)
self.travelz_entry = FCDoubleSpinner()
self.travelz_entry.set_range(-9999.9999, 9999.9999)
self.travelz_entry.set_precision(self.decimals)
self.travelz_entry.setSingleStep(0.1)
grid_lay.addWidget(travelz_lbl, 12, 0)
grid_lay.addWidget(self.travelz_entry, 12, 1, 1, 2)
# Verification Z entry
verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
verz_lbl.setToolTip(
_("Height (Z) for checking the point.")
)
self.verz_entry = FCDoubleSpinner()
self.verz_entry.set_range(-9999.9999, 9999.9999)
self.verz_entry.set_precision(self.decimals)
self.verz_entry.setSingleStep(0.1)
grid_lay.addWidget(verz_lbl, 13, 0)
grid_lay.addWidget(self.verz_entry, 13, 1, 1, 2)
# Zero the Z of the verification tool
self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
self.zeroz_cb.setToolTip(
_("Include a sequence to zero the height (Z)\n"
"of the verification tool.")
)
grid_lay.addWidget(self.zeroz_cb, 14, 0, 1, 3)
# Toochange Z entry
toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
toolchangez_lbl.setToolTip(
_("Height (Z) for mounting the verification probe.")
)
self.toolchangez_entry = FCDoubleSpinner()
self.toolchangez_entry.set_range(0.0000, 9999.9999)
self.toolchangez_entry.set_precision(self.decimals)
self.toolchangez_entry.setSingleStep(0.1)
grid_lay.addWidget(toolchangez_lbl, 15, 0)
grid_lay.addWidget(self.toolchangez_entry, 15, 1, 1, 2)
self.z_ois = OptionalInputSection(self.zeroz_cb, [toolchangez_lbl, self.toolchangez_entry])
# ## GCode Button # ## GCode Button
self.gcode_button = QtWidgets.QPushButton(_("Generate GCode")) self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
self.gcode_button.setToolTip( self.gcode_button.setToolTip(
_("Generate GCode file to locate and align the PCB by using\n" _("Generate GCode file to locate and align the PCB by using\n"
"the four points acquired above.") "the four points acquired above.\n"
"The points sequence is:\n"
"- first point -> set the origin\n"
"- second point -> alignment point. Can be: top-left or bottom-right.\n"
"- third point -> check point. Can be: top-left or bottom-right.\n"
"- forth point -> final verification point. Just for evaluation.")
) )
self.gcode_button.setStyleSheet(""" self.gcode_button.setStyleSheet("""
QPushButton QPushButton
@ -356,14 +419,14 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.gcode_button, 16, 0, 1, 3) grid_lay.addWidget(self.gcode_button, 21, 0, 1, 3)
separator_line1 = QtWidgets.QFrame() separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine) separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line1, 17, 0, 1, 3) grid_lay.addWidget(separator_line1, 22, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 18, 0, 1, 3) grid_lay.addWidget(QtWidgets.QLabel(''), 23, 0, 1, 3)
# STEP 3 # # STEP 3 #
step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3: Adjustments")) step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3: Adjustments"))
@ -372,7 +435,7 @@ class ToolCalibration(FlatCAMTool):
"found when checking the PCB pattern. The differences must be filled\n" "found when checking the PCB pattern. The differences must be filled\n"
"in the fields Found (Delta).") "in the fields Found (Delta).")
) )
grid_lay.addWidget(step_3, 19, 0, 1, 3) grid_lay.addWidget(step_3, 24, 0, 1, 3)
# ## Factors Button # ## Factors Button
self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors")) self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
@ -387,14 +450,14 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.generate_factors_button, 20, 0, 1, 3) grid_lay.addWidget(self.generate_factors_button, 25, 0, 1, 3)
separator_line1 = QtWidgets.QFrame() separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine) separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line1, 21, 0, 1, 3) grid_lay.addWidget(separator_line1, 26, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 22, 0, 1, 3) grid_lay.addWidget(QtWidgets.QLabel(''), 27, 0, 1, 3)
# STEP 4 # # STEP 4 #
step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode")) step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode"))
@ -402,7 +465,7 @@ class ToolCalibration(FlatCAMTool):
_("Generate verification GCode file adjusted with\n" _("Generate verification GCode file adjusted with\n"
"the factors above.") "the factors above.")
) )
grid_lay.addWidget(step_4, 23, 0, 1, 3) grid_lay.addWidget(step_4, 28, 0, 1, 3)
self.scalex_label = QtWidgets.QLabel(_("Scale Factor X:")) self.scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
self.scalex_label.setToolTip( self.scalex_label.setToolTip(
@ -413,8 +476,8 @@ class ToolCalibration(FlatCAMTool):
self.scalex_entry.set_precision(self.decimals) self.scalex_entry.set_precision(self.decimals)
self.scalex_entry.setSingleStep(0.1) self.scalex_entry.setSingleStep(0.1)
grid_lay.addWidget(self.scalex_label, 24, 0) grid_lay.addWidget(self.scalex_label, 29, 0)
grid_lay.addWidget(self.scalex_entry, 24, 1, 1, 2) grid_lay.addWidget(self.scalex_entry, 29, 1, 1, 2)
self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:")) self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
self.scaley_label.setToolTip( self.scaley_label.setToolTip(
@ -425,8 +488,8 @@ class ToolCalibration(FlatCAMTool):
self.scaley_entry.set_precision(self.decimals) self.scaley_entry.set_precision(self.decimals)
self.scaley_entry.setSingleStep(0.1) self.scaley_entry.setSingleStep(0.1)
grid_lay.addWidget(self.scaley_label, 25, 0) grid_lay.addWidget(self.scaley_label, 30, 0)
grid_lay.addWidget(self.scaley_entry, 25, 1, 1, 2) grid_lay.addWidget(self.scaley_entry, 30, 1, 1, 2)
self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors")) self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors"))
self.scale_button.setToolTip( self.scale_button.setToolTip(
@ -438,7 +501,7 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.scale_button, 26, 0, 1, 3) grid_lay.addWidget(self.scale_button, 31, 0, 1, 3)
self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:")) self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
self.skewx_label.setToolTip( self.skewx_label.setToolTip(
@ -450,8 +513,8 @@ class ToolCalibration(FlatCAMTool):
self.skewx_entry.set_precision(self.decimals) self.skewx_entry.set_precision(self.decimals)
self.skewx_entry.setSingleStep(0.1) self.skewx_entry.setSingleStep(0.1)
grid_lay.addWidget(self.skewx_label, 27, 0) grid_lay.addWidget(self.skewx_label, 32, 0)
grid_lay.addWidget(self.skewx_entry, 27, 1, 1, 2) grid_lay.addWidget(self.skewx_entry, 32, 1, 1, 2)
self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:")) self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
self.skewy_label.setToolTip( self.skewy_label.setToolTip(
@ -463,8 +526,8 @@ class ToolCalibration(FlatCAMTool):
self.skewy_entry.set_precision(self.decimals) self.skewy_entry.set_precision(self.decimals)
self.skewy_entry.setSingleStep(0.1) self.skewy_entry.setSingleStep(0.1)
grid_lay.addWidget(self.skewy_label, 28, 0) grid_lay.addWidget(self.skewy_label, 33, 0)
grid_lay.addWidget(self.skewy_entry, 28, 1, 1, 2) grid_lay.addWidget(self.skewy_entry, 33, 1, 1, 2)
self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors")) self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors"))
self.skew_button.setToolTip( self.skew_button.setToolTip(
@ -476,7 +539,7 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.skew_button, 29, 0, 1, 3) grid_lay.addWidget(self.skew_button, 34, 0, 1, 3)
# final_factors_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Final Factors")) # final_factors_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Final Factors"))
# final_factors_lbl.setToolTip( # final_factors_lbl.setToolTip(
@ -540,7 +603,9 @@ class ToolCalibration(FlatCAMTool):
self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode")) self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
self.adj_gcode_button.setToolTip( self.adj_gcode_button.setToolTip(
_("Generate verification GCode file adjusted with\n" _("Generate verification GCode file adjusted with\n"
"the factors above.") "the factors set above.\n"
"The GCode parameters can be readjusted\n"
"before clicking this button.")
) )
self.adj_gcode_button.setStyleSheet(""" self.adj_gcode_button.setStyleSheet("""
QPushButton QPushButton
@ -548,14 +613,14 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.adj_gcode_button, 35, 0, 1, 3) grid_lay.addWidget(self.adj_gcode_button, 42, 0, 1, 3)
separator_line1 = QtWidgets.QFrame() separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine) separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line1, 36, 0, 1, 3) grid_lay.addWidget(separator_line1, 43, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 37, 0, 1, 3) grid_lay.addWidget(QtWidgets.QLabel(''), 44, 0, 1, 3)
# STEP 5 # # STEP 5 #
step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects")) step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects"))
@ -563,19 +628,23 @@ class ToolCalibration(FlatCAMTool):
_("Adjust the FlatCAM objects\n" _("Adjust the FlatCAM objects\n"
"with the factors determined and verified above.") "with the factors determined and verified above.")
) )
grid_lay.addWidget(step_5, 38, 0, 1, 3) grid_lay.addWidget(step_5, 45, 0, 1, 3)
self.adj_object_type_combo = QtWidgets.QComboBox() self.adj_object_type_combo = QtWidgets.QComboBox()
self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")]) self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
self.adj_object_type_combo.setCurrentIndex(0) self.adj_object_type_combo.setCurrentIndex(0)
self.adj_object_type_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
self.adj_object_type_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
self.adj_object_type_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type")) self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type"))
self.adj_object_type_label.setToolTip( self.adj_object_type_label.setToolTip(
_("Type of the FlatCAM Object to be adjusted.") _("Type of the FlatCAM Object to be adjusted.")
) )
grid_lay.addWidget(self.adj_object_type_label, 39, 0, 1, 3) grid_lay.addWidget(self.adj_object_type_label, 46, 0, 1, 3)
grid_lay.addWidget(self.adj_object_type_combo, 40, 0, 1, 3) grid_lay.addWidget(self.adj_object_type_combo, 47, 0, 1, 3)
self.adj_object_combo = FCComboBox() self.adj_object_combo = FCComboBox()
self.adj_object_combo.setModel(self.app.collection) self.adj_object_combo.setModel(self.app.collection)
@ -587,8 +656,8 @@ class ToolCalibration(FlatCAMTool):
_("The FlatCAM Object to be adjusted.") _("The FlatCAM Object to be adjusted.")
) )
grid_lay.addWidget(self.adj_object_label, 41, 0, 1, 3) grid_lay.addWidget(self.adj_object_label, 48, 0, 1, 3)
grid_lay.addWidget(self.adj_object_combo, 42, 0, 1, 3) grid_lay.addWidget(self.adj_object_combo, 49, 0, 1, 3)
# ## Adjust Objects Button # ## Adjust Objects Button
self.cal_button = QtWidgets.QPushButton(_("Calibrate")) self.cal_button = QtWidgets.QPushButton(_("Calibrate"))
@ -602,14 +671,14 @@ class ToolCalibration(FlatCAMTool):
font-weight: bold; font-weight: bold;
} }
""") """)
grid_lay.addWidget(self.cal_button, 43, 0, 1, 3) grid_lay.addWidget(self.cal_button, 50, 0, 1, 3)
separator_line2 = QtWidgets.QFrame() separator_line2 = QtWidgets.QFrame()
separator_line2.setFrameShape(QtWidgets.QFrame.HLine) separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay.addWidget(separator_line2, 44, 0, 1, 3) grid_lay.addWidget(separator_line2, 51, 0, 1, 3)
grid_lay.addWidget(QtWidgets.QLabel(''), 45, 0, 1, 3) grid_lay.addWidget(QtWidgets.QLabel(''), 52, 0, 1, 3)
self.layout.addStretch() self.layout.addStretch()
@ -630,7 +699,7 @@ class ToolCalibration(FlatCAMTool):
self.units = '' self.units = ''
# here store 4 points to be used for calibration # here store 4 points to be used for calibration
self.click_points = list() self.click_points = [[], [], [], []]
# store the status of the grid # store the status of the grid
self.grid_status_memory = None self.grid_status_memory = None
@ -647,17 +716,22 @@ class ToolCalibration(FlatCAMTool):
self.cal_object = None self.cal_object = None
# ## Signals # ## Signals
self.start_button.clicked.connect(self.on_start_collect_points)
self.gcode_button.clicked.connect(self.generate_verification_gcode)
self.generate_factors_button.clicked.connect(self.calculate_factors)
self.reset_button.clicked.connect(self.set_tool_ui)
self.cal_source_radio.activated_custom.connect(self.on_cal_source_radio) self.cal_source_radio.activated_custom.connect(self.on_cal_source_radio)
self.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo) self.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo)
self.adj_object_type_combo.currentIndexChanged.connect(self.on_adj_obj_type_combo) self.adj_object_type_combo.currentIndexChanged.connect(self.on_adj_obj_type_combo)
self.start_button.clicked.connect(self.on_start_collect_points)
self.gcode_button.clicked.connect(self.generate_verification_gcode)
self.adj_gcode_button.clicked.connect(self.generate_verification_gcode)
self.generate_factors_button.clicked.connect(self.calculate_factors)
self.scale_button.clicked.connect(self.on_scale_button)
self.skew_button.clicked.connect(self.on_skew_button)
self.cal_button.clicked.connect(self.on_cal_button_click) self.cal_button.clicked.connect(self.on_cal_button_click)
self.reset_button.clicked.connect(self.set_tool_ui)
def run(self, toggle=True): def run(self, toggle=True):
self.app.report_usage("ToolCalibration()") self.app.report_usage("ToolCalibration()")
@ -685,7 +759,7 @@ class ToolCalibration(FlatCAMTool):
self.set_tool_ui() self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Calibrate Tool")) self.app.ui.notebook.setTabText(2, _("Calibration Tool"))
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs)
@ -703,6 +777,9 @@ class ToolCalibration(FlatCAMTool):
self.verz_entry.set_value(self.app.defaults['tools_cal_verz']) self.verz_entry.set_value(self.app.defaults['tools_cal_verz'])
self.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz']) self.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz'])
self.toolchangez_entry.set_value(self.app.defaults['tools_cal_toolchangez']) self.toolchangez_entry.set_value(self.app.defaults['tools_cal_toolchangez'])
self.toolchange_xy_entry.set_value(self.app.defaults['tools_cal_toolchange_xy'])
self.second_point_radio.set_value(self.app.defaults['tools_cal_sec_point'])
self.scalex_entry.set_value(1.0) self.scalex_entry.set_value(1.0)
self.scaley_entry.set_value(1.0) self.scaley_entry.set_value(1.0)
@ -743,7 +820,7 @@ class ToolCalibration(FlatCAMTool):
model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex()) model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
try: try:
self.target_obj = model_index.internalPointer().obj self.target_obj = model_index.internalPointer().obj
except Exception: except AttributeError:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no source FlatCAM object selected...")) self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no source FlatCAM object selected..."))
return return
@ -768,60 +845,75 @@ class ToolCalibration(FlatCAMTool):
self.app.inform.emit(_("Get First calibration point. Bottom Left...")) self.app.inform.emit(_("Get First calibration point. Bottom Left..."))
def on_mouse_click_release(self, event): def on_mouse_click_release(self, event):
if event.button == 1: if self.app.is_legacy is False:
if self.app.is_legacy is False: event_pos = event.pos
event_pos = event.pos right_button = 2
else: self.app.event_is_dragging = self.app.event_is_dragging
event_pos = (event.xdata, event.ydata) else:
event_pos = (event.xdata, event.ydata)
right_button = 3
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
pos_canvas = self.canvas.translate_coords(event_pos) pos_canvas = self.canvas.translate_coords(event_pos)
if event.button == 1:
click_pt = Point([pos_canvas[0], pos_canvas[1]]) click_pt = Point([pos_canvas[0], pos_canvas[1]])
if self.cal_source_radio.get_value() == 'object': if self.app.selection_type is not None:
if self.target_obj.kind.lower() == 'excellon': # delete previous selection shape
for tool, tool_dict in self.target_obj.tools.items(): self.app.delete_selection_shape()
for geo in tool_dict['solid_geometry']: self.app.selection_type = None
if click_pt.within(geo):
center_pt = geo.centroid
self.click_points.append(
(
float('%.*f' % (self.decimals, center_pt.x)),
float('%.*f' % (self.decimals, center_pt.y))
)
)
self.check_points()
else:
for apid, apid_val in self.target_obj.apertures.items():
for geo_el in apid_val['geometry']:
if 'solid' in geo_el:
if click_pt.within(geo_el['solid']):
if isinstance(geo_el['follow'], Point):
center_pt = geo_el['solid'].centroid
self.click_points.append(
(
float('%.*f' % (self.decimals, center_pt.x)),
float('%.*f' % (self.decimals, center_pt.y))
)
)
self.check_points()
else: else:
self.click_points.append( if self.cal_source_radio.get_value() == 'object':
( if self.target_obj.kind.lower() == 'excellon':
float('%.*f' % (self.decimals, click_pt.x)), for tool, tool_dict in self.target_obj.tools.items():
float('%.*f' % (self.decimals, click_pt.y)) for geo in tool_dict['solid_geometry']:
if click_pt.within(geo):
center_pt = geo.centroid
self.click_points.append(
[
float('%.*f' % (self.decimals, center_pt.x)),
float('%.*f' % (self.decimals, center_pt.y))
]
)
self.check_points()
else:
for apid, apid_val in self.target_obj.apertures.items():
for geo_el in apid_val['geometry']:
if 'solid' in geo_el:
if click_pt.within(geo_el['solid']):
if isinstance(geo_el['follow'], Point):
center_pt = geo_el['solid'].centroid
self.click_points.append(
[
float('%.*f' % (self.decimals, center_pt.x)),
float('%.*f' % (self.decimals, center_pt.y))
]
)
self.check_points()
else:
self.click_points.append(
[
float('%.*f' % (self.decimals, click_pt.x)),
float('%.*f' % (self.decimals, click_pt.y))
]
) )
) self.check_points()
self.check_points() elif event.button == right_button and self.app.event_is_dragging is False:
if len(self.click_points) != 4:
self.reset_calibration_points()
self.disconnect_cal_events()
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
def check_points(self): def check_points(self):
if len(self.click_points) == 1: if len(self.click_points) == 1:
self.bottom_left_coordx_tgt.set_value(self.click_points[0][0]) self.bottom_left_coordx_tgt.set_value(self.click_points[0][0])
self.bottom_left_coordy_tgt.set_value(self.click_points[0][1]) self.bottom_left_coordy_tgt.set_value(self.click_points[0][1])
self.app.inform.emit(_("Get Second calibration point. Bottom Right...")) self.app.inform.emit(_("Get Second calibration point. Bottom Right (Top Left)..."))
elif len(self.click_points) == 2: elif len(self.click_points) == 2:
self.bottom_right_coordx_tgt.set_value(self.click_points[1][0]) self.bottom_right_coordx_tgt.set_value(self.click_points[1][0])
self.bottom_right_coordy_tgt.set_value(self.click_points[1][1]) self.bottom_right_coordy_tgt.set_value(self.click_points[1][1])
self.app.inform.emit(_("Get Third calibration point. Top Left...")) self.app.inform.emit(_("Get Third calibration point. Top Left (Bottom Right)..."))
elif len(self.click_points) == 3: elif len(self.click_points) == 3:
self.top_left_coordx_tgt.set_value(self.click_points[2][0]) self.top_left_coordx_tgt.set_value(self.click_points[2][0])
self.top_left_coordy_tgt.set_value(self.click_points[2][1]) self.top_left_coordy_tgt.set_value(self.click_points[2][1])
@ -847,6 +939,12 @@ class ToolCalibration(FlatCAMTool):
self.top_right_coordx_tgt.set_value('') self.top_right_coordx_tgt.set_value('')
self.top_right_coordy_tgt.set_value('') self.top_right_coordy_tgt.set_value('')
self.bottom_right_coordx_found.set_value('')
self.bottom_right_coordy_found.set_value('')
self.top_left_coordx_found.set_value('')
self.top_left_coordy_found.set_value('')
def gcode_header(self): def gcode_header(self):
log.debug("ToolCalibration.gcode_header()") log.debug("ToolCalibration.gcode_header()")
time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
@ -854,10 +952,10 @@ class ToolCalibration(FlatCAMTool):
gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
(str(self.app.version), str(self.app.version_date)) + '\n' (str(self.app.version), str(self.app.version_date)) + '\n'
gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibrate Tool') + ')\n' gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibration Tool') + ')\n'
gcode += '(Units: ' + self.units.upper() + ')\n' + "\n" gcode += '(Units: ' + self.units.upper() + ')\n\n'
gcode += '(Created on ' + time_str + ')\n' + '\n' gcode += '(Created on ' + time_str + ')\n\n'
gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n' gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
gcode += 'G90\n' gcode += 'G90\n'
gcode += 'G17\n' gcode += 'G17\n'
@ -872,9 +970,13 @@ class ToolCalibration(FlatCAMTool):
self.app.ui.plot_tab_area.removeTab(idx) self.app.ui.plot_tab_area.removeTab(idx)
def generate_verification_gcode(self): def generate_verification_gcode(self):
sec_point = self.second_point_radio.get_value()
travel_z = '%.*f' % (self.decimals, self.travelz_entry.get_value()) travel_z = '%.*f' % (self.decimals, self.travelz_entry.get_value())
toolchange_z = '%.*f' % (self.decimals, self.toolchangez_entry.get_value()) toolchange_z = '%.*f' % (self.decimals, self.toolchangez_entry.get_value())
toolchange_xy_temp = self.toolchange_xy_entry.get_value().split(",")
toolchange_xy = [float(eval(a)) for a in toolchange_xy_temp if a != '']
verification_z = '%.*f' % (self.decimals, self.verz_entry.get_value()) verification_z = '%.*f' % (self.decimals, self.verz_entry.get_value())
if len(self.click_points) != 4: if len(self.click_points) != 4:
@ -884,36 +986,64 @@ class ToolCalibration(FlatCAMTool):
gcode = self.gcode_header() gcode = self.gcode_header()
if self.zeroz_cb.get_value(): if self.zeroz_cb.get_value():
gcode += 'M5\n' gcode += 'M5\n'
gcode += f'G00 Z{toolchange_z}\n' gcode += 'G00 Z%s\n' % toolchange_z
if toolchange_xy:
gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
gcode += 'M0\n' gcode += 'M0\n'
gcode += 'G01 Z0\n' gcode += 'G01 Z0\n'
gcode += 'M0\n' gcode += 'M0\n'
gcode += f'G00 Z{toolchange_z}\n' gcode += 'G00 Z%s\n' % toolchange_z
gcode += 'M0\n' gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n' # first point: bottom - left -> ORIGIN set
gcode += f'G00 X{self.click_points[0][0]} Y{self.click_points[0][1]}\n' gcode += 'G00 Z%s\n' % travel_z
gcode += f'G01 Z{verification_z}\n' gcode += 'G00 X%s Y%s\n' % (self.click_points[0][0], self.click_points[0][1])
gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n' gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n' if sec_point == 'tl':
gcode += f'G00 X{self.click_points[2][0]} Y{self.click_points[2][1]}\n' # second point: top - left -> align the PCB to this point
gcode += f'G01 Z{verification_z}\n' gcode += 'G00 Z%s\n' % travel_z
gcode += 'M0\n' gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n' # third point: bottom - right -> check for scale on X axis or for skew on Y axis
gcode += f'G00 X{self.click_points[3][0]} Y{self.click_points[3][1]}\n' gcode += 'G00 Z%s\n' % travel_z
gcode += f'G01 Z{verification_z}\n' gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
gcode += 'M0\n' gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n' # forth point: top - right -> verification point
gcode += f'G00 X{self.click_points[1][0]} Y{self.click_points[1][1]}\n' gcode += 'G00 Z%s\n' % travel_z
gcode += f'G01 Z{verification_z}\n' gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
gcode += 'M0\n' gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
else:
# second point: bottom - right -> align the PCB to this point
gcode += 'G00 Z%s\n' % travel_z
gcode += 'G00 X%s Y%s\n' % (self.click_points[1][0], self.click_points[1][1])
gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n' # third point: top - left -> check for scale on Y axis or for skew on X axis
gcode += f'G00 X0 Y0\n' gcode += 'G00 Z%s\n' % travel_z
gcode += f'G00 Z{toolchange_z}\n' gcode += 'G00 X%s Y%s\n' % (self.click_points[2][0], self.click_points[2][1])
gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
# forth point: top - right -> verification point
gcode += 'G00 Z%s\n' % travel_z
gcode += 'G00 X%s Y%s\n' % (self.click_points[3][0], self.click_points[3][1])
gcode += 'G01 Z%s\n' % verification_z
gcode += 'M0\n'
# return to (toolchange_xy[0], toolchange_xy[1], toolchange_z) point for toolchange event
gcode += 'G00 Z%s\n' % travel_z
gcode += 'G00 X0 Y0\n'
gcode += 'G00 Z%s\n' % toolchange_z
if toolchange_xy:
gcode += 'G00 X%s Y%s\n' % (toolchange_xy[0], toolchange_xy[1])
gcode += 'M2' gcode += 'M2'
@ -961,74 +1091,142 @@ class ToolCalibration(FlatCAMTool):
origin_x = self.click_points[0][0] origin_x = self.click_points[0][0]
origin_y = self.click_points[0][1] origin_y = self.click_points[0][1]
top_left_x = float('%.*f' % (self.decimals, self.click_points[2][0])) top_left_x = self.click_points[2][0]
top_left_y = float('%.*f' % (self.decimals, self.click_points[2][1])) top_left_y = self.click_points[2][1]
bot_right_x = self.click_points[1][0]
bot_right_y = self.click_points[1][1]
try: try:
top_left_dx = float('%.*f' % (self.decimals, self.top_left_coordx_found.get_value())) top_left_dx = float(self.top_left_coordx_found.get_value())
except TypeError: except TypeError:
top_left_dx = top_left_x top_left_dx = top_left_x
try: try:
top_left_dy = float('%.*f' % (self.decimals, self.top_left_coordy_found.get_value())) top_left_dy = float(self.top_left_coordy_found.get_value())
except TypeError: except TypeError:
top_left_dy = top_left_y top_left_dy = top_left_y
# top_right_x = float('%.*f' % (self.decimals, self.click_points[3][0]))
# top_right_y = float('%.*f' % (self.decimals, self.click_points[3][1]))
# try:
# top_right_dx = float('%.*f' % (self.decimals, self.top_right_coordx_found.get_value()))
# except TypeError:
# top_right_dx = top_right_x
#
# try:
# top_right_dy = float('%.*f' % (self.decimals, self.top_right_coordy_found.get_value()))
# except TypeError:
# top_right_dy = top_right_y
bot_right_x = float('%.*f' % (self.decimals, self.click_points[1][0]))
bot_right_y = float('%.*f' % (self.decimals, self.click_points[1][1]))
try: try:
bot_right_dx = float('%.*f' % (self.decimals, self.bottom_right_coordx_found.get_value())) bot_right_dx = float(self.bottom_right_coordx_found.get_value())
except TypeError: except TypeError:
bot_right_dx = bot_right_x bot_right_dx = bot_right_x
try: try:
bot_right_dy = float('%.*f' % (self.decimals, self.bottom_right_coordy_found.get_value())) bot_right_dy = float(self.bottom_right_coordy_found.get_value())
except TypeError: except TypeError:
bot_right_dy = bot_right_y bot_right_dy = bot_right_y
# ------------------------------------------------------------------------------- # # ------------------------------------------------------------------------------- #
# --------------------------- FACTORS CALCULUS ---------------------------------- # # --------------------------- FACTORS CALCULUS ---------------------------------- #
# ------------------------------------------------------------------------------- # # ------------------------------------------------------------------------------- #
if top_left_dy != float('%.*f' % (self.decimals, 0.0)): if bot_right_dx != float('%.*f' % (self.decimals, bot_right_x)):
# we have scale on X
scale_x = (bot_right_dx / (bot_right_x - origin_x)) + 1
self.scalex_entry.set_value(scale_x)
if top_left_dy != float('%.*f' % (self.decimals, top_left_y)):
# we have scale on Y # we have scale on Y
scale_y = (top_left_dy + top_left_y - origin_y) / (top_left_y - origin_y) scale_y = (top_left_dy / (top_left_y - origin_y)) + 1
self.scaley_entry.set_value(scale_y) self.scaley_entry.set_value(scale_y)
if top_left_dx != float('%.*f' % (self.decimals, 0.0)): if top_left_dx != float('%.*f' % (self.decimals, top_left_x)):
# we have skew on X # we have skew on X
dx = top_left_dx dx = top_left_dx
dy = top_left_y - origin_y dy = top_left_y - origin_y
skew_angle_x = math.degrees(math.atan(dx / dy)) skew_angle_x = math.degrees(math.atan(dx / dy))
self.skewx_entry.set_value(skew_angle_x) self.skewx_entry.set_value(skew_angle_x)
if bot_right_dx != float('%.*f' % (self.decimals, 0.0)): if bot_right_dy != float('%.*f' % (self.decimals, bot_right_y)):
# we have scale on X
scale_x = (bot_right_dx + bot_right_x - origin_x) / (bot_right_x - origin_x)
self.scalex_entry.set_value(scale_x)
if bot_right_dy != float('%.*f' % (self.decimals, 0.0)):
# we have skew on Y # we have skew on Y
dx = bot_right_x - origin_x dx = bot_right_x - origin_x
dy = bot_right_dy + origin_y dy = bot_right_dy + origin_y
skew_angle_y = math.degrees(math.atan(dy / dx)) skew_angle_y = math.degrees(math.atan(dy / dx))
self.skewy_entry.set_value(skew_angle_y) self.skewy_entry.set_value(skew_angle_y)
@property
def target_values_in_table(self):
self.click_points[0][0] = self.bottom_left_coordx_tgt.get_value()
self.click_points[0][1] = self.bottom_left_coordy_tgt.get_value()
self.click_points[1][0] = self.bottom_right_coordx_tgt.get_value()
self.click_points[1][1] = self.bottom_right_coordy_tgt.get_value()
self.click_points[2][0] = self.top_left_coordx_tgt.get_value()
self.click_points[2][1] = self.top_left_coordy_tgt.get_value()
self.click_points[3][0] = self.top_right_coordx_tgt.get_value()
self.click_points[3][1] = self.top_right_coordy_tgt.get_value()
return self.click_points
@target_values_in_table.setter
def target_values_in_table(self, param):
bl_pt, br_pt, tl_pt, tr_pt = param
self.click_points[0] = [bl_pt[0], bl_pt[1]]
self.click_points[1] = [br_pt[0], br_pt[1]]
self.click_points[2] = [tl_pt[0], tl_pt[1]]
self.click_points[3] = [tr_pt[0], tr_pt[1]]
self.bottom_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[0])))
self.bottom_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, bl_pt[1])))
self.bottom_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, br_pt[0])))
self.bottom_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, br_pt[1])))
self.top_left_coordx_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[0])))
self.top_left_coordy_tgt.set_value(float('%.*f' % (self.decimals, tl_pt[1])))
self.top_right_coordx_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[0])))
self.top_right_coordy_tgt.set_value(float('%.*f' % (self.decimals, tr_pt[1])))
def on_scale_button(self):
scalex_fact = self.scalex_entry.get_value()
scaley_fact = self.scaley_entry.get_value()
bl, br, tl, tr = self.target_values_in_table
bl_geo = Point(bl[0], bl[1])
br_geo = Point(br[0], br[1])
tl_geo = Point(tl[0], tl[1])
tr_geo = Point(tr[0], tr[1])
bl_scaled = scale(bl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
br_scaled = scale(br_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
tl_scaled = scale(tl_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
tr_scaled = scale(tr_geo, xfact=scalex_fact, yfact=scaley_fact, origin=(bl[0], bl[1]))
scaled_values = [
[bl_scaled.x, bl_scaled.y],
[br_scaled.x, br_scaled.y],
[tl_scaled.x, tl_scaled.y],
[tr_scaled.x, tr_scaled.y]
]
self.target_values_in_table = scaled_values
def on_skew_button(self):
skewx_angle = self.skewx_entry.get_value()
skewy_angle = self.skewy_entry.get_value()
bl, br, tl, tr = self.target_values_in_table
bl_geo = Point(bl[0], bl[1])
br_geo = Point(br[0], br[1])
tl_geo = Point(tl[0], tl[1])
tr_geo = Point(tr[0], tr[1])
bl_skewed = skew(bl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
br_skewed = skew(br_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
tl_skewed = skew(tl_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
tr_skewed = skew(tr_geo, xs=skewx_angle, ys=skewy_angle, origin=(bl[0], bl[1]))
skewed_values = [
[bl_skewed.x, bl_skewed.y],
[br_skewed.x, br_skewed.y],
[tl_skewed.x, tl_skewed.y],
[tr_skewed.x, tr_skewed.y]
]
self.target_values_in_table = skewed_values
def on_cal_button_click(self): def on_cal_button_click(self):
# get the FlatCAM object to calibrate # get the FlatCAM object to calibrate
selection_index = self.adj_object_combo.currentIndex() selection_index = self.adj_object_combo.currentIndex()
@ -1076,8 +1274,8 @@ class ToolCalibration(FlatCAMTool):
try: try:
if obj.tools: if obj.tools:
obj_init.tools = deepcopy(obj.tools) obj_init.tools = deepcopy(obj.tools)
except Exception as e: except Exception as ee:
log.debug("App.on_copy_object() --> %s" % str(e)) log.debug("App.on_copy_object() --> %s" % str(ee))
obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y)) obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y)) obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
@ -1102,8 +1300,8 @@ class ToolCalibration(FlatCAMTool):
try: try:
if obj.tools: if obj.tools:
obj_init.tools = deepcopy(obj.tools) obj_init.tools = deepcopy(obj.tools)
except Exception as e: except Exception as err:
log.debug("App.on_copy_object() --> %s" % str(e)) log.debug("App.on_copy_object() --> %s" % str(err))
obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y)) obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y)) obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))

View File

@ -128,7 +128,7 @@ class ToolCopperThieving(FlatCAMTool):
], orientation='vertical', stretch=False) ], orientation='vertical', stretch=False)
self.reference_label = QtWidgets.QLabel(_("Reference:")) self.reference_label = QtWidgets.QLabel(_("Reference:"))
self.reference_label.setToolTip( self.reference_label.setToolTip(
_("- 'Itself' - the copper thieving extent is based on the object that is copper cleared.\n" _("- 'Itself' - the copper thieving extent is based on the object extent.\n"
"- 'Area Selection' - left mouse click to start selection of the area to be filled.\n" "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
"- 'Reference Object' - will do copper thieving within the area specified by another object.") "- 'Reference Object' - will do copper thieving within the area specified by another object.")
) )

View File

@ -153,7 +153,7 @@ class DblSidedTool(FlatCAMTool):
# ## Axis Location # ## Axis Location
self.axis_location = RadioSet([{'label': _('Point'), 'value': 'point'}, self.axis_location = RadioSet([{'label': _('Point'), 'value': 'point'},
{'label': _('Box'), 'value': 'box'}]) {'label': _('Box'), 'value': 'box'}])
self.axloc_label = QtWidgets.QLabel(_("Axis Ref:")) self.axloc_label = QtWidgets.QLabel('%s:' % _("Axis Ref"))
self.axloc_label.setToolTip( self.axloc_label.setToolTip(
_("The axis should pass through a <b>point</b> or cut\n " _("The axis should pass through a <b>point</b> or cut\n "
"a specified <b>box</b> (in a FlatCAM object) through \n" "a specified <b>box</b> (in a FlatCAM object) through \n"

View File

@ -349,7 +349,10 @@ class Distance(FlatCAMTool):
d = math.sqrt(dx ** 2 + dy ** 2) d = math.sqrt(dx ** 2 + dy ** 2)
self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format( self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | (tx3} = {d_z}".format(
tx1=_("MEASURING"),
tx2=_("Result"),
tx3=_("Distance"),
d_x='%*f' % (self.decimals, abs(dx)), d_x='%*f' % (self.decimals, abs(dx)),
d_y='%*f' % (self.decimals, abs(dy)), d_y='%*f' % (self.decimals, abs(dy)),
d_z='%*f' % (self.decimals, abs(d))) d_z='%*f' % (self.decimals, abs(d)))

View File

@ -278,7 +278,10 @@ class DistanceMin(FlatCAMTool):
) )
if d != 0: if d != 0:
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format( self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | (tx3} = {d_z}".format(
tx1=_("MEASURING"),
tx2=_("Result"),
tx3=_("Distance"),
d_x='%*f' % (self.decimals, abs(dx)), d_x='%*f' % (self.decimals, abs(dx)),
d_y='%*f' % (self.decimals, abs(dy)), d_y='%*f' % (self.decimals, abs(dy)),
d_z='%*f' % (self.decimals, abs(d))) d_z='%*f' % (self.decimals, abs(d)))

View File

@ -203,7 +203,7 @@ class ToolFiducials(FlatCAMTool):
self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial")) self.pos_label = QtWidgets.QLabel('%s:' % _("Second fiducial"))
self.pos_label.setToolTip( self.pos_label.setToolTip(
_("The position for the second fiducial.\n" _("The position for the second fiducial.\n"
"- 'Up' - the order is: bottom-left, top-left, top-right.\n " "- 'Up' - the order is: bottom-left, top-left, top-right.\n"
"- 'Down' - the order is: bottom-left, bottom-right, top-right.\n" "- 'Down' - the order is: bottom-left, bottom-right, top-right.\n"
"- 'None' - there is no second fiducial. The order is: bottom-left, top-right.") "- 'None' - there is no second fiducial. The order is: bottom-left, top-right.")
) )

View File

@ -19,7 +19,7 @@ from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.graphics import renderPM from reportlab.graphics import renderPM
from reportlab.lib.units import inch, mm from reportlab.lib.units import inch, mm
from reportlab.lib.pagesizes import landscape, portrait, A4 from reportlab.lib.pagesizes import landscape, portrait
from svglib.svglib import svg2rlg from svglib.svglib import svg2rlg
from xml.dom.minidom import parseString as parse_xml_string from xml.dom.minidom import parseString as parse_xml_string
@ -669,6 +669,10 @@ class Film(FlatCAMTool):
_("No FlatCAM object selected. Load an object for Box and retry.")) _("No FlatCAM object selected. Load an object for Box and retry."))
return return
if name == '' or boxname == '':
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No FlatCAM object selected."))
return
scale_stroke_width = float(self.film_scale_stroke_entry.get_value()) scale_stroke_width = float(self.film_scale_stroke_entry.get_value())
source = self.source_punch.get_value() source = self.source_punch.get_value()
file_type = self.file_type_radio.get_value() file_type = self.file_type_radio.get_value()
@ -738,12 +742,18 @@ class Film(FlatCAMTool):
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export positive film cancelled.")) self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export positive film cancelled."))
return return
else: else:
pagesize = self.pagesize_combo.get_value()
orientation = self.orientation_radio.get_value()
color = self.app.defaults['tools_film_color']
self.export_positive(name, boxname, filename, self.export_positive(name, boxname, filename,
scale_stroke_factor=factor, scale_stroke_factor=factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y, skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
skew_reference=skew_reference, skew_reference=skew_reference,
mirror=mirror, ftype=ftype mirror=mirror,
pagesize=pagesize, orientation=orientation, color=color, opacity=1.0,
ftype=ftype
) )
def generate_positive_punched_film(self, name, boxname, source, factor, ftype='svg'): def generate_positive_punched_film(self, name, boxname, source, factor, ftype='svg'):
@ -1068,7 +1078,7 @@ class Film(FlatCAMTool):
scale_stroke_factor=0.00, scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None, scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None, skew_reference='center', skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None, mirror=None, orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
use_thread=True, ftype='svg'): use_thread=True, ftype='svg'):
""" """
Exports a Geometry Object to an SVG file in positive black. Exports a Geometry Object to an SVG file in positive black.
@ -1112,7 +1122,12 @@ class Film(FlatCAMTool):
self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj)) self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
box = obj box = obj
def make_positive_film(): p_size = pagesize_val
orientation = orientation_val
color = color_val
transparency_level = opacity_val
def make_positive_film(p_size, orientation, color, transparency_level):
log.debug("FilmTool.export_positive().make_positive_film()") log.debug("FilmTool.export_positive().make_positive_film()")
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor, exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
@ -1127,9 +1142,9 @@ class Film(FlatCAMTool):
# We set the colour to WHITE # We set the colour to WHITE
root = ET.fromstring(exported_svg) root = ET.fromstring(exported_svg)
for child in root: for child in root:
child.set('fill', str(self.app.defaults['tools_film_color'])) child.set('fill', str(color))
child.set('opacity', '1.0') child.set('opacity', str(transparency_level))
child.set('stroke', str(self.app.defaults['tools_film_color'])) child.set('stroke', str(color))
exported_svg = ET.tostring(root) exported_svg = ET.tostring(root)
@ -1190,7 +1205,7 @@ class Film(FlatCAMTool):
return 'fail' return 'fail'
else: else:
try: try:
if self.units == 'INCH': if self.units == 'IN':
unit = inch unit = inch
else: else:
unit = mm unit = mm
@ -1198,11 +1213,10 @@ class Film(FlatCAMTool):
doc_final = StringIO(doc_final) doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final) drawing = svg2rlg(doc_final)
p_size = self.pagesize_combo.get_value()
if p_size == 'Bounds': if p_size == 'Bounds':
renderPDF.drawToFile(drawing, filename) renderPDF.drawToFile(drawing, filename)
else: else:
if self.orientation_radio.get_value() == 'p': if orientation == 'p':
page_size = portrait(self.pagesize[p_size]) page_size = portrait(self.pagesize[p_size])
else: else:
page_size = landscape(self.pagesize[p_size]) page_size = landscape(self.pagesize[p_size])
@ -1225,7 +1239,8 @@ class Film(FlatCAMTool):
def job_thread_film(app_obj): def job_thread_film(app_obj):
try: try:
make_positive_film() make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
except Exception: except Exception:
proc.done() proc.done()
return return
@ -1233,7 +1248,8 @@ class Film(FlatCAMTool):
self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]}) self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else: else:
make_positive_film() make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
def reset_fields(self): def reset_fields(self):
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -681,6 +681,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"], "depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"], "toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"], "endz": self.app.defaults["geometry_endz"],

View File

@ -440,6 +440,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"], "depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"], "toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"], "endz": self.app.defaults["geometry_endz"],
@ -633,6 +634,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": float(self.app.defaults["geometry_depthperpass"]), "depthperpass": float(self.app.defaults["geometry_depthperpass"]),
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": float(self.app.defaults["geometry_toolchangez"]), "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
"endz": float(self.app.defaults["geometry_endz"]), "endz": float(self.app.defaults["geometry_endz"]),

View File

@ -1283,8 +1283,7 @@ class SolderPaste(FlatCAMTool):
obj = self.app.collection.get_by_name(name) obj = self.app.collection.get_by_name(name)
if name == '': if name == '':
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object available."))
_("There is no Geometry object available."))
return 'fail' return 'fail'
if obj.special_group != 'solder_paste_tool': if obj.special_group != 'solder_paste_tool':
@ -1298,8 +1297,7 @@ class SolderPaste(FlatCAMTool):
if obj.tools[tooluid_key]['solid_geometry'] is None: if obj.tools[tooluid_key]['solid_geometry'] is None:
a += 1 a += 1
if a == len(obj.tools): if a == len(obj.tools):
self.app.inform.emit('[ERROR_NOTCL] %s...' % self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry'))
_('Cancelled. Empty file, it has no geometry'))
return 'fail' return 'fail'
# use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia

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

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

View File

@ -1,11 +1,11 @@
# This file contains python only requirements to be installed with pip # This file contains python only requirements to be installed with pip
# Python packages that cannot be installed with pip (e.g. PyQt5, GDAL) are not included. # Python packages that cannot be installed with pip (e.g. PyQt5, GDAL) are not included.
# Usage: pip3 install -r requirements.txt # Usage: pip3 install -r requirements.txt
numpy>=1.16 numpy >=1.16
matplotlib>=3.1 matplotlib>=3.1
cycler>=0.10 cycler>=0.10
python-dateutil>=2.1 python-dateutil>=2.1
kiwisolver>=1.0.1 kiwisolver>=1.1
six six
setuptools setuptools
dill dill
@ -21,6 +21,6 @@ fontTools
rasterio rasterio
lxml lxml
ezdxf ezdxf
qrcode>=6.0 qrcode>=6.1
reportlab>=3.0 reportlab>=3.5
svglib svglib

BIN
share/calibrate_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

BIN
share/calibrate_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

View File

@ -35,6 +35,7 @@ class TclCommandCncjob(TclCommandSignaled):
('feedrate_rapid', float), ('feedrate_rapid', float),
('multidepth', bool), ('multidepth', bool),
('extracut', bool), ('extracut', bool),
('extracut_length', float),
('depthperpass', float), ('depthperpass', float),
('toolchange', int), ('toolchange', int),
('toolchangez', float), ('toolchangez', float),
@ -65,6 +66,7 @@ class TclCommandCncjob(TclCommandSignaled):
('feedrate_rapid', 'Rapid moving at speed when cutting.'), ('feedrate_rapid', 'Rapid moving at speed when cutting.'),
('multidepth', 'Use or not multidepth cnc cut. (True or False)'), ('multidepth', 'Use or not multidepth cnc cut. (True or False)'),
('extracut', 'Use or not an extra cnccut over the first point in path,in the job end (example: True)'), ('extracut', 'Use or not an extra cnccut over the first point in path,in the job end (example: True)'),
('extracut', 'The value for extra cnccut over the first point in path,in the job end; float'),
('depthperpass', 'Height of one layer for multidepth.'), ('depthperpass', 'Height of one layer for multidepth.'),
('toolchange', 'Enable tool changes (example: True).'), ('toolchange', 'Enable tool changes (example: True).'),
('toolchangez', 'Z distance for toolchange (example: 30.0).'), ('toolchangez', 'Z distance for toolchange (example: 30.0).'),
@ -136,6 +138,8 @@ class TclCommandCncjob(TclCommandSignaled):
args["multidepth"] = bool(args["multidepth"]) if "multidepth" in args else obj.options["multidepth"] args["multidepth"] = bool(args["multidepth"]) if "multidepth" in args else obj.options["multidepth"]
args["extracut"] = bool(args["extracut"]) if "extracut" in args else obj.options["extracut"] args["extracut"] = bool(args["extracut"]) if "extracut" in args else obj.options["extracut"]
args["extracut_length"] = float(args["extracut_length"]) if "extracut_length" in args else \
obj.options["extracut_length"]
args["depthperpass"] = args["depthperpass"] if "depthperpass" in args and args["depthperpass"] else \ args["depthperpass"] = args["depthperpass"] if "depthperpass" in args and args["depthperpass"] else \
obj.options["depthperpass"] obj.options["depthperpass"]
@ -143,7 +147,7 @@ class TclCommandCncjob(TclCommandSignaled):
self.app.defaults["geometry_startz"] self.app.defaults["geometry_startz"]
args["endz"] = args["endz"] if "endz" in args and args["endz"] else obj.options["endz"] args["endz"] = args["endz"] if "endz" in args and args["endz"] else obj.options["endz"]
args["spindlespeed"] = args["spindlespeed"] if "spindlespeed" in args and args["spindlespeed"] else None args["spindlespeed"] = args["spindlespeed"] if "spindlespeed" in args and args["spindlespeed"] != 0 else None
args["dwell"] = bool(args["dwell"]) if "dwell" in args else obj.options["dwell"] args["dwell"] = bool(args["dwell"]) if "dwell" in args else obj.options["dwell"]
args["dwelltime"] = args["dwelltime"] if "dwelltime" in args and args["dwelltime"] else obj.options["dwelltime"] args["dwelltime"] = args["dwelltime"] if "dwelltime" in args and args["dwelltime"] else obj.options["dwelltime"]
@ -189,6 +193,7 @@ class TclCommandCncjob(TclCommandSignaled):
local_tools_dict[tool_uid]['data']['feedrate_rapid'] = args["feedrate_rapid"] local_tools_dict[tool_uid]['data']['feedrate_rapid'] = args["feedrate_rapid"]
local_tools_dict[tool_uid]['data']['multidepth'] = args["multidepth"] local_tools_dict[tool_uid]['data']['multidepth'] = args["multidepth"]
local_tools_dict[tool_uid]['data']['extracut'] = args["extracut"] local_tools_dict[tool_uid]['data']['extracut'] = args["extracut"]
local_tools_dict[tool_uid]['data']['extracut_length'] = args["extracut_length"]
local_tools_dict[tool_uid]['data']['depthperpass'] = args["depthperpass"] local_tools_dict[tool_uid]['data']['depthperpass'] = args["depthperpass"]
local_tools_dict[tool_uid]['data']['toolchange'] = args["toolchange"] local_tools_dict[tool_uid]['data']['toolchange'] = args["toolchange"]
local_tools_dict[tool_uid]['data']['toolchangez'] = args["toolchangez"] local_tools_dict[tool_uid]['data']['toolchangez'] = args["toolchangez"]

View File

@ -170,6 +170,7 @@ class TclCommandCopperClear(TclCommand):
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"], "depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"], "toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"], "endz": self.app.defaults["geometry_endz"],

View File

@ -159,6 +159,7 @@ class TclCommandPaint(TclCommand):
"ppname_g": self.app.defaults["geometry_ppname_g"], "ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": self.app.defaults["geometry_depthperpass"], "depthperpass": self.app.defaults["geometry_depthperpass"],
"extracut": self.app.defaults["geometry_extracut"], "extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"], "toolchange": self.app.defaults["geometry_toolchange"],
"toolchangez": self.app.defaults["geometry_toolchangez"], "toolchangez": self.app.defaults["geometry_toolchangez"],
"endz": self.app.defaults["geometry_endz"], "endz": self.app.defaults["geometry_endz"],