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 ctypes
import tkinter as tk
from PyQt5 import QtPrintSupport
# import tkinter as tk
# 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
import gc
@ -57,6 +63,8 @@ from flatcamEditors.FlatCAMExcEditor import FlatCAMExcEditor
from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor
from flatcamEditors.FlatCAMTextEditor import TextEditor
from flatcamParsers.ParseHPGL2 import HPGL2
from FlatCAMProcess import *
from FlatCAMWorkerStack import WorkerStack
# from flatcamGUI.VisPyVisuals import Color
@ -133,7 +141,7 @@ class App(QtCore.QObject):
# ################## Version and VERSION DATE ##############################
# ##########################################################################
version = 8.99
version_date = "2019/12/12"
version_date = "2019/12/15"
beta = True
engine = '3D'
@ -591,7 +599,7 @@ class App(QtCore.QObject):
"excellon_travelz": 2,
"excellon_endz": 0.5,
"excellon_feedrate": 300,
"excellon_spindlespeed": None,
"excellon_spindlespeed": 0,
"excellon_dwell": False,
"excellon_dwelltime": 1,
"excellon_toolchange": False,
@ -658,7 +666,7 @@ class App(QtCore.QObject):
"geometry_endz": 15.0,
"geometry_feedrate": 120,
"geometry_feedrate_z": 60,
"geometry_spindlespeed": None,
"geometry_spindlespeed": 0,
"geometry_dwell": False,
"geometry_dwelltime": 1,
"geometry_ppname_g": 'default',
@ -668,6 +676,7 @@ class App(QtCore.QObject):
"geometry_startz": None,
"geometry_feedrate_rapid": 1500,
"geometry_extracut": False,
"geometry_extracut_length": 0.1,
"geometry_z_pdepth": -0.02,
"geometry_f_plunge": False,
"geometry_spindledir": 'CW',
@ -901,12 +910,14 @@ class App(QtCore.QObject):
"tools_cal_verz": 0.1,
"tools_cal_zeroz": False,
"tools_cal_toolchangez": 15,
"tools_cal_toolchange_xy": '',
"tools_cal_sec_point": 'tl',
# Utilities
# file associations
"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, '
'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, '
'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',
@ -1260,6 +1271,7 @@ class App(QtCore.QObject):
"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_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_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,
@ -1482,6 +1494,8 @@ class App(QtCore.QObject):
"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_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
# File associations
@ -1759,7 +1773,7 @@ class App(QtCore.QObject):
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.menufileimport_hpgl2_as_geo.triggered.connect(self.on_fileopenhpgl2)
self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng)
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.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
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.menufileexportpref.triggered.connect(self.on_export_preferences)
@ -2476,6 +2491,7 @@ class App(QtCore.QObject):
# variable to store coordinates
self.pos = (0, 0)
self.pos_canvas = (0, 0)
self.pos_jump = (0, 0)
# variable to store mouse coordinates
@ -2545,7 +2561,7 @@ class App(QtCore.QObject):
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',
'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']
self.svg_list = ['svg']
self.dxf_list = ['dxf']
@ -2979,7 +2995,7 @@ class App(QtCore.QObject):
self.dblsidedtool.install(icon=QtGui.QIcon('share/doubleside16.png'), separator=True)
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,
separator=False)
self.distance_tool = Distance(self)
@ -3148,6 +3164,7 @@ class App(QtCore.QObject):
# Tools Toolbar Signals
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.ncc_btn.triggered.connect(lambda: self.ncclear_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, " \
"AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\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, " \
"ListSys, MillDrills,\n"\
"# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
@ -4297,23 +4314,41 @@ class App(QtCore.QObject):
# self.inform.emit('[selected] %s created & selected: %s' %
# (str(obj.kind).capitalize(), str(obj.options['name'])))
if obj.kind == 'gerber':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='green', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='green',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'excellon':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='brown', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='brown',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'cncjob':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='blue', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='blue',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'geometry':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='red', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='red',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'script':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='orange', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='orange',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'document':
self.inform.emit(_('[selected] {kind} created/selected: <span style="color:{color};">{name}</span>').format(
kind=obj.kind.capitalize(), color='darkCyan', name=str(obj.options['name'])))
self.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
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
self.shell._edit.set_model_data(self.myKeywords)
@ -4468,10 +4503,11 @@ class App(QtCore.QObject):
attributions_label = QtWidgets.QLabel(
_(
'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="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))
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])
else:
# 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
# canvas) and the point (width, height) is in the top-right 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])
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
self.app_cursor.set_data(np.asarray([(location[0], location[1])]),
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>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
self.inform.emit('[success] %s' %
_("Done."))
self.inform.emit('[success] %s' % _("Done."))
return location
def on_copy_object(self):
@ -8431,23 +8475,17 @@ class App(QtCore.QObject):
was clicked, the pixel coordinates and the axes coordinates.
:return: None
"""
self.pos = []
self.pos = list()
if self.is_legacy is False:
event_pos = event.pos
if self.defaults["global_pan_button"] == '2':
pan_button = 2
else:
pan_button = 3
pan_button = 2 if self.defaults["global_pan_button"] == '2'else 3
# Set the mouse button for panning
self.plotcanvas.view.camera.pan_button_setting = pan_button
else:
event_pos = (event.xdata, event.ydata)
# Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
if self.defaults["global_pan_button"] == '2':
pan_button = 3
else:
pan_button = 2
pan_button = 3 if self.defaults["global_pan_button"] == '2'else 2
# So it can receive key presses
self.plotcanvas.native.setFocus()
@ -8836,17 +8874,29 @@ class App(QtCore.QObject):
def selected_message(self, curr_sel_obj):
if curr_sel_obj:
if curr_sel_obj.kind == 'gerber':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='green', name=str(curr_sel_obj.options['name'])))
self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='green',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'excellon':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='brown', name=str(curr_sel_obj.options['name'])))
self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='brown',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'cncjob':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='blue', name=str(curr_sel_obj.options['name'])))
self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='blue',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
elif curr_sel_obj.kind == 'geometry':
self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='red', name=str(curr_sel_obj.options['name'])))
self.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='red',
name=str(curr_sel_obj.options['name']),
tx=_("selected"))
)
def delete_hover_shape(self):
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
_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 (*.*)"
if name is None:
@ -9277,6 +9327,44 @@ class App(QtCore.QObject):
# thread safe. The new_project()
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):
"""
File menu callback for opening a config file.
@ -10108,23 +10196,25 @@ class App(QtCore.QObject):
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Save Project As ..."),
directory=_('{l_save}/Project_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date),
filter=filter_)
directory=('{l_save}/{proj}_{date}').format(l_save=str(self.get_last_save_folder()), date=self.date,
proj=_("Project")),
filter=filter_
)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Project As ..."), filter=filter_)
filename = str(filename)
if filename == '':
self.inform.emit('[WARNING_NOTCL] %s' %
_("Save Project cancelled."))
self.inform.emit('[WARNING_NOTCL] %s' % _("Save Project cancelled."))
return
try:
f = open(filename, 'r')
f.close()
except IOError:
pass
self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application."))
return
if use_thread is True:
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.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):
"""
Exports a Geometry Object to an SVG file.
@ -10870,6 +11004,73 @@ class App(QtCore.QObject):
self.inform.emit('[success] %s: %s' %
(_("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):
"""
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 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
import sys
@ -505,7 +505,7 @@ class ToolsDB(QtWidgets.QWidget):
self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
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.setHorizontalHeaderLabels(
[
@ -530,6 +530,7 @@ class ToolsDB(QtWidgets.QWidget):
_("Dwelltime"),
_("Preprocessor"),
_("ExtraCut"),
_("E-Cut Length"),
_("Toolchange"),
_("Toolchange XY"),
_("Toolchange Z"),
@ -620,23 +621,30 @@ class ToolsDB(QtWidgets.QWidget):
"such as that this point is covered by this extra cut to\n"
"ensure a complete isolation."))
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"
"It will create a toolchange event.\n"
"The kind of toolchange is determined by\n"
"the preprocessor file."))
self.table_widget.horizontalHeaderItem(22).setToolTip(
self.table_widget.horizontalHeaderItem(23).setToolTip(
_("Toolchange XY.\n"
"A set of coordinates in the format (x, y).\n"
"Will determine the cartesian position of the point\n"
"where the tool change event take place."))
self.table_widget.horizontalHeaderItem(23).setToolTip(
self.table_widget.horizontalHeaderItem(24).setToolTip(
_("Toolchange Z.\n"
"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"
"If it's left empty it will not be used.\n"
"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"
"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'])
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.set_precision(self.decimals)
depth_per_pass_item.setSingleStep(0.1)
@ -890,8 +908,11 @@ class ToolsDB(QtWidgets.QWidget):
frrapids_item.set_value(float(data['feedrate_rapid']))
widget.setCellWidget(row, 15, frrapids_item)
spindlespeed_item = QtWidgets.QTableWidgetItem(str(data['spindlespeed']) if data['spindlespeed'] else '')
widget.setItem(row, 16, spindlespeed_item)
spindlespeed_item = FCSpinner()
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.set_value(data['dwell'])
@ -913,12 +934,18 @@ class ToolsDB(QtWidgets.QWidget):
ecut_item.set_value(data['extracut'])
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.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 '')
widget.setItem(row, 22, toolchangexy_item)
widget.setItem(row, 23, toolchangexy_item)
toolchangez_item = FCDoubleSpinner()
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_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 '')
widget.setItem(row, 24, startz_item)
widget.setItem(row, 25, startz_item)
endz_item = FCDoubleSpinner()
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_value(float(data['endz']))
widget.setCellWidget(row, 25, endz_item)
widget.setCellWidget(row, 26, endz_item)
def on_tool_add(self):
"""
@ -970,6 +997,7 @@ class ToolsDB(QtWidgets.QWidget):
"dwelltime": float(self.app.defaults["geometry_dwelltime"]),
"ppname_g": self.app.defaults["geometry_ppname_g"],
"extracut": self.app.defaults["geometry_extracut"],
"extracut_length": self.app.defaults["geometry_extracut_length"],
"toolchange": self.app.defaults["geometry_toolchange"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"toolchangez": float(self.app.defaults["geometry_toolchangez"]),
@ -1257,8 +1285,7 @@ class ToolsDB(QtWidgets.QWidget):
elif column_header_text == 'FR Rapids':
default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Spindle Speed':
default_data['spindlespeed'] = float(self.table_widget.item(row, col).text()) \
if self.table_widget.item(row, col).text() is not '' else None
default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Dwell':
default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value()
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()
elif column_header_text == 'ExtraCut':
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':
default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value()
elif column_header_text == 'Toolchange XY':

View File

@ -195,10 +195,11 @@ class FlatCAMObj(QtCore.QObject):
pass
# Creates problems on focusOut
# try:
# self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
# except (TypeError, AttributeError):
# pass
try:
self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
except (TypeError, AttributeError):
pass
# self.ui.skew_button.clicked.connect(self.on_skew_button_click)
def build_ui(self):
@ -267,9 +268,19 @@ class FlatCAMObj(QtCore.QObject):
def on_scale_button_click(self):
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 == 1.0:
self.app.inform.emit('[success] %s' % _("Scale done."))
return
log.debug("FlatCAMObj.on_scale_button_click()")
@ -277,6 +288,8 @@ class FlatCAMObj(QtCore.QObject):
def worker_task():
with self.app.proc_container.new(_("Scaling...")):
self.scale(factor)
self.app.inform.emit('[success] %s' % _("Scale done."))
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
@ -625,6 +638,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# Mouse events
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
self.poly_dict = dict()
@ -1066,11 +1081,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if self.app.is_legacy is False:
event_pos = event.pos
right_button = 2
event_is_dragging = self.app.event_is_dragging
self.app.event_is_dragging = self.app.event_is_dragging
else:
event_pos = (event.xdata, event.ydata)
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:
x = float(event_pos[0])
@ -1080,11 +1095,18 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
event_pos = (x, y)
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:
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():
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',
@ -1113,8 +1135,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.app.tool_shapes.redraw()
else:
self.app.inform.emit(_("No polygon detected under click position."))
elif event.button == right_button and event_is_dragging is False:
elif event.button == right_button and self.app.event_is_dragging is False:
# restore the Grid snapping if it was active before
if self.grid_status_memory is True:
self.app.ui.grid_snap_btn.trigger()
@ -1136,6 +1157,75 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
else:
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,
milling_type=None, follow=None, plot=True):
"""
@ -1242,6 +1332,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"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'],
@ -1718,18 +1809,25 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:param aperture: string; aperture for which to clear the mark shapes
:return:
"""
try:
if self.mark_shapes:
if aperture == 'all':
for apid in list(self.apertures.keys()):
if self.app.is_legacy is True:
self.mark_shapes[apid].clear(update=False)
else:
self.mark_shapes[apid].clear(update=True)
try:
if self.app.is_legacy is True:
self.mark_shapes[apid].clear(update=False)
else:
self.mark_shapes[apid].clear(update=True)
except Exception as e:
log.debug("FlatCAMGerber.clear_plot_apertures() 'all' --> %s" % str(e))
else:
self.mark_shapes[aperture].clear(update=True)
except Exception as e:
log.debug("FlatCAMGerber.clear_plot_apertures() --> %s" % str(e))
try:
if self.app.is_legacy is True:
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):
self.ui.mark_all_cb.set_value(False)
@ -2117,7 +2215,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
"toolchangexy": "0.0, 0.0",
"endz": 2.0,
"startz": None,
"spindlespeed": None,
"spindlespeed": 0,
"dwell": True,
"dwelltime": 1000,
"ppname_e": 'defaults',
@ -2128,10 +2226,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
})
# TODO: Document this.
self.tool_cbs = {}
self.tool_cbs = dict()
# 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
self.tot_drill_cnt = 0
@ -3175,7 +3273,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
job_obj.feedrate = float(self.options["feedrate"])
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.dwell = self.options["dwell"]
job_obj.dwelltime = float(self.options["dwelltime"])
@ -3254,30 +3352,31 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def convert_units(self, 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
self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate'] = float(self.options['feedrate']) * 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"
else:
coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")]
if len(coords_xy) < 2:
self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be "
"in the format (x, y) \n"
"but now there is only one value, not two. "))
return 'fail'
coords_xy[0] *= factor
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
self.options['endz'] = float(self.options['endz']) * factor
# factor = Excellon.convert_units(self, units)
# self.options['drillz'] = float(self.options['drillz']) * factor
# self.options['travelz'] = float(self.options['travelz']) * factor
# self.options['feedrate'] = float(self.options['feedrate']) * 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"
# else:
# coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")]
# if len(coords_xy) < 2:
# self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be "
# "in the format (x, y) \n"
# "but now there is only one value, not two. "))
# return 'fail'
# coords_xy[0] *= factor
# 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
# self.options['endz'] = float(self.options['endz']) * factor
def on_solid_cb_click(self, *args):
if self.muted_ui:
@ -3439,12 +3538,13 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"feedrate": 5.0,
"feedrate_z": 5.0,
"feedrate_rapid": 5.0,
"spindlespeed": None,
"spindlespeed": 0,
"dwell": True,
"dwelltime": 1000,
"multidepth": False,
"depthperpass": 0.002,
"extracut": False,
"extracut_length": 0.1,
"endz": 2.0,
"startz": None,
"toolchange": False,
@ -3684,6 +3784,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"feedrate_probe": self.ui.feedrate_probe_entry,
"depthperpass": self.ui.maxdepth_entry,
"extracut": self.ui.extracut_cb,
"extracut_length": self.ui.e_cut_entry,
"toolchange": self.ui.toolchangeg_cb,
"toolchangez": self.ui.toolchangez_entry,
"endz": self.ui.gendz_entry,
@ -3722,10 +3823,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"ppname_g": None,
"depthperpass": None,
"extracut": None,
"extracut_length": None,
"toolchange": None,
"toolchangez": None,
"endz": None,
"spindlespeed": None,
"spindlespeed": 0,
"toolchangexy": None,
"startz": None
})
@ -3814,6 +3916,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.fr_rapidlabel.hide()
self.ui.cncfeedrate_rapid_entry.hide()
self.ui.extracut_cb.hide()
self.ui.e_cut_entry.hide()
self.ui.pdepth_label.hide()
self.ui.pdepth_entry.hide()
self.ui.feedrate_probe_label.hide()
@ -3821,9 +3924,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
else:
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.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.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.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"]
multidepth = tools_dict[tooluid_key]['data']["multidepth"]
extracut = tools_dict[tooluid_key]['data']["extracut"]
extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
toolchange = tools_dict[tooluid_key]['data']["toolchange"]
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,
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
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,
pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt)
@ -5127,6 +5234,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"]
multidepth = tools_dict[tooluid_key]['data']["multidepth"]
extracut = tools_dict[tooluid_key]['data']["extracut"]
extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
toolchange = tools_dict[tooluid_key]['data']["toolchange"]
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,
spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
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,
pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt)
@ -5226,7 +5334,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
spindlespeed=None, dwell=None, dwelltime=None,
multidepth=None, depthperpass=None,
toolchange=None, toolchangez=None, toolchangexy=None,
extracut=None, startz=None, endz=None,
extracut=None, extracut_length=None, startz=None, endz=None,
pp=None,
segx=None, segy=None,
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'])
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"]
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,
multidepth=multidepth, depthpercut=depthperpass,
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
)
@ -5625,7 +5735,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
tooldia = self.ui.addtool_entry.get_value()
if tooldia:
tooldia *= factor
# limit the decimals to 2 for METRIC and 3 for INCH
tooldia = float('%.*f' % (self.decimals, 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):
visible = visible if visible else self.options['plot']
try:
for sub_el in element:
self.plot_element(sub_el)
@ -5951,15 +6059,22 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.ui_disconnect()
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()
# 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
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.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):
FlatCAMObj.set_ui(self, ui)
@ -6645,10 +6843,20 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
else:
# multiple tools usage
for tooluid_key in self.cnc_tools:
tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed']
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
if self.cnc_tools:
for tooluid_key in self.cnc_tools:
tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia'])))
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()
except (ObjectDeleted, AttributeError):
self.shapes.clear(update=True)

View File

@ -785,23 +785,41 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.item_selected.emit(obj.options['name'])
if obj.kind == 'gerber':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='green', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='green',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'excellon':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='brown', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='brown',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'cncjob':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='blue', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='blue',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'geometry':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='red', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='red',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'script':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='orange', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='orange',
name=str(obj.options['name']),
tx=_("selected"))
)
elif obj.kind == 'document':
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
color='darkCyan', name=str(obj.options['name'])))
self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
color='darkCyan',
name=str(obj.options['name']),
tx=_("selected"))
)
except IndexError:
self.item_selected.emit('none')
# 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
- updated the border for fit view on OpenGL graphic mode
- Calibration Tool - added preferences values
- 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
- 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 FlatCAMGeometry fixed the scale and offset methods to always process the self.solid_geometry
- Calibration Tool - finished the calibrated object creation method
- updated the POT file
- fixed an error in the German PO file
- 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

105
camlib.py
View File

@ -459,7 +459,7 @@ class Geometry(object):
defaults = {
"units": 'in',
"geo_steps_per_circle": 128
"geo_steps_per_circle": 64
}
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
if str(type(self)) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
if self.kind.lower() == 'geometry':
flat_geo = []
if self.multigeo:
for tool in self.tools:
@ -2158,7 +2158,7 @@ class CNCjob(Geometry):
self.units = units
self.z_cut = z_cut
self.tool_offset = {}
self.tool_offset = dict()
self.z_move = z_move
@ -2359,7 +2359,9 @@ class CNCjob(Geometry):
self.exc_drills = deepcopy(exobj.drills)
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 drillz > 0:
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)
)
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]]['tool'] = it[0]
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]]['offset_z'] = z_off
self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo)
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)
except KeyError:
z_offset = 0
self.z_cut += z_offset
self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90":
@ -2682,11 +2690,11 @@ class CNCjob(Geometry):
else:
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented'))
return 'fail'
self.z_cut = deepcopy(old_zcut)
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL] %s...' %
_('The loaded Excellon file has no drills'))
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('The loaded Excellon file has no drills'))
return 'fail'
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)
except KeyError:
z_offset = 0
self.z_cut += z_offset
self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90":
@ -2825,6 +2833,7 @@ class CNCjob(Geometry):
else:
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented'))
return 'fail'
self.z_cut = deepcopy(old_zcut)
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
@ -2879,7 +2888,7 @@ class CNCjob(Geometry):
z_offset = float(self.tool_offset[current_tooldia]) * (-1)
except KeyError:
z_offset = 0
self.z_cut += z_offset
self.z_cut = z_offset + old_zcut
self.coordinates_type = self.app.defaults["cncjob_coords_type"]
if self.coordinates_type == "G90":
@ -2933,6 +2942,7 @@ class CNCjob(Geometry):
self.app.inform.emit('[ERROR_NOTCL] %s...' %
_('The loaded Excellon file has no drills'))
return 'fail'
self.z_cut = deepcopy(old_zcut)
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
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,
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
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):
"""
Algorithm to generate from multitool Geometry.
@ -2992,6 +3002,7 @@ class CNCjob(Geometry):
:param toolchangexy:
: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
:param extracut_length: Extra cut legth at the end of the path
:param startz:
:param endz:
: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.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.dwell = dwell
self.dwelltime = float(dwelltime) if dwelltime else None
@ -3213,7 +3224,8 @@ class CNCjob(Geometry):
# calculate the cut distance
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 ---------
else:
@ -3227,7 +3239,7 @@ class CNCjob(Geometry):
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)
# calculate the total distance
@ -3270,7 +3282,7 @@ class CNCjob(Geometry):
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
multidepth=False, depthpercut=None,
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):
"""
Second algorithm to generate from Geometry.
@ -3288,6 +3300,7 @@ class CNCjob(Geometry):
:param depthpercut: Maximum depth in each pass.
: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
:param extracut_length: The extra cut length
:return: None
"""
@ -3375,7 +3388,7 @@ class CNCjob(Geometry):
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.spindlespeed = int(spindlespeed) if spindlespeed else None
self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
self.spindledir = spindledir
self.dwell = dwell
self.dwelltime = float(dwelltime) if dwelltime else None
@ -3559,7 +3572,8 @@ class CNCjob(Geometry):
if not multidepth:
# calculate the cut distance
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 ---------
else:
@ -3573,7 +3587,7 @@ class CNCjob(Geometry):
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)
# calculate the travel distance
@ -3798,7 +3812,7 @@ class CNCjob(Geometry):
gcode += self.doformat(p.lift_code)
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.
gcode_single_pass = ''
@ -3807,7 +3821,8 @@ class CNCjob(Geometry):
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point)
else:
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:
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, old_point=old_point)
elif type(geometry) == Point:
@ -3818,7 +3833,7 @@ class CNCjob(Geometry):
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 = ''
@ -3851,8 +3866,8 @@ class CNCjob(Geometry):
old_point=old_point)
else:
if geometry.is_ring:
gcode_multi_pass += self.linear2gcode_extra(geometry, tolerance=tolerance, z_cut=depth,
up=False, old_point=old_point)
gcode_multi_pass += self.linear2gcode_extra(geometry, extracut_length, tolerance=tolerance,
z_cut=depth, up=False, old_point=old_point)
else:
gcode_multi_pass += self.linear2gcode(geometry, tolerance=tolerance, z_cut=depth, up=False,
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
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,
feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)):
"""
Generates G-code to cut along the linear feature.
: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
:param tolerance: All points in the simplified object will be within the
tolerance distance of the original geometry.
@ -4602,8 +4618,7 @@ class CNCjob(Geometry):
# For Incremental coordinates type G91
# next_x = pt[0] - prev_x
# next_y = pt[1] - prev_y
self.app.inform.emit('[ERROR_NOTCL] %s' %
_('G91 coordinates not implemented ...'))
self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
next_x = pt[0]
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
# to make sure that we remove the copper leftovers
# Linear motion to the 1st point in the cut path
if self.coordinates_type == "G90":
# For Absolute coordinates type G90
last_x = path[1][0]
last_y = path[1][1]
# if self.coordinates_type == "G90":
# # For Absolute coordinates type G90
# last_x = path[1][0]
# 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:
# 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)
if abs(distance(path[1], path[0])) > extracut_length:
i_point = LineString([path[0], path[1]]).interpolate(extracut_length)
gcode += self.doformat(p.linear_code, x=i_point.x, y=i_point.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.
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

View File

@ -455,8 +455,6 @@ class PaintOptionsTool(FlatCAMTool):
ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
ovlabel.setToolTip(
_("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"
"and increasing it if areas that should be painted are still \n"
"not painted.\n"

View File

@ -168,6 +168,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('&DXF as Gerber Object ...'), self)
self.menufileimport.addAction(self.menufileimportdxf_as_gerber)
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 ...
self.menufileexport = self.menufile.addMenu(QtGui.QIcon('share/export.png'), _('Export'))
@ -250,6 +254,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('Save Project C&opy ...'), self)
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
self.menufile.addSeparator()
@ -754,6 +765,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_("Copper Thieving 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# ####################
@ -2198,6 +2210,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_("Copper Thieving 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 # ##
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)
return
# Calibrate Tool
# Calibration Tool
if key == QtCore.Qt.Key_E:
self.app.cal_exc_tool.run(toggle=True)
return

View File

@ -100,13 +100,10 @@ class ObjectUI(QtWidgets.QWidget):
faclabel = QtWidgets.QLabel('%s:' % _('Factor'))
faclabel.setToolTip(
_("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.set_precision(self.decimals)
self.scale_entry.setRange(0.0, 9999.9999)
self.scale_entry.setSingleStep(0.1)
self.scale_entry = FCEntry()
self.scale_entry.set_value(1.0)
# GO Button
@ -131,7 +128,8 @@ class ObjectUI(QtWidgets.QWidget):
self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector'))
self.offset_vectorlabel.setToolTip(
_("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.setText("(0.0, 0.0)")
@ -158,6 +156,8 @@ class GerberObjectUI(ObjectUI):
ObjectUI.__init__(self, title=_('Gerber Object'), parent=parent, decimals=decimals)
self.decimals = decimals
self.custom_box.addWidget(QtWidgets.QLabel(''))
# Plot options
grid0 = QtWidgets.QGridLayout()
grid0.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
@ -165,10 +165,20 @@ class GerberObjectUI(ObjectUI):
grid0.setColumnStretch(0, 0)
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.setMinimumWidth(90)
grid0.addWidget(self.plot_options_label, 0, 0)
grid0.addWidget(self.plot_options_label, 1, 0)
# Solid CB
self.solid_cb = FCCheckBox(label=_('Solid'))
@ -176,23 +186,15 @@ class GerberObjectUI(ObjectUI):
_("Solid color polygons.")
)
self.solid_cb.setMinimumWidth(50)
grid0.addWidget(self.solid_cb, 0, 1)
grid0.addWidget(self.solid_cb, 1, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label=_('M-Color'))
self.multicolored_cb = FCCheckBox(label=_('Multi-Color'))
self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.")
)
self.multicolored_cb.setMinimumWidth(55)
grid0.addWidget(self.multicolored_cb, 0, 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)
grid0.addWidget(self.multicolored_cb, 1, 2)
# ## Object name
self.name_hlay = QtWidgets.QHBoxLayout()
@ -264,7 +266,10 @@ class GerberObjectUI(ObjectUI):
# start with apertures table hidden
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
self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing"))
@ -552,6 +557,12 @@ class GerberObjectUI(ObjectUI):
_("Create the Geometry Object\n"
"for non-copper routing.")
)
self.generate_ncc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.clearcopper_label, 1, 0)
grid2.addWidget(self.generate_ncc_button, 1, 1)
@ -568,6 +579,12 @@ class GerberObjectUI(ObjectUI):
_("Generate the geometry for\n"
"the board cutout.")
)
self.generate_cutout_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.board_cutout_label, 2, 0)
grid2.addWidget(self.generate_cutout_button, 2, 1)
@ -913,7 +930,9 @@ class ExcellonObjectUI(ObjectUI):
"in RPM (optional)")
)
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)
# Dwell
@ -1011,6 +1030,12 @@ class ExcellonObjectUI(ObjectUI):
self.generate_cnc_button.setToolTip(
_("Generate the CNC Job.")
)
self.generate_cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.generate_cnc_button, 2, 0, 1, 3)
# ### Milling Holes Drills ####
@ -1036,6 +1061,12 @@ class ExcellonObjectUI(ObjectUI):
_("Create the Geometry Object\n"
"for milling DRILLS toolpaths.")
)
self.generate_milling_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.tdlabel, 4, 0)
grid2.addWidget(self.tooldia_entry, 4, 1)
@ -1057,6 +1088,12 @@ class ExcellonObjectUI(ObjectUI):
_("Create the Geometry Object\n"
"for milling SLOTS toolpaths.")
)
self.generate_milling_slots_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid2.addWidget(self.stdlabel, 5, 0)
grid2.addWidget(self.slot_tooldia_entry, 5, 1)
@ -1501,14 +1538,29 @@ class GeometryObjectUI(ObjectUI):
self.cncfeedrate_rapid_entry.hide()
# 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(
_("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.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.e_cut_entry, 13, 1)
self.ois_e_cut = OptionalInputSection(self.extracut_cb, [self.e_cut_entry])
# Spindlespeed
spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
@ -1519,7 +1571,9 @@ class GeometryObjectUI(ObjectUI):
"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(self.cncspindlespeed_entry, 14, 1)
@ -1617,13 +1671,28 @@ class GeometryObjectUI(ObjectUI):
self.generate_cnc_button.setToolTip(
_("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 ##
# ##############
self.paint_label = QtWidgets.QLabel('<b>%s</b>' % _('Paint Area'))
self.paint_label.setToolTip(
self.tools_label = QtWidgets.QLabel('<b>%s</b>' % _('TOOLS'))
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"
"whole area of a polygon (remove\n"
@ -1631,14 +1700,27 @@ class GeometryObjectUI(ObjectUI):
"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
self.paint_tool_button = QtWidgets.QPushButton(_('Paint Tool'))
self.paint_tool_button.setToolTip(
_("Launch Paint Tool in Tools Tab.")
# NCC Tool
self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
self.generate_ncc_button.setToolTip(
_("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):
@ -1781,12 +1863,20 @@ class CNCObjectUI(ObjectUI):
self.cnc_tools_table.setColumnCount(7)
self.cnc_tools_table.setColumnWidth(0, 20)
self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '',
_('P')])
self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '', _('P')])
self.cnc_tools_table.setColumnHidden(5, True)
# stylesheet = "::section{Background-color:rgb(239,239,245)}"
# 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.set_range(0, 9999.9999)
self.tooldia_entry.set_precision(self.decimals)
@ -1820,7 +1910,7 @@ class CNCObjectUI(ObjectUI):
self.prepend_text = FCTextArea()
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.")
)
self.custom_box.addWidget(self.prepend_text)
@ -1836,8 +1926,8 @@ class CNCObjectUI(ObjectUI):
self.append_text = FCTextArea()
self.append_text.setPlaceholderText(
_("Type here any G-Code commands you would "
"like to append to the generated file. "
_("Type here any G-Code commands you would\n"
"like to append to the generated file.\n"
"I.e.: M2 (End of program)")
)
self.custom_box.addWidget(self.append_text)
@ -1868,12 +1958,12 @@ class CNCObjectUI(ObjectUI):
self.toolchange_text = FCTextArea()
self.toolchange_text.setPlaceholderText(
_(
"Type here any G-Code commands you would "
"like to be executed when Toolchange event is encountered. "
"This will constitute a Custom Toolchange GCode, "
"or a Toolchange Macro. "
"The FlatCAM variables are surrounded by '%' symbol. \n"
"WARNING: it can be used only with a preprocessor file "
"Type here any G-Code commands you would\n"
"like to be executed when Toolchange event is encountered.\n"
"This will constitute a Custom Toolchange GCode,\n"
"or a Toolchange Macro.\n"
"The FlatCAM variables are surrounded by '%' symbol.\n"
"WARNING: it can be used only with a preprocessor file\n"
"that has 'toolchange_custom' in it's name."
)
)

View File

@ -156,7 +156,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
self.big_cursor = None
# Keep VisPy canvas happy by letting it be "frozen" again.
self.freeze()
self.fit_view()
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
def draw_workspace(self, workspace_size):
@ -303,7 +303,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
p2 = np.array(curr_pos)[:2]
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 = self.fcapp.geo_editor.snap(pos_canvas[0], pos_canvas[1])

View File

@ -2430,7 +2430,9 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
"in RPM (optional)")
)
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)
# Dwell
@ -3341,7 +3343,10 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
)
)
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)
# Dwell
@ -3440,14 +3445,27 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
grid1.addWidget(self.cncfeedrate_rapid_entry, 4, 1)
# 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(
_("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.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.e_cut_entry, 5, 1)
# Probe depth
self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
@ -3762,7 +3780,7 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
self.prepend_text = FCTextArea()
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.")
)
self.layout.addWidget(self.prepend_text)
@ -3779,8 +3797,8 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI):
self.append_text = FCTextArea()
self.append_text.setPlaceholderText(
_("Type here any G-Code commands you would "
"like to append to the generated file. "
_("Type here any G-Code commands you would\n"
"like to append to the generated file.\n"
"I.e.: M2 (End of program)")
)
self.layout.addWidget(self.append_text)
@ -3832,12 +3850,12 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
self.toolchange_text = FCTextArea()
self.toolchange_text.setPlaceholderText(
_(
"Type here any G-Code commands you would "
"like to be executed when Toolchange event is encountered. "
"This will constitute a Custom Toolchange GCode, "
"or a Toolchange Macro. "
"The FlatCAM variables are surrounded by '%' symbol. \n"
"WARNING: it can be used only with a preprocessor file "
"Type here any G-Code commands you would\n"
"like to be executed when Toolchange event is encountered.\n"
"This will constitute a Custom Toolchange GCode,\n"
"or a Toolchange Macro.\n"
"The FlatCAM variables are surrounded by '%' symbol.\n"
"WARNING: it can be used only with a preprocessor file\n"
"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.setToolTip(_("Can be:\n"
"- Portrait\n"
"- Lanscape"))
"- Landscape"))
self.orientation_radio = RadioSet([{'label': _('Portrait'), 'value': 'p'},
{'label': _('Landscape'), 'value': 'l'},
@ -6105,7 +6123,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], orientation='vertical', stretch=False)
self.reference_label = QtWidgets.QLabel(_("Reference:"))
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"
"- 'Reference Object' - will do copper thieving within the area specified by another object.")
)
@ -6119,7 +6137,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], stretch=False)
self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
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.")
)
grid_lay.addWidget(self.bbox_type_label, 5, 0)
@ -6139,7 +6157,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
], orientation='vertical', stretch=False)
self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
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"
"- '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.")
@ -6346,7 +6364,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
], stretch=False)
self.mode_label = QtWidgets.QLabel(_("Mode:"))
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.")
)
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.setToolTip(
_("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"
"- '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(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()

View File

@ -108,10 +108,17 @@ class VisPyCanvas(scene.SceneCanvas):
# self.measure_fps()
def translate_coords(self, pos):
"""
Translate pixels to FlatCAM units.
"""
tr = self.grid.get_transform('canvas', 'visual')
return tr.map(pos)
def translate_coords_2(self, pos):
"""
Translate FlatCAM units to pixels.
"""
tr = self.grid.get_transform('visual', 'document')
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))
# dictionary to store tools, see above for description
self.tools = {}
self.tools = dict()
# 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 = []
self.slots = list()
self.source_file = ''
@ -109,8 +109,8 @@ class Excellon(Geometry):
self.match_routing_start = None
self.match_routing_stop = None
self.num_tools = [] # List for keeping the tools sorted
self.index_per_tool = {} # Dictionary to store the indexed points for each tool
self.num_tools = list() # List for keeping the tools sorted
self.index_per_tool = dict() # Dictionary to store the indexed points for each tool
# ## IN|MM -> Units are inherited from Geometry
self.units = self.app.defaults['units']
@ -118,8 +118,8 @@ class Excellon(Geometry):
# Trailing "T" or leading "L" (default)
# self.zeros = "T"
self.zeros = zeros or self.defaults["zeros"]
self.zeros_found = self.zeros
self.units_found = self.units
self.zeros_found = deepcopy(self.zeros)
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
# in another file like for PCB WIzard ECAD software
@ -790,7 +790,7 @@ class Excellon(Geometry):
# ## Units and number format # ##
match = self.units_re.match(eline)
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.excellon_format = match.group(3)
if self.excellon_format:
@ -884,8 +884,9 @@ class Excellon(Geometry):
log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
msg = '[ERROR_NOTCL] %s' % \
_("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]',
tx=_("Excellon Parser error.\nParsing Failed. Line"),
l_nr=line_num,
line=eline)
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 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 flatcamEditors.FlatCAMTextEditor import TextEditor
from shapely.geometry import Point
from shapely.geometry.base import *
from shapely.affinity import scale, skew
import math
from datetime import datetime
@ -63,13 +64,119 @@ class ToolCalibration(FlatCAMTool):
grid_lay.setColumnStretch(1, 1)
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.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"
"(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.setToolTip(_("The source of calibration points.\n"
@ -80,8 +187,8 @@ class ToolCalibration(FlatCAMTool):
{'label': _('Free'), 'value': 'free'}],
stretch=False)
grid_lay.addWidget(self.cal_source_lbl, 1, 0)
grid_lay.addWidget(self.cal_source_radio, 1, 1, 1, 2)
grid_lay.addWidget(self.cal_source_lbl, 11, 0)
grid_lay.addWidget(self.cal_source_radio, 11, 1, 1, 2)
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.setCurrentIndex(1)
grid_lay.addWidget(self.obj_type_label, 2, 0)
grid_lay.addWidget(self.obj_type_combo, 2, 1, 1, 2)
self.obj_type_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
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.setModel(self.app.collection)
@ -103,20 +213,20 @@ class ToolCalibration(FlatCAMTool):
_("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_combo, 4, 0, 1, 3)
grid_lay.addWidget(self.object_label, 13, 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.setToolTip(
_("Contain the expected calibration points and the\n"
"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.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# 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.setHorizontalHeaderLabels(
@ -220,6 +330,7 @@ class ToolCalibration(FlatCAMTool):
self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
self.top_right_coordx_tgt.setReadOnly(True)
self.top_right_coordx_found = EvalEntry()
self.top_right_coordx_found.setDisabled(True)
self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
row += 1
@ -229,6 +340,7 @@ class ToolCalibration(FlatCAMTool):
self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
self.top_right_coordy_tgt.setReadOnly(True)
self.top_right_coordy_found = EvalEntry()
self.top_right_coordy_found.setDisabled(True)
self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
vertical_header = self.points_table.verticalHeader()
@ -268,87 +380,38 @@ class ToolCalibration(FlatCAMTool):
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.setFrameShape(QtWidgets.QFrame.HLine)
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 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode"))
step_2.setToolTip(
_("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)
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])
grid_lay.addWidget(step_2, 20, 0, 1, 3)
# ## GCode Button
self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
self.gcode_button.setToolTip(
_("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("""
QPushButton
@ -356,14 +419,14 @@ class ToolCalibration(FlatCAMTool):
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.setFrameShape(QtWidgets.QFrame.HLine)
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 = 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"
"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
self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
@ -387,14 +450,14 @@ class ToolCalibration(FlatCAMTool):
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.setFrameShape(QtWidgets.QFrame.HLine)
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 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode"))
@ -402,7 +465,7 @@ class ToolCalibration(FlatCAMTool):
_("Generate verification GCode file adjusted with\n"
"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.setToolTip(
@ -413,8 +476,8 @@ class ToolCalibration(FlatCAMTool):
self.scalex_entry.set_precision(self.decimals)
self.scalex_entry.setSingleStep(0.1)
grid_lay.addWidget(self.scalex_label, 24, 0)
grid_lay.addWidget(self.scalex_entry, 24, 1, 1, 2)
grid_lay.addWidget(self.scalex_label, 29, 0)
grid_lay.addWidget(self.scalex_entry, 29, 1, 1, 2)
self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
self.scaley_label.setToolTip(
@ -425,8 +488,8 @@ class ToolCalibration(FlatCAMTool):
self.scaley_entry.set_precision(self.decimals)
self.scaley_entry.setSingleStep(0.1)
grid_lay.addWidget(self.scaley_label, 25, 0)
grid_lay.addWidget(self.scaley_entry, 25, 1, 1, 2)
grid_lay.addWidget(self.scaley_label, 30, 0)
grid_lay.addWidget(self.scaley_entry, 30, 1, 1, 2)
self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors"))
self.scale_button.setToolTip(
@ -438,7 +501,7 @@ class ToolCalibration(FlatCAMTool):
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.setToolTip(
@ -450,8 +513,8 @@ class ToolCalibration(FlatCAMTool):
self.skewx_entry.set_precision(self.decimals)
self.skewx_entry.setSingleStep(0.1)
grid_lay.addWidget(self.skewx_label, 27, 0)
grid_lay.addWidget(self.skewx_entry, 27, 1, 1, 2)
grid_lay.addWidget(self.skewx_label, 32, 0)
grid_lay.addWidget(self.skewx_entry, 32, 1, 1, 2)
self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
self.skewy_label.setToolTip(
@ -463,8 +526,8 @@ class ToolCalibration(FlatCAMTool):
self.skewy_entry.set_precision(self.decimals)
self.skewy_entry.setSingleStep(0.1)
grid_lay.addWidget(self.skewy_label, 28, 0)
grid_lay.addWidget(self.skewy_entry, 28, 1, 1, 2)
grid_lay.addWidget(self.skewy_label, 33, 0)
grid_lay.addWidget(self.skewy_entry, 33, 1, 1, 2)
self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors"))
self.skew_button.setToolTip(
@ -476,7 +539,7 @@ class ToolCalibration(FlatCAMTool):
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.setToolTip(
@ -540,7 +603,9 @@ class ToolCalibration(FlatCAMTool):
self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
self.adj_gcode_button.setToolTip(
_("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("""
QPushButton
@ -548,14 +613,14 @@ class ToolCalibration(FlatCAMTool):
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.setFrameShape(QtWidgets.QFrame.HLine)
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 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects"))
@ -563,19 +628,23 @@ class ToolCalibration(FlatCAMTool):
_("Adjust the FlatCAM objects\n"
"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.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
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.setToolTip(
_("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_combo, 40, 0, 1, 3)
grid_lay.addWidget(self.adj_object_type_label, 46, 0, 1, 3)
grid_lay.addWidget(self.adj_object_type_combo, 47, 0, 1, 3)
self.adj_object_combo = FCComboBox()
self.adj_object_combo.setModel(self.app.collection)
@ -587,8 +656,8 @@ class ToolCalibration(FlatCAMTool):
_("The FlatCAM Object to be adjusted.")
)
grid_lay.addWidget(self.adj_object_label, 41, 0, 1, 3)
grid_lay.addWidget(self.adj_object_combo, 42, 0, 1, 3)
grid_lay.addWidget(self.adj_object_label, 48, 0, 1, 3)
grid_lay.addWidget(self.adj_object_combo, 49, 0, 1, 3)
# ## Adjust Objects Button
self.cal_button = QtWidgets.QPushButton(_("Calibrate"))
@ -602,14 +671,14 @@ class ToolCalibration(FlatCAMTool):
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.setFrameShape(QtWidgets.QFrame.HLine)
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()
@ -630,7 +699,7 @@ class ToolCalibration(FlatCAMTool):
self.units = ''
# here store 4 points to be used for calibration
self.click_points = list()
self.click_points = [[], [], [], []]
# store the status of the grid
self.grid_status_memory = None
@ -647,17 +716,22 @@ class ToolCalibration(FlatCAMTool):
self.cal_object = None
# ## 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.obj_type_combo.currentIndexChanged.connect(self.on_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.reset_button.clicked.connect(self.set_tool_ui)
def run(self, toggle=True):
self.app.report_usage("ToolCalibration()")
@ -685,7 +759,7 @@ class ToolCalibration(FlatCAMTool):
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):
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.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz'])
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.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())
try:
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..."))
return
@ -768,60 +845,75 @@ class ToolCalibration(FlatCAMTool):
self.app.inform.emit(_("Get First calibration point. Bottom Left..."))
def on_mouse_click_release(self, event):
if event.button == 1:
if self.app.is_legacy is False:
event_pos = event.pos
else:
event_pos = (event.xdata, event.ydata)
if self.app.is_legacy is False:
event_pos = event.pos
right_button = 2
self.app.event_is_dragging = self.app.event_is_dragging
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]])
if self.cal_source_radio.get_value() == 'object':
if self.target_obj.kind.lower() == 'excellon':
for tool, tool_dict in self.target_obj.tools.items():
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()
if self.app.selection_type is not None:
# delete previous selection shape
self.app.delete_selection_shape()
self.app.selection_type = None
else:
self.click_points.append(
(
float('%.*f' % (self.decimals, click_pt.x)),
float('%.*f' % (self.decimals, click_pt.y))
if self.cal_source_radio.get_value() == 'object':
if self.target_obj.kind.lower() == 'excellon':
for tool, tool_dict in self.target_obj.tools.items():
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):
if len(self.click_points) == 1:
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.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:
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.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:
self.top_left_coordx_tgt.set_value(self.click_points[2][0])
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_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):
log.debug("ToolCalibration.gcode_header()")
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' % \
(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 += '(Created on ' + time_str + ')\n' + '\n'
gcode += '(Units: ' + self.units.upper() + ')\n\n'
gcode += '(Created on ' + time_str + ')\n\n'
gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
gcode += 'G90\n'
gcode += 'G17\n'
@ -872,9 +970,13 @@ class ToolCalibration(FlatCAMTool):
self.app.ui.plot_tab_area.removeTab(idx)
def generate_verification_gcode(self):
sec_point = self.second_point_radio.get_value()
travel_z = '%.*f' % (self.decimals, self.travelz_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())
if len(self.click_points) != 4:
@ -884,36 +986,64 @@ class ToolCalibration(FlatCAMTool):
gcode = self.gcode_header()
if self.zeroz_cb.get_value():
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 += 'G01 Z0\n'
gcode += 'M0\n'
gcode += f'G00 Z{toolchange_z}\n'
gcode += 'G00 Z%s\n' % toolchange_z
gcode += 'M0\n'
gcode += f'G00 Z{travel_z}\n'
gcode += f'G00 X{self.click_points[0][0]} Y{self.click_points[0][1]}\n'
gcode += f'G01 Z{verification_z}\n'
# first point: bottom - left -> ORIGIN set
gcode += 'G00 Z%s\n' % travel_z
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 += f'G00 Z{travel_z}\n'
gcode += f'G00 X{self.click_points[2][0]} Y{self.click_points[2][1]}\n'
gcode += f'G01 Z{verification_z}\n'
gcode += 'M0\n'
if sec_point == 'tl':
# second point: top - left -> align the PCB to this point
gcode += 'G00 Z%s\n' % travel_z
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'
gcode += f'G00 X{self.click_points[3][0]} Y{self.click_points[3][1]}\n'
gcode += f'G01 Z{verification_z}\n'
gcode += 'M0\n'
# third point: bottom - right -> check for scale on X axis or for skew on Y axis
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'
gcode += f'G00 X{self.click_points[1][0]} Y{self.click_points[1][1]}\n'
gcode += f'G01 Z{verification_z}\n'
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'
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'
gcode += f'G00 X0 Y0\n'
gcode += f'G00 Z{toolchange_z}\n'
# third point: top - left -> check for scale on Y axis or for skew on X axis
gcode += 'G00 Z%s\n' % travel_z
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'
@ -961,74 +1091,142 @@ class ToolCalibration(FlatCAMTool):
origin_x = self.click_points[0][0]
origin_y = self.click_points[0][1]
top_left_x = float('%.*f' % (self.decimals, self.click_points[2][0]))
top_left_y = float('%.*f' % (self.decimals, self.click_points[2][1]))
top_left_x = self.click_points[2][0]
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:
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:
top_left_dx = top_left_x
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:
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:
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:
bot_right_dx = bot_right_x
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:
bot_right_dy = bot_right_y
# ------------------------------------------------------------------------------- #
# --------------------------- 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
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)
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
dx = top_left_dx
dy = top_left_y - origin_y
skew_angle_x = math.degrees(math.atan(dx / dy))
self.skewx_entry.set_value(skew_angle_x)
if bot_right_dx != float('%.*f' % (self.decimals, 0.0)):
# 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)):
if bot_right_dy != float('%.*f' % (self.decimals, bot_right_y)):
# we have skew on Y
dx = bot_right_x - origin_x
dy = bot_right_dy + origin_y
skew_angle_y = math.degrees(math.atan(dy / dx))
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):
# get the FlatCAM object to calibrate
selection_index = self.adj_object_combo.currentIndex()
@ -1076,8 +1274,8 @@ class ToolCalibration(FlatCAMTool):
try:
if obj.tools:
obj_init.tools = deepcopy(obj.tools)
except Exception as e:
log.debug("App.on_copy_object() --> %s" % str(e))
except Exception as ee:
log.debug("App.on_copy_object() --> %s" % str(ee))
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))
@ -1102,8 +1300,8 @@ class ToolCalibration(FlatCAMTool):
try:
if obj.tools:
obj_init.tools = deepcopy(obj.tools)
except Exception as e:
log.debug("App.on_copy_object() --> %s" % str(e))
except Exception as err:
log.debug("App.on_copy_object() --> %s" % str(err))
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))

