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

This commit is contained in:
Camellan 2019-12-20 18:30:05 +04:00
commit 07b4046d4f
15 changed files with 733 additions and 130 deletions

View File

@ -47,6 +47,14 @@ if __name__ == '__main__':
else:
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0"
# if hdpi_support == 2:
# tst_screen = QtWidgets.QApplication(sys.argv)
# if tst_screen.screens()[0].geometry().width() > 1930 or tst_screen.screens()[1].geometry().width() > 1930:
# QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
# del tst_screen
# else:
# QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling, False)
app = QtWidgets.QApplication(sys.argv)
# apply style

View File

@ -31,6 +31,7 @@ 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 svglib.svglib import svg2rlg
from contextlib import contextmanager
import gc
@ -415,6 +416,13 @@ class App(QtCore.QObject):
"global_serial": 0,
"global_stats": dict(),
"global_tabs_detachable": True,
"global_jump_ref": 'abs',
"global_tpdf_tmargin": 15.0,
"global_tpdf_bmargin": 10.0,
"global_tpdf_lmargin": 20.0,
"global_tpdf_rmargin": 20.0,
# General
"global_graphic_engine": '3D',
"global_app_level": 'b',
"global_portable": False,
@ -511,6 +519,9 @@ class App(QtCore.QObject):
"gerber_multicolored": False,
"gerber_circle_steps": 64,
"gerber_use_buffer_for_union": True,
"gerber_clean_apertures": True,
"gerber_extra_buffering": True,
"gerber_def_units": 'IN',
"gerber_def_zeros": 'L',
"gerber_save_filters": "Gerber File (*.gbr);;Gerber File (*.bot);;Gerber File (*.bsm);;"
@ -972,6 +983,10 @@ class App(QtCore.QObject):
self.current_units = self.defaults['units']
# store here the current self.defaults so it can be restored if Preferences changes are cancelled
self.current_defaults = dict()
self.current_defaults.update(self.defaults)
# #############################################################################
# ##################### CREATE MULTIPROCESSING POOL ###########################
# #############################################################################
@ -1079,6 +1094,11 @@ class App(QtCore.QObject):
"global_bookmarks_limit": self.ui.general_defaults_form.general_app_group.bm_limit_spinner,
"global_machinist_setting": self.ui.general_defaults_form.general_app_group.machinist_cb,
"global_tpdf_tmargin": self.ui.general_defaults_form.general_app_group.tmargin_entry,
"global_tpdf_bmargin": self.ui.general_defaults_form.general_app_group.bmargin_entry,
"global_tpdf_lmargin": self.ui.general_defaults_form.general_app_group.lmargin_entry,
"global_tpdf_rmargin": self.ui.general_defaults_form.general_app_group.rmargin_entry,
# General GUI Preferences
"global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_entry,
"global_gridy": self.ui.general_defaults_form.general_gui_group.gridy_entry,
@ -1121,6 +1141,8 @@ class App(QtCore.QObject):
"gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry,
"gerber_def_units": self.ui.gerber_defaults_form.gerber_gen_group.gerber_units_radio,
"gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio,
"gerber_clean_apertures": self.ui.gerber_defaults_form.gerber_gen_group.gerber_clean_cb,
"gerber_extra_buffering": self.ui.gerber_defaults_form.gerber_gen_group.gerber_extra_buffering,
# Gerber Options
"gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
@ -1673,6 +1695,7 @@ class App(QtCore.QObject):
self.mr = None
self.mdc = None
self.mp_zc = None
self.kp = None
# Matplotlib axis
self.axes = None
@ -1791,10 +1814,11 @@ class App(QtCore.QObject):
self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf)
self.ui.menufile_print.triggered.connect(lambda: self.on_file_save_objects_pdf(use_thread=True))
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)
@ -2931,17 +2955,25 @@ class App(QtCore.QObject):
except Exception as e:
log.debug("App.defaults_read_form() --> %s" % str(e))
def defaults_write_form(self, factor=None, fl_units=None):
def defaults_write_form(self, factor=None, fl_units=None, source_dict=None):
"""
Will set the values for all the GUI elements in Preferences GUI based on the values found in the
self.defaults dictionary.
:param factor: will apply a factor to the values that written in the GUI elements
:param fl_units: current measuring units in FlatCAM: Metric or Inch
:param source_dict: the repository of options, usually is the self.defaults
:return: None
"""
for option in self.defaults:
self.defaults_write_form_field(option, factor=factor, units=fl_units)
options_storage = self.defaults if source_dict is None else source_dict
for option in options_storage:
if source_dict:
self.defaults_write_form_field(option, factor=factor, units=fl_units, defaults_dict=source_dict)
else:
self.defaults_write_form_field(option, factor=factor, units=fl_units)
# try:
# self.defaults_form_fields[option].set_value(self.defaults[option])
# except KeyError:
@ -2949,7 +2981,7 @@ class App(QtCore.QObject):
# # TODO: Rethink this?
# pass
def defaults_write_form_field(self, field, factor=None, units=None):
def defaults_write_form_field(self, field, factor=None, units=None, defaults_dict=None):
"""
Basically it is the worker in the self.defaults_write_form()
@ -2958,21 +2990,23 @@ class App(QtCore.QObject):
:param units: current FLatCAM measuring units
:return: None, it updates GUI elements
"""
def_dict = self.defaults if defaults_dict is None else defaults_dict
try:
if factor is None:
if units is None:
self.defaults_form_fields[field].set_value(self.defaults[field])
self.defaults_form_fields[field].set_value(def_dict[field])
elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
self.defaults_form_fields[field].set_value(self.defaults[field])
self.defaults_form_fields[field].set_value(def_dict[field])
elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
self.defaults_form_fields[field].set_value(self.defaults[field])
self.defaults_form_fields[field].set_value(def_dict[field])
else:
if units is None:
self.defaults_form_fields[field].set_value(self.defaults[field] * factor)
self.defaults_form_fields[field].set_value(def_dict[field] * factor)
elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
self.defaults_form_fields[field].set_value((self.defaults[field] * factor))
self.defaults_form_fields[field].set_value((def_dict[field] * factor))
elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
self.defaults_form_fields[field].set_value((self.defaults[field] * factor))
self.defaults_form_fields[field].set_value((def_dict[field] * factor))
except KeyError:
# self.log.debug("defaults_write_form(): No field for: %s" % option)
# TODO: Rethink this?
@ -3882,6 +3916,10 @@ class App(QtCore.QObject):
_("Failed to parse defaults file."))
return
self.defaults.update(defaults_from_file)
# update the dict that is used to restore the values in the defaults form if Cancel is clicked in the
# Preferences window
self.current_defaults.update(defaults_from_file)
self.on_preferences_edited()
self.inform.emit('[success] %s: %s' %
(_("Imported Defaults from"), filename))
@ -4161,16 +4199,10 @@ class App(QtCore.QObject):
try:
return_value = initialize(obj, self)
except Exception as e:
msg = '[ERROR_NOTCL] %s' % \
_("An internal error has ocurred. See shell.\n")
msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e))
msg += traceback.format_exc()
self.inform.emit(msg)
# if str(e) == "Empty Geometry":
# self.inform.emit("[ERROR_NOTCL] )
# else:
# self.inform.emit("[ERROR] Object (%s) failed because: %s" % (kind, str(e)))
return "fail"
t2 = time.time()
@ -5761,14 +5793,15 @@ class App(QtCore.QObject):
"tools_cr_trace_size_val", "tools_cr_c2c_val", "tools_cr_c2o_val", "tools_cr_s2s_val",
"tools_cr_s2sm_val", "tools_cr_s2o_val", "tools_cr_sm2sm_val", "tools_cr_ri_val",
"tools_cr_h2h_val", "tools_cr_dh_val", "tools_fiducials_dia", "tools_fiducials_margin",
"tools_fiducials_mode", "tools_fiducials_second_pos", "tools_fiducials_type",
"tools_fiducials_line_thickness",
"tools_copper_thieving_clearance", "tools_copper_thieving_margin",
"tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing",
"tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing",
"tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing",
"tools_copper_thieving_rb_margin", "tools_copper_thieving_rb_thickness",
'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance"]
'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance",
'global_tpdf_bmargin', 'global_tpdf_tmargin', 'global_tpdf_rmargin', 'global_tpdf_lmargin']
def scale_defaults(sfactor):
for dim in dimensions:
@ -5793,6 +5826,7 @@ class App(QtCore.QObject):
tools_diameters = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
self.defaults['geometry_cnctooldia'] = ''
for t in range(len(tools_diameters)):
@ -5805,6 +5839,7 @@ class App(QtCore.QObject):
ncctools = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
self.defaults['tools_ncctools'] = ''
for t in range(len(ncctools)):
@ -5817,6 +5852,7 @@ class App(QtCore.QObject):
sptools = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
self.defaults['tools_solderpaste_tools'] = ""
for t in range(len(sptools)):
@ -5836,6 +5872,7 @@ class App(QtCore.QObject):
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
continue
self.defaults[dim] = float('%.*f' % (self.decimals, val))
else:
@ -5844,6 +5881,7 @@ class App(QtCore.QObject):
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
continue
self.defaults[dim] = float('%.*f' % (self.decimals, val))
else:
@ -5852,7 +5890,8 @@ class App(QtCore.QObject):
try:
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
log.debug('App.on_toggle_units().scale_defaults() --> Value: %s %s' % (str(dim), str(e)))
continue
self.defaults[dim] = val
@ -7026,6 +7065,9 @@ class App(QtCore.QObject):
self.inform.emit('%s' % _("Preferences applied."))
# make sure we update the self.current_defaults dict used to undo changes to self.defaults
self.current_defaults.update(self.defaults)
if save_to_file:
self.save_defaults(silent=False)
# load the defaults so they are updated into the app
@ -7064,7 +7106,18 @@ class App(QtCore.QObject):
except TypeError:
pass
self.defaults_write_form()
try:
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect()
except (TypeError, AttributeError):
pass
self.defaults_write_form(source_dict=self.current_defaults)
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
lambda: self.on_toggle_units(no_pref=False))
self.defaults.update(self.current_defaults)
# shared_items = {k: self.defaults[k] for k in self.defaults if k in self.current_defaults and
# self.defaults[k] == self.current_defaults[k]}
# print(len(self.defaults), len(shared_items))
# Preferences save, update the color of the Preferences Tab text
for idx in range(self.ui.plot_tab_area.count()):
@ -7379,7 +7432,8 @@ class App(QtCore.QObject):
dia_box = DialogBoxRadio(title=_("Jump to ..."),
label=_("Enter the coordinates in format X,Y:"),
icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
initial_text=dia_box_location)
initial_text=dia_box_location,
reference=self.defaults['global_jump_ref'])
if dia_box.ok is True:
try:
@ -7393,7 +7447,7 @@ class App(QtCore.QObject):
rel_x = self.mouse[0] + location[0]
rel_y = self.mouse[1] + location[1]
location = (rel_x, rel_y)
self.defaults['global_jump_ref'] = dia_box.reference
except Exception:
return
else:
@ -7793,8 +7847,7 @@ class App(QtCore.QObject):
pass
def on_preferences_edited(self):
self.inform.emit('[WARNING_NOTCL] %s' %
_("Preferences edited but not saved."))
self.inform.emit('[WARNING_NOTCL] %s' % _("Preferences edited but not saved."))
for idx in range(self.ui.plot_tab_area.count()):
if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
@ -9930,7 +9983,7 @@ class App(QtCore.QObject):
def init_code_editor(self, name):
self.text_editor_tab = TextEditor(app=self)
self.text_editor_tab = TextEditor(app=self, plain_text=True)
# add the tab if it was closed
self.ui.plot_tab_area.addTab(self.text_editor_tab, '%s' % name)
@ -10271,19 +10324,26 @@ 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):
def on_file_save_objects_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']))
obj_selection = self.collection.get_selected()
if len(obj_selection) == 1:
obj_name = str(obj_selection[0].options['name'])
else:
obj_name = _("FlatCAM objects print")
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
if not obj_selection:
self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
return
filter_ = "PDF File (*.PDF);; All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
@ -10303,17 +10363,185 @@ class App(QtCore.QObject):
return
if use_thread is True:
self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name]})
proc = self.proc_container.new(_("Printing PDF ... Please wait."))
self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_selection]})
else:
self.save_pdf(filename, obj_name)
self.save_pdf(filename, obj_selection)
# 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 save_pdf(self, file_name, obj_selection):
p_size = self.defaults['global_workspaceT']
orientation = self.defaults['global_workspace_orientation']
color = 'black'
transparency_level = 1.0
self.pagesize = dict()
self.pagesize.update(
{
'Bounds': None,
'A0': (841*mm, 1189*mm),
'A1': (594*mm, 841*mm),
'A2': (420*mm, 594*mm),
'A3': (297*mm, 420*mm),
'A4': (210*mm, 297*mm),
'A5': (148*mm, 210*mm),
'A6': (105*mm, 148*mm),
'A7': (74*mm, 105*mm),
'A8': (52*mm, 74*mm),
'A9': (37*mm, 52*mm),
'A10': (26*mm, 37*mm),
'B0': (1000*mm, 1414*mm),
'B1': (707*mm, 1000*mm),
'B2': (500*mm, 707*mm),
'B3': (353*mm, 500*mm),
'B4': (250*mm, 353*mm),
'B5': (176*mm, 250*mm),
'B6': (125*mm, 176*mm),
'B7': (88*mm, 125*mm),
'B8': (62*mm, 88*mm),
'B9': (44*mm, 62*mm),
'B10': (31*mm, 44*mm),
'C0': (917*mm, 1297*mm),
'C1': (648*mm, 917*mm),
'C2': (458*mm, 648*mm),
'C3': (324*mm, 458*mm),
'C4': (229*mm, 324*mm),
'C5': (162*mm, 229*mm),
'C6': (114*mm, 162*mm),
'C7': (81*mm, 114*mm),
'C8': (57*mm, 81*mm),
'C9': (40*mm, 57*mm),
'C10': (28*mm, 40*mm),
# American paper sizes
'LETTER': (8.5*inch, 11*inch),
'LEGAL': (8.5*inch, 14*inch),
'ELEVENSEVENTEEN': (11*inch, 17*inch),
# From https://en.wikipedia.org/wiki/Paper_size
'JUNIOR_LEGAL': (5*inch, 8*inch),
'HALF_LETTER': (5.5*inch, 8*inch),
'GOV_LETTER': (8*inch, 10.5*inch),
'GOV_LEGAL': (8.5*inch, 13*inch),
'LEDGER': (17*inch, 11*inch),
}
)
exported_svg = list()
for obj in obj_selection:
svg_obj = obj.export_svg(scale_stroke_factor=0.0,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None,
mirror=None)
if obj.kind.lower() == 'gerber':
color = self.defaults["global_plot_fill"][:-2]
elif obj.kind.lower() == 'excellon':
color = '#C40000'
elif obj.kind.lower() == 'geometry':
color = self.defaults["global_draw_color"]
# Change the attributes of the exported SVG
# We don't need stroke-width
# We set opacity to maximum
# We set the colour to WHITE
root = ET.fromstring(svg_obj)
for child in root:
child.set('fill', str(color))
child.set('opacity', str(transparency_level))
child.set('stroke', str(color))
exported_svg.append(ET.tostring(root))
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
for obj in obj_selection:
try:
gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
except Exception as e:
log.warning("DEV WARNING: Tried to get bounds of empty geometry in App.save_pdf(). %s" % str(e))
# Determine bounding area for svg export
bounds = [xmin, ymin, xmax, ymax]
size = bounds[2] - bounds[0], bounds[3] - bounds[1]
# This contain the measure units
uom = obj_selection[0].units.lower()
# Define a boundary around SVG of about 1.0mm (~39mils)
if uom in "mm":
boundary = 1.0
else:
boundary = 0.0393701
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0] + (2 * boundary))
svgheight = str(size[1] + (2 * boundary))
minx = str(bounds[0] - boundary)
miny = str(bounds[1] + boundary + size[1])
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders
# properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
svg_header += '>'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
svg_elem = str(svg_header)
for svg_item in exported_svg:
svg_elem += str(svg_item)
svg_elem += str(svg_footer)
# Parse the xml through a xml parser just to add line feeds
# and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
doc_final = doc.toprettyxml()
try:
if self.defaults['units'].upper() == 'IN':
unit = inch
else:
unit = mm
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
if p_size == 'Bounds':
renderPDF.drawToFile(drawing, file_name)
else:
if orientation == 'p':
page_size = portrait(self.pagesize[p_size])
else:
page_size = landscape(self.pagesize[p_size])
my_canvas = canvas.Canvas(file_name, pagesize=page_size)
my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
renderPDF.draw(drawing, my_canvas, 0, 0)
my_canvas.save()
except Exception as e:
log.debug("App.save_pdf() --> PDF output --> %s" % str(e))
return 'fail'
self.inform.emit('[success] %s: %s' % (_("PDF file saved to"), file_name))
def export_svg(self, obj_name, filename, scale_stroke_factor=0.00):
"""
@ -11261,6 +11489,7 @@ class App(QtCore.QObject):
# Project options
self.options.update(d['options'])
self.project_filename = filename
# for some reason, setting ui_title does not work when this method is called from Tcl Shell
@ -11273,6 +11502,7 @@ class App(QtCore.QObject):
for obj in d['objs']:
def obj_init(obj_inst, app_inst):
obj_inst.from_dict(obj)
App.log.debug("Recreating from opened project an %s object: %s" %

View File

@ -981,6 +981,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
if isinstance(self.solid_geometry, list):
self.solid_geometry = cascaded_union(self.solid_geometry)
bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
if not self.options["noncopperrounded"]:
bounding_box = bounding_box.envelope
@ -5173,8 +5176,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if self.tools[tooluid_key]['solid_geometry'] is None:
a += 1
if a == len(self.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'
for tooluid_key in list(tools_dict.keys()):
@ -5292,10 +5294,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if use_thread:
# To be run in separate thread
# The idea is that if there is a solid_geometry in the file "root" then most likely thare are no
# separate solid_geometry in the self.tools dictionary
def job_thread(app_obj):
if self.solid_geometry:
if self.multigeo is False:
with self.app.proc_container.new(_("Generating CNC Code")):
if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))

View File

@ -9,6 +9,31 @@ CAD program, and create G-Code for Isolation routing.
=================================================
20.12.2019
- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab)
- Print function is now printing a PDF file for a selection of objects in the colors from canvas
19.12.2019
- in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid
- in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string
- added Preferences values for PDF margins when saving text in Code Editor as PDF
- when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values
- starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection
- added shortcut key CTRL+P for printing to PDF method
18.12.2019
- added new parameters to improve Gerber parsing
- small optimizations in the Preferences UI
- the Jump To function reference is now saving it's last used value
- added the ability to use the Jump To method in the Gerber Editor
- improved the loading of Config File by using the advanced code editor
- fixed a bug in the new feature 'extra buffering'
- fixed the creation of CNCJob objects out of multigeo Geometry objects (objects with multiple tools)
- optimized the NCC Tool
17.12.2019
- more optimizations in NCC Tool
@ -21,6 +46,7 @@ CAD program, and create G-Code for Isolation routing.
- added ability to save the Source File as PDF - fixed page size and added line breaks
- more mods to generate_from_geometry_2() method
- fixed bug saving the FlatCAM project saying the file is used by another application
- fixed issue #347 - a Gerber generated by Sprint Layout with copper pour ON will not have rendered the copper pour
16.12.2019

View File

@ -1880,7 +1880,6 @@ class DrawTool(object):
return ""
def on_key(self, key):
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.draw_app.app.on_jump_to()

View File

@ -139,7 +139,9 @@ class DrawTool(object):
return ""
def on_key(self, key):
return None
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.draw_app.app.on_jump_to()
def utility_geometry(self, data=None):
return None
@ -874,9 +876,11 @@ class FCRegion(FCShapeTool):
except Exception as e:
log.debug("FlatCAMGrbEditor.FCRegion --> %s" % str(e))
self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero.png'))
self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
self.draw_app.app.inform.emit(_('Corner Mode 1: 45 degrees ...'))
self.start_msg = _("Click on 1st point ...")
@ -1064,8 +1068,10 @@ class FCRegion(FCShapeTool):
self.geometry = DrawToolShape(new_geo_el)
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit('[success] %s' %
_("Done."))
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
@ -1073,6 +1079,10 @@ class FCRegion(FCShapeTool):
self.draw_app.plot_all()
def on_key(self, key):
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.draw_app.app.on_jump_to()
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
if self.draw_app.bend_mode == 5:
@ -1148,9 +1158,12 @@ class FCTrack(FCRegion):
except Exception as e:
log.debug("FlatCAMGrbEditor.FCTrack.__init__() --> %s" % str(e))
self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_path%s.png' % self.draw_app.bend_mode))
self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location +
'/aero_path%s.png' % self.draw_app.bend_mode))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
def make(self):
@ -1168,8 +1181,10 @@ class FCTrack(FCRegion):
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit('[success] %s' %
_("Done."))
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
@ -1287,6 +1302,10 @@ class FCTrack(FCRegion):
self.draw_app.draw_utility_geometry(geo=geo)
return _("Backtracked one point ...")
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.draw_app.app.on_jump_to()
if key == 'T' or key == QtCore.Qt.Key_T:
try:
QtGui.QGuiApplication.restoreOverrideCursor()
@ -1396,6 +1415,8 @@ class FCDisc(FCShapeTool):
self.draw_app.app.inform.emit(_("Click on Center point ..."))
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
self.steps_per_circ = self.draw_app.app.defaults["gerber_circle_steps"]
def click(self, point):
@ -1442,8 +1463,10 @@ class FCDisc(FCShapeTool):
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit('[success] %s' %
_("Done."))
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
@ -1490,6 +1513,7 @@ class FCSemiDisc(FCShapeTool):
self.storage_obj = self.draw_app.storage_dict['0']['geometry']
self.steps_per_circ = self.draw_app.app.defaults["gerber_circle_steps"]
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
def click(self, point):
self.points.append(point)
@ -1523,6 +1547,10 @@ class FCSemiDisc(FCShapeTool):
self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
return '%s: %s' % (_('Direction'), self.direction.upper())
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
self.draw_app.app.on_jump_to()
if key == 'M' or key == QtCore.Qt.Key_M:
# delete the possible points made before this action; we want to start anew
self.points = []
@ -1700,8 +1728,10 @@ class FCSemiDisc(FCShapeTool):
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit('[success] %s' %
_("Done."))
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
@ -4517,6 +4547,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.snap_x = x
self.snap_y = y
self.app.mouse = [x, y]
# update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (x, y))
@ -4594,19 +4626,21 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.clear(update=True)
for storage in self.storage_dict:
for elem in self.storage_dict[storage]['geometry']:
if 'solid' in elem.geo:
geometric_data = elem.geo['solid']
if geometric_data is None:
continue
# fix for apertures with now geometry inside
if 'geometry' in self.storage_dict[storage]:
for elem in self.storage_dict[storage]['geometry']:
if 'solid' in elem.geo:
geometric_data = elem.geo['solid']
if geometric_data is None:
continue
if elem in self.selected:
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_sel_draw_color'] + 'FF',
linewidth=2)
else:
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_draw_color'] + 'FF')
if elem in self.selected:
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_sel_draw_color'] + 'FF',
linewidth=2)
else:
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_draw_color'] + 'FF')
if self.utility:
for elem in self.utility:

View File

@ -196,7 +196,7 @@ class TextEditor(QtWidgets.QWidget):
_filter_ = filt
else:
_filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
"All Files (*.*)"
"PDF Files (*.pdf);;All Files (*.*)"
if name:
obj_name = name
@ -206,7 +206,7 @@ class TextEditor(QtWidgets.QWidget):
except AttributeError:
obj_name = 'file'
if filt is None:
_filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
_filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
try:
filename = str(QtWidgets.QFileDialog.getSaveFileName(
@ -237,13 +237,24 @@ class TextEditor(QtWidgets.QWidget):
styleH = styles['Heading1']
story = []
if self.app.defaults['units'].lower() == 'mm':
bmargin = self.app.defaults['global_tpdf_bmargin'] * mm
tmargin = self.app.defaults['global_tpdf_tmargin'] * mm
rmargin = self.app.defaults['global_tpdf_rmargin'] * mm
lmargin = self.app.defaults['global_tpdf_lmargin'] * mm
else:
bmargin = self.app.defaults['global_tpdf_bmargin'] * inch
tmargin = self.app.defaults['global_tpdf_tmargin'] * inch
rmargin = self.app.defaults['global_tpdf_rmargin'] * inch
lmargin = self.app.defaults['global_tpdf_lmargin'] * inch
doc = SimpleDocTemplate(
filename,
pagesize=page_size,
bottomMargin=0.4 * inch,
topMargin=0.6 * inch,
rightMargin=0.8 * inch,
leftMargin=0.8 * inch)
bottomMargin=bmargin,
topMargin=tmargin,
rightMargin=rmargin,
leftMargin=lmargin)
P = Paragraph(lined_gcode, styleN)
story.append(P)

View File

@ -240,6 +240,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# Separator
self.menufile.addSeparator()
self.menufile_print = QtWidgets.QAction(
QtGui.QIcon(self.app.resource_location + '/printer32.png'), '%s\tCTRL+P' % _('Print (PDF)'))
self.menufile.addAction(self.menufile_print)
self.menufile_save = self.menufile.addMenu(QtGui.QIcon(self.app.resource_location + '/save_as.png'), _('Save'))
@ -260,11 +263,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.menufile_save.addSeparator()
# Save Object PDF
self.menufilesave_object_pdf = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/pdf32.png'),
_('Save Object as PDF ...'), self)
self.menufile_save.addAction(self.menufilesave_object_pdf)
# Separator
self.menufile.addSeparator()
@ -1381,18 +1379,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>CTRL+G</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+N</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+M</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+N</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+O</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+P</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>CTRL+Q</strong></td>
<td>&nbsp;%s</td>
@ -1579,8 +1581,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# CTRL section
_("Select All"), _("Copy Obj"), _("Open Tools Database"),
_("Open Excellon File"), _("Open Gerber File"), _("New Project"), _("Distance Tool"),
_("Open Project"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"),
_("Open Excellon File"), _("Open Gerber File"), _("Distance Tool"), _("New Project"),
_("Open Project"), _("Print (PDF)"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"),
# SHIFT section
_("Copy Obj_Name"),
@ -2671,18 +2673,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_G:
self.app.on_fileopengerber()
# Create New Project
if key == QtCore.Qt.Key_N:
self.app.on_file_new_click()
# Distance Tool
if key == QtCore.Qt.Key_M:
self.app.distance_tool.run()
# Create New Project
if key == QtCore.Qt.Key_N:
self.app.on_file_new_click()
# Open Project
if key == QtCore.Qt.Key_O:
self.app.on_file_openproject()
# Open Project
if key == QtCore.Qt.Key_P:
self.app.on_file_save_objects_pdf(use_thread=True)
# PDF Import
if key == QtCore.Qt.Key_Q:
self.app.pdf_tool.run()

View File

@ -2239,7 +2239,7 @@ class Dialog_box(QtWidgets.QWidget):
class DialogBoxRadio(QtWidgets.QDialog):
def __init__(self, title=None, label=None, icon=None, initial_text=None):
def __init__(self, title=None, label=None, icon=None, initial_text=None, reference='abs'):
"""
:param title: string with the window title
@ -2258,11 +2258,6 @@ class DialogBoxRadio(QtWidgets.QDialog):
self.form = QtWidgets.QFormLayout(self)
self.form.addRow(QtWidgets.QLabel(''))
self.wdg_label = QtWidgets.QLabel('<b>%s</b>' % str(label))
self.form.addRow(self.wdg_label)
self.ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
self.ref_label.setToolTip(
_("The reference can be:\n"
@ -2273,10 +2268,15 @@ class DialogBoxRadio(QtWidgets.QDialog):
{"label": _("Abs"), "value": "abs"},
{"label": _("Relative"), "value": "rel"}
], orientation='horizontal', stretch=False)
self.ref_radio.set_value('abs')
self.ref_radio.set_value(reference)
self.form.addRow(self.ref_label, self.ref_radio)
self.loc_label = QtWidgets.QLabel('<b>%s:</b>' % _("Location"))
self.form.addRow(QtWidgets.QLabel(''))
self.wdg_label = QtWidgets.QLabel('<b>%s</b>' % str(label))
self.form.addRow(self.wdg_label)
self.loc_label = QtWidgets.QLabel('%s:' % _("Location"))
self.loc_label.setToolTip(
_("The Location value is a tuple (x,y).\n"
"If the reference is Absolute then the Jump will be at the position (x,y).\n"

View File

@ -1330,6 +1330,76 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.machinist_cb, 21, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.layout.addWidget(separator_line)
self.layout.addWidget(QtWidgets.QLabel(''))
grid1 = QtWidgets.QGridLayout()
self.layout.addLayout(grid1)
grid1.setColumnStretch(0, 0)
grid1.setColumnStretch(1, 1)
self.pdf_param_label = QtWidgets.QLabel('<B>%s:</b>' % _("Text to PDF parameters"))
self.pdf_param_label.setToolTip(
_("Used when saving text in Code Editor or in FlatCAM Document objects.")
)
grid1.addWidget(self.pdf_param_label, 0, 0, 1, 2)
# Top Margin value
self.tmargin_entry = FCDoubleSpinner()
self.tmargin_entry.set_precision(self.decimals)
self.tmargin_entry.set_range(0.0000, 9999.9999)
self.tmargin_label = QtWidgets.QLabel('%s:' % _("Top Margin"))
self.tmargin_label.setToolTip(
_("Distance between text body and the top of the PDF file.")
)
grid1.addWidget(self.tmargin_label, 1, 0)
grid1.addWidget(self.tmargin_entry, 1, 1)
# Bottom Margin value
self.bmargin_entry = FCDoubleSpinner()
self.bmargin_entry.set_precision(self.decimals)
self.bmargin_entry.set_range(0.0000, 9999.9999)
self.bmargin_label = QtWidgets.QLabel('%s:' % _("Bottom Margin"))
self.bmargin_label.setToolTip(
_("Distance between text body and the bottom of the PDF file.")
)
grid1.addWidget(self.bmargin_label, 2, 0)
grid1.addWidget(self.bmargin_entry, 2, 1)
# Left Margin value
self.lmargin_entry = FCDoubleSpinner()
self.lmargin_entry.set_precision(self.decimals)
self.lmargin_entry.set_range(0.0000, 9999.9999)
self.lmargin_label = QtWidgets.QLabel('%s:' % _("Left Margin"))
self.lmargin_label.setToolTip(
_("Distance between text body and the left of the PDF file.")
)
grid1.addWidget(self.lmargin_label, 3, 0)
grid1.addWidget(self.lmargin_entry, 3, 1)
# Right Margin value
self.rmargin_entry = FCDoubleSpinner()
self.rmargin_entry.set_precision(self.decimals)
self.rmargin_entry.set_range(0.0000, 9999.9999)
self.rmargin_label = QtWidgets.QLabel('%s:' % _("Right Margin"))
self.rmargin_label.setToolTip(
_("Distance between text body and the right of the PDF file.")
)
grid1.addWidget(self.rmargin_label, 4, 0)
grid1.addWidget(self.rmargin_entry, 4, 1)
self.layout.addStretch()
if sys.platform != 'win32':
@ -1435,6 +1505,29 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.gerber_zeros_label, 5, 0)
grid0.addWidget(self.gerber_zeros_radio, 5, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 6, 0, 1, 3)
# Apertures Cleaning
self.gerber_clean_cb = FCCheckBox(label='%s' % _('Clean Apertures'))
self.gerber_clean_cb.setToolTip(
_("Will remove apertures that do not have geometry\n"
"thus lowering the number of apertures in the Gerber object.")
)
grid0.addWidget(self.gerber_clean_cb, 7, 0, 1, 3)
# Apply Extra Buffering
self.gerber_extra_buffering = FCCheckBox(label='%s' % _('Polarity change buffer'))
self.gerber_extra_buffering.setToolTip(
_("Will apply extra buffering for the\n"
"solid geometry when we have polarity changes.\n"
"May help loading Gerber files that otherwise\n"
"do not load correctly.")
)
grid0.addWidget(self.gerber_extra_buffering, 8, 0, 1, 3)
self.layout.addStretch()
@ -1528,6 +1621,11 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.combine_passes_cb, 5, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 6, 0, 1, 2)
# ## Clear non-copper regions
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
self.clearcopper_label.setToolTip(
@ -1564,6 +1662,11 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
)
grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line, 2, 0, 1, 2)
# ## Bounding box
self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
self.layout.addWidget(self.boundingbox_label)
@ -1634,6 +1737,11 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.aperture_table_visibility_cb, 1, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 2, 0, 1, 2)
# Tool Type
self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
self.tool_type_label.setToolTip(
@ -1645,8 +1753,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
{'label': 'V-Shape', 'value': 'v'}])
grid0.addWidget(self.tool_type_label, 2, 0)
grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
grid0.addWidget(self.tool_type_label, 3, 0)
grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@ -1658,8 +1766,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.tipdia_spinner.set_range(-99.9999, 99.9999)
self.tipdia_spinner.setSingleStep(0.1)
self.tipdia_spinner.setWrapping(True)
grid0.addWidget(self.tipdialabel, 3, 0)
grid0.addWidget(self.tipdia_spinner, 3, 1, 1, 2)
grid0.addWidget(self.tipdialabel, 4, 0)
grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@ -1671,8 +1779,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.tipangle_spinner.set_range(0, 180)
self.tipangle_spinner.setSingleStep(5)
self.tipangle_spinner.setWrapping(True)
grid0.addWidget(self.tipanglelabel, 4, 0)
grid0.addWidget(self.tipangle_spinner, 4, 1, 1, 2)
grid0.addWidget(self.tipanglelabel, 5, 0)
grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
# Cut Z
self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@ -1686,8 +1794,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.cutz_spinner.setSingleStep(0.1)
self.cutz_spinner.setWrapping(True)
grid0.addWidget(self.cutzlabel, 5, 0)
grid0.addWidget(self.cutz_spinner, 5, 1, 1, 2)
grid0.addWidget(self.cutzlabel, 6, 0)
grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
@ -1705,8 +1813,13 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
{'label': _('Exterior'), 'value': 'ext'},
{'label': _('Interior'), 'value': 'int'}])
grid0.addWidget(self.iso_type_label, 6, 0,)
grid0.addWidget(self.iso_type_radio, 6, 1, 1, 2)
grid0.addWidget(self.iso_type_label, 7, 0,)
grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 8, 0, 1, 2)
# Buffering Type
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
@ -1718,8 +1831,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
)
self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
{'label': _('Full'), 'value': 'full'}])
grid0.addWidget(buffering_label, 7, 0)
grid0.addWidget(self.buffering_radio, 7, 1)
grid0.addWidget(buffering_label, 9, 0)
grid0.addWidget(self.buffering_radio, 9, 1)
# Simplification
self.simplify_cb = FCCheckBox(label=_('Simplify'))
@ -1728,7 +1841,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
"loaded with simplification having a set tolerance.\n"
"<<WARNING>>: Don't change this unless you know what you are doing !!!")
)
grid0.addWidget(self.simplify_cb, 8, 0, 1, 2)
grid0.addWidget(self.simplify_cb, 10, 0, 1, 2)
# Simplification tolerance
self.simplification_tol_label = QtWidgets.QLabel(_('Tolerance'))
@ -1740,8 +1853,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.simplification_tol_spinner.setRange(0.00000, 0.01000)
self.simplification_tol_spinner.setSingleStep(0.0001)
grid0.addWidget(self.simplification_tol_label, 9, 0)
grid0.addWidget(self.simplification_tol_spinner, 9, 1)
grid0.addWidget(self.simplification_tol_label, 11, 0)
grid0.addWidget(self.simplification_tol_spinner, 11, 1)
self.ois_simplif = OptionalInputSection(
self.simplify_cb,
[

View File

@ -72,6 +72,8 @@ class Gerber(Geometry):
# "use_buffer_for_union": True
# }
app = None
def __init__(self, steps_per_circle=None):
"""
The constructor takes no parameters. Use ``gerber.parse_files()``
@ -465,11 +467,12 @@ class Gerber(Geometry):
geo_dict['follow'] = geo_f
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo_s.is_empty:
if not geo_s.is_empty and geo_s.is_valid:
if self.app.defaults['gerber_simplification']:
poly_buffer.append(geo_s.simplify(s_tol))
else:
poly_buffer.append(geo_s)
if self.is_lpc is True:
geo_dict['clear'] = geo_s
else:
@ -1430,6 +1433,18 @@ class Gerber(Geometry):
self.solid_geometry = final_poly
# FIX for issue #347 - Sprint Layout generate strange Gerber files when the copper pour is enabled
# it use a filled bounding box polygon to which add clear polygons (negative) to isolate the copper
# features
if self.app.defaults['gerber_extra_buffering']:
candidate_geo = list()
try:
for p in self.solid_geometry:
candidate_geo.append(p.buffer(0.0000001))
except TypeError:
candidate_geo.append(self.solid_geometry.buffer(0.0000001))
self.solid_geometry = candidate_geo
# try:
# self.solid_geometry = self.solid_geometry.union(new_poly)
# except Exception as e:
@ -1442,6 +1457,12 @@ class Gerber(Geometry):
else:
self.solid_geometry = self.solid_geometry.difference(new_poly)
if self.app.defaults['gerber_clean_apertures']:
# clean the Gerber file of apertures with no geometry
for apid, apvalue in list(self.apertures.items()):
if 'geometry' not in apvalue:
self.apertures.pop(apid)
# init this for the following operations
self.conversion_done = False
except Exception as err:

View File

@ -2,9 +2,11 @@
from PyQt5 import QtWidgets, QtCore
from FlatCAMTool import FlatCAMTool
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry
from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry
from numpy import Inf
from shapely.geometry import Point
from shapely import affinity
@ -219,6 +221,11 @@ class DblSidedTool(FlatCAMTool):
self.box_combo.hide()
self.box_combo_type.hide()
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid_lay2.addWidget(separator_line, 12, 0, 1, 2)
# ## Alignment holes
self.ah_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Coordinates'))
self.ah_label.setToolTip(
@ -272,14 +279,14 @@ class DblSidedTool(FlatCAMTool):
# Drill diameter value
self.drill_dia = FCDoubleSpinner()
self.drill_dia.set_precision(self.decimals)
self.drill_dia.set_range(0.0000, 9999.9999)
self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
self.dd_label.setToolTip(
self.drill_dia.setToolTip(
_("Diameter of the drill for the "
"alignment holes.")
)
grid0.addWidget(self.dd_label, 1, 0)
grid0.addWidget(self.drill_dia, 1, 1)
grid0.addWidget(self.drill_dia, 1, 0, 1, 2)
# ## Buttons
self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
@ -296,6 +303,102 @@ class DblSidedTool(FlatCAMTool):
""")
self.layout.addWidget(self.create_alignment_hole_button)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.layout.addWidget(separator_line)
self.layout.addWidget(QtWidgets.QLabel(''))
grid1 = QtWidgets.QGridLayout()
self.layout.addLayout(grid1)
grid1.setColumnStretch(0, 0)
grid1.setColumnStretch(1, 1)
# Xmin value
self.xmin_entry = FCDoubleSpinner()
self.xmin_entry.set_precision(self.decimals)
self.xmin_entry.set_range(-9999.9999, 9999.9999)
self.xmin_label = QtWidgets.QLabel('%s:' % _("X min"))
self.xmin_label.setToolTip(
_("Minimum location.")
)
self.xmin_entry.setReadOnly(True)
grid1.addWidget(self.xmin_label, 1, 0)
grid1.addWidget(self.xmin_entry, 1, 1)
# Ymin value
self.ymin_entry = FCDoubleSpinner()
self.ymin_entry.set_precision(self.decimals)
self.ymin_entry.set_range(-9999.9999, 9999.9999)
self.ymin_label = QtWidgets.QLabel('%s:' % _("Y min"))
self.ymin_label.setToolTip(
_("Minimum location.")
)
self.ymin_entry.setReadOnly(True)
grid1.addWidget(self.ymin_label, 2, 0)
grid1.addWidget(self.ymin_entry, 2, 1)
# Xmax value
self.xmax_entry = FCDoubleSpinner()
self.xmax_entry.set_precision(self.decimals)
self.xmax_entry.set_range(-9999.9999, 9999.9999)
self.xmax_label = QtWidgets.QLabel('%s:' % _("X max"))
self.xmax_label.setToolTip(
_("Maximum location.")
)
self.xmax_entry.setReadOnly(True)
grid1.addWidget(self.xmax_label, 3, 0)
grid1.addWidget(self.xmax_entry, 3, 1)
# Ymax value
self.ymax_entry = FCDoubleSpinner()
self.ymax_entry.set_precision(self.decimals)
self.ymax_entry.set_range(-9999.9999, 9999.9999)
self.ymax_label = QtWidgets.QLabel('%s:' % _("Y max"))
self.ymax_label.setToolTip(
_("Maximum location.")
)
self.ymax_entry.setReadOnly(True)
grid1.addWidget(self.ymax_label, 4, 0)
grid1.addWidget(self.ymax_entry, 4, 1)
# Center point value
self.center_entry = FCEntry()
self.center_label = QtWidgets.QLabel('%s:' % _("Centroid"))
self.center_label.setToolTip(
_("The center point location for the rectangular\n"
"bounding shape. Centroid. Format is (x, y).")
)
self.center_entry.setReadOnly(True)
grid1.addWidget(self.center_label, 5, 0)
grid1.addWidget(self.center_entry, 5, 1)
# Calculate Bounding box
self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounds Values"))
self.calculate_bb_button.setToolTip(
_("Calculate the enveloping rectangular shape coordinates,\n"
"for the selection of objects.\n"
"The envelope shape is parallel with the X, Y axis.")
)
self.calculate_bb_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.layout.addWidget(self.calculate_bb_button)
self.layout.addStretch()
# ## Reset Tool
@ -312,18 +415,20 @@ class DblSidedTool(FlatCAMTool):
self.layout.addWidget(self.reset_button)
# ## Signals
self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
self.mirror_gerber_button.clicked.connect(self.on_mirror_gerber)
self.mirror_exc_button.clicked.connect(self.on_mirror_exc)
self.mirror_geo_button.clicked.connect(self.on_mirror_geo)
self.add_point_button.clicked.connect(self.on_point_add)
self.add_drill_point_button.clicked.connect(self.on_drill_add)
self.reset_button.clicked.connect(self.reset_fields)
self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
self.axis_location.group_toggle_fn = self.on_toggle_pointbox
self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
self.reset_button.clicked.connect(self.set_tool_ui)
self.drill_values = ""
def install(self, icon=None, separator=None, **kwargs):
@ -366,6 +471,12 @@ class DblSidedTool(FlatCAMTool):
self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
self.xmin_entry.set_value(0.0)
self.ymin_entry.set_value(0.0)
self.xmax_entry.set_value(0.0)
self.ymax_entry.set_value(0.0)
self.center_entry.set_value('')
def on_combo_box_type(self):
obj_type = self.box_combo_type.currentIndex()
self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
@ -589,6 +700,41 @@ class DblSidedTool(FlatCAMTool):
self.box_combo_type.show()
self.add_point_button.setDisabled(True)
def on_bbox_coordinates(self):
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
obj_list = self.app.collection.get_selected()
if not obj_list:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
return
for obj in obj_list:
try:
gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
except Exception as e:
log.warning("DEV WARNING: Tried to get bounds of empty geometry in DblSidedTool. %s" % str(e))
self.xmin_entry.set_value(xmin)
self.ymin_entry.set_value(ymin)
self.xmax_entry.set_value(xmax)
self.ymax_entry.set_value(ymax)
cx = '%.*f' % (self.decimals, (((xmax - xmin) / 2.0) + xmin))
cy = '%.*f' % (self.decimals, (((ymax - ymin) / 2.0) + ymin))
val_txt = '(%s, %s)' % (cx, cy)
self.center_entry.set_value(val_txt)
self.axis_location.set_value('point')
self.point_entry.set_value(val_txt)
def reset_fields(self):
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))