View File

@ -128,7 +128,7 @@ class ToolCopperThieving(FlatCAMTool):
], orientation='vertical', stretch=False)
self.reference_label = QtWidgets.QLabel(_("Reference:"))
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"
"- 'Reference Object' - will do copper thieving within the area specified by another object.")
)

View File

@ -153,7 +153,7 @@ class DblSidedTool(FlatCAMTool):
# ## Axis Location
self.axis_location = RadioSet([{'label': _('Point'), 'value': 'point'},
{'label': _('Box'), 'value': 'box'}])
self.axloc_label = QtWidgets.QLabel(_("Axis Ref:"))
self.axloc_label = QtWidgets.QLabel('%s:' % _("Axis Ref"))
self.axloc_label.setToolTip(
_("The axis should pass through a <b>point</b> or cut\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)
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_y='%*f' % (self.decimals, abs(dy)),
d_z='%*f' % (self.decimals, abs(d)))

View File

@ -278,7 +278,10 @@ class DistanceMin(FlatCAMTool):
)
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_y='%*f' % (self.decimals, abs(dy)),
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.setToolTip(
_("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"
"- '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.graphics import renderPM
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 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."))
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())
source = self.source_punch.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."))
return
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,
scale_stroke_factor=factor,
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_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'):
@ -1068,7 +1078,7 @@ class Film(FlatCAMTool):
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
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'):
"""
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))
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()")
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
@ -1127,9 +1142,9 @@ class Film(FlatCAMTool):
# We set the colour to WHITE
root = ET.fromstring(exported_svg)
for child in root:
child.set('fill', str(self.app.defaults['tools_film_color']))
child.set('opacity', '1.0')
child.set('stroke', str(self.app.defaults['tools_film_color']))
child.set('fill', str(color))
child.set('opacity', str(transparency_level))
child.set('stroke', str(color))
exported_svg = ET.tostring(root)
@ -1190,7 +1205,7 @@ class Film(FlatCAMTool):
return 'fail'
else:
try:
if self.units == 'INCH':
if self.units == 'IN':
unit = inch
else:
unit = mm
@ -1198,11 +1213,10 @@ class Film(FlatCAMTool):
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
p_size = self.pagesize_combo.get_value()
if p_size == 'Bounds':
renderPDF.drawToFile(drawing, filename)
else:
if self.orientation_radio.get_value() == 'p':
if orientation == 'p':
page_size = portrait(self.pagesize[p_size])
else:
page_size = landscape(self.pagesize[p_size])
@ -1225,7 +1239,8 @@ class Film(FlatCAMTool):
def job_thread_film(app_obj):
try:
make_positive_film()
make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
except Exception:
proc.done()
return
@ -1233,7 +1248,8 @@ class Film(FlatCAMTool):
self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else:
make_positive_film()
make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
def reset_fields(self):
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"],
"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"],

View File

@ -440,6 +440,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"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"],
@ -633,6 +634,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"ppname_g": self.app.defaults["geometry_ppname_g"],
"depthperpass": float(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": float(self.app.defaults["geometry_toolchangez"]),
"endz": float(self.app.defaults["geometry_endz"]),

View File

@ -1283,8 +1283,7 @@ class SolderPaste(FlatCAMTool):
obj = self.app.collection.get_by_name(name)
if name == '':
self.app.inform.emit('[WARNING_NOTCL] %s' %
_("There is no Geometry object available."))
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object available."))
return 'fail'
if obj.special_group != 'solder_paste_tool':
@ -1298,8 +1297,7 @@ class SolderPaste(FlatCAMTool):
if obj.tools[tooluid_key]['solid_geometry'] is None:
a += 1
if a == len(obj.tools):
self.app.inform.emit('[ERROR_NOTCL] %s...' %
_('Cancelled. Empty file, it has no geometry'))
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry'))
return 'fail'
# 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
# Python packages that cannot be installed with pip (e.g. PyQt5, GDAL) are not included.
# Usage: pip3 install -r requirements.txt
numpy>=1.16
numpy >=1.16
matplotlib>=3.1
cycler>=0.10
python-dateutil>=2.1
kiwisolver>=1.0.1
kiwisolver>=1.1
six
setuptools
dill
@ -21,6 +21,6 @@ fontTools
rasterio
lxml
ezdxf
qrcode>=6.0
reportlab>=3.0
qrcode>=6.1
reportlab>=3.5
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),
('multidepth', bool),
('extracut', bool),
('extracut_length', float),
('depthperpass', float),
('toolchange', int),
('toolchangez', float),
@ -65,6 +66,7 @@ class TclCommandCncjob(TclCommandSignaled):
('feedrate_rapid', 'Rapid moving at speed when cutting.'),
('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', 'The value for extra cnccut over the first point in path,in the job end; float'),
('depthperpass', 'Height of one layer for multidepth.'),
('toolchange', 'Enable tool changes (example: True).'),
('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["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 \
obj.options["depthperpass"]
@ -143,7 +147,7 @@ class TclCommandCncjob(TclCommandSignaled):
self.app.defaults["geometry_startz"]
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["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']['multidepth'] = args["multidepth"]
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']['toolchange'] = args["toolchange"]
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"],
"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"],

View File

@ -159,6 +159,7 @@ class TclCommandPaint(TclCommand):
"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"],