View File

@ -1723,11 +1723,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
sol_geo = cascaded_union(isolated_geo)
if has_offset is True:
app_obj.inform.emit('[WARNING_NOTCL] %s ...' %
_("Buffering"))
app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
app_obj.inform.emit('[success] %s ...' %
_("Buffering finished"))
app_obj.inform.emit('[success] %s ...' % _("Buffering finished"))
empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
if empty == 'fail':
return 'fail'
@ -1760,6 +1758,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
log.debug("NCC Tool. Finished calculation of 'empty' area.")
self.app.inform.emit(_("NCC Tool. Finished calculation of 'empty' area."))
# COPPER CLEARING #
cp = None
for tool in sorted_tools:
log.debug("Starting geometry processing for tool: %s" % str(tool))
@ -1916,17 +1915,27 @@ class NonCopperClear(FlatCAMTool, Gerber):
if self.app.defaults["tools_ncc_plotting"] == 'progressive':
self.temp_shapes.clear(update=True)
# delete tools with empty geometry
keys_to_delete = []
# look for keys in the tools_storage dict that have 'solid_geometry' values empty
for uid in tools_storage:
# if the solid_geometry (type=list) is empty
if not tools_storage[uid]['solid_geometry']:
keys_to_delete.append(uid)
# # delete tools with empty geometry
# keys_to_delete = []
# # look for keys in the tools_storage dict that have 'solid_geometry' values empty
# for uid in tools_storage:
# # if the solid_geometry (type=list) is empty
# if not tools_storage[uid]['solid_geometry']:
# keys_to_delete.append(uid)
#
# # actual delete of keys from the tools_storage dict
# for k in keys_to_delete:
# tools_storage.pop(k, None)
# actual delete of keys from the tools_storage dict
for k in keys_to_delete:
tools_storage.pop(k, None)
# delete tools with empty geometry
# look for keys in the tools_storage dict that have 'solid_geometry' values empty
for uid, uid_val in list(tools_storage.items()):
try:
# if the solid_geometry (type=list) is empty
if not uid_val['solid_geometry']:
tools_storage.pop(uid, None)
except KeyError:
tools_storage.pop(uid, None)
geo_obj.options["cnctooldia"] = str(tool)

BIN
share/printer16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

BIN
share/printer32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B