- added a new FlatCAM Tool: Gerber Invert Tool. It will invert the copper features in a Gerber file: where is copper there will be empty and where is empty it will be copper

This commit is contained in:
Marius Stanciu 2020-02-14 17:08:06 +02:00
parent 6926b5be65
commit 9fc2ba8ffd
12 changed files with 324 additions and 27 deletions

View File

@ -14,7 +14,7 @@ import getopt
import random import random
import simplejson as json import simplejson as json
import lzma import lzma
import threading # import threading
import shutil import shutil
import stat import stat
@ -26,7 +26,7 @@ import ctypes
from reportlab.graphics import renderPDF from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.graphics import renderPM # from reportlab.graphics import renderPM
from reportlab.lib.units import inch, mm from reportlab.lib.units import inch, mm
from reportlab.lib.pagesizes import landscape, portrait from reportlab.lib.pagesizes import landscape, portrait
from svglib.svglib import svg2rlg from svglib.svglib import svg2rlg
@ -39,9 +39,9 @@ from xml.dom.minidom import parseString as parse_xml_string
from multiprocessing.connection import Listener, Client from multiprocessing.connection import Listener, Client
from multiprocessing import Pool from multiprocessing import Pool
import socket import socket
from array import array # from array import array
import vispy.scene as scene # import vispy.scene as scene
# ####################################### # #######################################
# # Imports part of FlatCAM ## # # Imports part of FlatCAM ##
@ -1306,7 +1306,7 @@ class App(QtCore.QObject):
# Excellon Options # Excellon Options
"excellon_drillz": self.ui.excellon_defaults_form.excellon_opt_group.cutz_entry, "excellon_drillz": self.ui.excellon_defaults_form.excellon_opt_group.cutz_entry,
"excellon_multidepth": self.ui.excellon_defaults_form.excellon_opt_group.mpass_cb, "excellon_multidepth": self.ui.excellon_defaults_form.excellon_opt_group.mpass_cb,
"excellon_depthperpass":self.ui.excellon_defaults_form.excellon_opt_group.maxdepth_entry, "excellon_depthperpass": self.ui.excellon_defaults_form.excellon_opt_group.maxdepth_entry,
"excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry, "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
"excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.endz_entry, "excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.endz_entry,
"excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_z_entry, "excellon_feedrate": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_z_entry,
@ -2558,6 +2558,7 @@ class App(QtCore.QObject):
self.edrills_tool = None self.edrills_tool = None
self.align_objects_tool = None self.align_objects_tool = None
self.punch_tool = None self.punch_tool = None
self.invert_tool = None
# always install tools only after the shell is initialized because the self.inform.emit() depends on shell # always install tools only after the shell is initialized because the self.inform.emit() depends on shell
try: try:
@ -2724,6 +2725,8 @@ class App(QtCore.QObject):
# this holds a widget that is installed in the Plot Area when View Source option is used # this holds a widget that is installed in the Plot Area when View Source option is used
self.source_editor_tab = None self.source_editor_tab = None
self.pagesize = dict()
# Storage for shapes, storage that can be used by FlatCAm tools for utility geometry # Storage for shapes, storage that can be used by FlatCAm tools for utility geometry
# VisPy visuals # VisPy visuals
if self.is_legacy is False: if self.is_legacy is False:
@ -3194,6 +3197,9 @@ class App(QtCore.QObject):
self.punch_tool = ToolPunchGerber(self) self.punch_tool = ToolPunchGerber(self)
self.punch_tool.install(icon=QtGui.QIcon(self.resource_location + '/punch32.png'), pos=self.ui.menutool) self.punch_tool.install(icon=QtGui.QIcon(self.resource_location + '/punch32.png'), pos=self.ui.menutool)
self.invert_tool = ToolInvertGerber(self)
self.invert_tool.install(icon=QtGui.QIcon(self.resource_location + '/invert32.png'), pos=self.ui.menutool)
self.transform_tool = ToolTransform(self) self.transform_tool = ToolTransform(self)
self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'), self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'),
pos=self.ui.menuoptions, separator=True) pos=self.ui.menuoptions, separator=True)
@ -3338,6 +3344,7 @@ class App(QtCore.QObject):
self.ui.copperfill_btn.triggered.connect(lambda: self.copper_thieving_tool.run(toggle=True)) self.ui.copperfill_btn.triggered.connect(lambda: self.copper_thieving_tool.run(toggle=True))
self.ui.fiducials_btn.triggered.connect(lambda: self.fiducial_tool.run(toggle=True)) self.ui.fiducials_btn.triggered.connect(lambda: self.fiducial_tool.run(toggle=True))
self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True)) self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True))
self.ui.invert_btn.triggered.connect(lambda: self.invert_tool.run(toggle=True))
def object2editor(self): def object2editor(self):
""" """
@ -8716,14 +8723,14 @@ class App(QtCore.QObject):
else: else:
event_pos = (event.xdata, event.ydata) event_pos = (event.xdata, event.ydata)
# Matplotlib has the middle and right buttons mapped in reverse compared with VisPy # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
pan_button = 3 if self.defaults["global_pan_button"] == '2'else 2 pan_button = 3 if self.defaults["global_pan_button"] == '2' else 2
# So it can receive key presses # So it can receive key presses
self.plotcanvas.native.setFocus() self.plotcanvas.native.setFocus()
self.pos_canvas = self.plotcanvas.translate_coords(event_pos) self.pos_canvas = self.plotcanvas.translate_coords(event_pos)
if self.grid_status() == True: if self.grid_status():
self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1]) self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
else: else:
self.pos = (self.pos_canvas[0], self.pos_canvas[1]) self.pos = (self.pos_canvas[0], self.pos_canvas[1])

View File

@ -2164,14 +2164,17 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
gerber_code += 'D02*\n' gerber_code += 'D02*\n'
gerber_code += 'G37*\n' gerber_code += 'G37*\n'
gerber_code += '%LPD*%\n' gerber_code += '%LPD*%\n'
except Exception as e:
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() '0' aperture --> %s" % str(e))
for apid in self.apertures: for apid in self.apertures:
if apid == '0': if apid == '0':
continue continue
else: else:
gerber_code += 'D%s*\n' % str(apid) gerber_code += 'D%s*\n' % str(apid)
if 'geometry' in self.apertures[apid]: if 'geometry' in self.apertures[apid]:
for geo_elem in self.apertures[apid]['geometry']: for geo_elem in self.apertures[apid]['geometry']:
try:
if 'follow' in geo_elem: if 'follow' in geo_elem:
geo = geo_elem['follow'] geo = geo_elem['follow']
if not geo.is_empty: if not geo.is_empty:
@ -2212,7 +2215,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
prev_coord = coord prev_coord = coord
# gerber_code += "D02*\n" # gerber_code += "D02*\n"
except Exception as e:
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'follow' --> %s" % str(e))
try:
if 'clear' in geo_elem: if 'clear' in geo_elem:
gerber_code += '%LPC*%\n' gerber_code += '%LPC*%\n'
@ -2256,9 +2262,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
prev_coord = coord prev_coord = coord
# gerber_code += "D02*\n" # gerber_code += "D02*\n"
gerber_code += '%LPD*%\n' gerber_code += '%LPD*%\n'
except Exception as e:
except Exception as e: log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'clear' --> %s" % str(e))
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> %s" % str(e))
if not self.apertures: if not self.apertures:
log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> Gerber Object is empty: no apertures.") log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> Gerber Object is empty: no apertures.")

View File

@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
14.02.2020 14.02.2020
- adjusted the UI for Excellon and Geometry objects - adjusted the UI for Excellon and Geometry objects
- added a new FlatCAM Tool: Gerber Invert Tool. It will invert the copper features in a Gerber file: where is copper there will be empty and where is empty it will be copper
13.02.2020 13.02.2020

View File

@ -928,6 +928,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/calibrate_32.png'), _("Calibration Tool")) QtGui.QIcon(self.app.resource_location + '/calibrate_32.png'), _("Calibration Tool"))
self.punch_btn = self.toolbartools.addAction( self.punch_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool")) QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool"))
self.invert_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/invert32.png'), _("Invert Gerber Tool"))
# ######################################################################## # ########################################################################
# ########################## Excellon Editor Toolbar# #################### # ########################## Excellon Editor Toolbar# ####################

View File

@ -1414,9 +1414,12 @@ class Gerber(Geometry):
self.follow_geometry = follow_buffer self.follow_geometry = follow_buffer
# this treats the case when we are storing geometry as solids # this treats the case when we are storing geometry as solids
try:
if len(poly_buffer) == 0 and len(self.solid_geometry) == 0: if len(poly_buffer) == 0 and len(self.solid_geometry) == 0:
log.error("Object is not Gerber file or empty. Aborting Object creation.") log.error("Object is not Gerber file or empty. Aborting Object creation.")
return 'fail'
except TypeError as e:
log.error("Object is not Gerber file or empty. Aborting Object creation. %s" % str(e))
return 'fail' return 'fail'
log.warning("Joining %d polygons." % len(poly_buffer)) log.warning("Joining %d polygons." % len(poly_buffer))

View File

@ -0,0 +1,274 @@
# ##########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# File Author: Marius Adrian Stanciu (c) #
# Date: 2/14/2020 #
# MIT Licence #
# ##########################################################
from PyQt5 import QtWidgets, QtCore
from FlatCAMTool import FlatCAMTool
from flatcamGUI.GUIElements import FCButton, FCDoubleSpinner
from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString, box
from shapely.ops import cascaded_union
import traceback
from copy import deepcopy
import time
import logging
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
log = logging.getLogger('base')
class ToolInvertGerber(FlatCAMTool):
toolName = _("Invert Tool")
def __init__(self, app):
self.app = app
self.decimals = self.app.decimals
FlatCAMTool.__init__(self, app)
self.tools_frame = QtWidgets.QFrame()
self.tools_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.tools_frame)
self.tools_box = QtWidgets.QVBoxLayout()
self.tools_box.setContentsMargins(0, 0, 0, 0)
self.tools_frame.setLayout(self.tools_box)
# Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.tools_box.addWidget(title_label)
# Form Layout
grid0 = QtWidgets.QGridLayout()
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
self.tools_box.addLayout(grid0)
grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
# Target Gerber Object
self.gerber_combo = QtWidgets.QComboBox()
self.gerber_combo.setModel(self.app.collection)
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.gerber_combo.setCurrentIndex(1)
self.gerber_label = QtWidgets.QLabel('%s:' % _("Gerber Object"))
self.gerber_label.setToolTip(
_("Gerber object that will be inverted.")
)
grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
# Margin
self.margin_label = QtWidgets.QLabel('%s:' % _('Margin'))
self.margin_label.setToolTip(
_("Distance by which to avoid\n"
"the edges of the Gerber object.")
)
self.margin_entry = FCDoubleSpinner()
self.margin_entry.set_precision(self.decimals)
self.margin_entry.set_range(0.0000, 9999.9999)
self.margin_entry.setObjectName(_("Margin"))
grid0.addWidget(self.margin_label, 3, 0)
grid0.addWidget(self.margin_entry, 3, 1)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 4, 0, 1, 2)
self.invert_btn = FCButton(_('Invert Gerber'))
self.invert_btn.setToolTip(
_("Will invert the Gerber object: areas that have copper\n"
"will be emty of copper and previous empty area will be\n"
"filled with copper.")
)
self.invert_btn.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.invert_btn, 5, 0, 1, 2)
self.tools_box.addStretch()
# ## Reset Tool
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
self.reset_button.setToolTip(
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.tools_box.addWidget(self.reset_button)
self.invert_btn.clicked.connect(self.on_grb_invert)
self.reset_button.clicked.connect(self.set_tool_ui)
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='', **kwargs)
def run(self, toggle=True):
self.app.report_usage("ToolInvertGerber()")
log.debug("ToolInvertGerber() is running ...")
if toggle:
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
else:
try:
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
# if tab is populated with the tool but it does not have the focus, focus on it
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
# focus on Tool Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
else:
self.app.ui.splitter.setSizes([0, 1])
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Invert Tool"))
def set_tool_ui(self):
self.margin_entry.set_value(0.0)
def on_grb_invert(self):
margin = self.margin_entry.get_value()
if round(margin, self.decimals) == 0.0:
margin = 1E-10
grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
obj_name = self.gerber_combo.currentText()
outname = obj_name + "_inverted"
# Get source object.
try:
grb_obj = self.app.collection.get_by_name(obj_name)
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
if grb_obj is None:
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
return
xmin, ymin, xmax, ymax = grb_obj.bounds()
grb_box = box(xmin, ymin, xmax, ymax).buffer(margin, resolution=grb_circle_steps, join_style=2)
try:
__ = iter(grb_obj.solid_geometry)
except TypeError:
grb_obj.solid_geometry = list(grb_obj.solid_geometry)
new_solid_geometry = deepcopy(grb_box)
for poly in grb_obj.solid_geometry:
new_solid_geometry = new_solid_geometry.difference(poly)
new_options = dict()
for opt in grb_obj.options:
new_options[opt] = deepcopy(grb_obj.options[opt])
new_apertures = dict()
# for apid, val in grb_obj.apertures.items():
# new_apertures[apid] = dict()
# for key in val:
# if key == 'geometry':
# new_apertures[apid]['geometry'] = list()
# for elem in val['geometry']:
# geo_elem = dict()
# if 'follow' in elem:
# try:
# geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior
# except AttributeError:
# # TODO should test if width or height is bigger
# geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior
# if 'clear' in elem:
# if isinstance(elem['clear'], Polygon):
# try:
# geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps)
# except AttributeError:
# # TODO should test if width or height is bigger
# geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps)
# else:
# geo_elem['follow'] = elem['clear']
# new_apertures[apid]['geometry'].append(deepcopy(geo_elem))
# else:
# new_apertures[apid][key] = deepcopy(val[key])
if '0' not in new_apertures:
new_apertures['0'] = dict()
new_apertures['0']['type'] = 'C'
new_apertures['0']['size'] = 0.0
new_apertures['0']['geometry'] = list()
try:
for poly in new_solid_geometry:
new_el = dict()
new_el['solid'] = poly
new_el['follow'] = poly.exterior
new_apertures['0']['geometry'].append(new_el)
except TypeError:
new_el = dict()
new_el['solid'] = new_solid_geometry
new_el['follow'] = new_solid_geometry.exterior
new_apertures['0']['geometry'].append(new_el)
for td in new_apertures:
print(td, new_apertures[td])
def init_func(new_obj, app_obj):
new_obj.options.update(new_options)
new_obj.options['name'] = outname
new_obj.fill_color = deepcopy(grb_obj.fill_color)
new_obj.outline_color = deepcopy(grb_obj.outline_color)
new_obj.apertures = deepcopy(new_apertures)
new_obj.solid_geometry = deepcopy(new_solid_geometry)
new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
local_use=new_obj, use_thread=False)
self.app.new_object('gerber', outname, init_func)
def reset_fields(self):
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@staticmethod
def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors]
# end of file

View File

@ -358,6 +358,7 @@ class ToolPaint(FlatCAMTool, Gerber):
) )
self.paintmargin_entry = FCDoubleSpinner() self.paintmargin_entry = FCDoubleSpinner()
self.paintmargin_entry.set_precision(self.decimals) self.paintmargin_entry.set_precision(self.decimals)
self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
self.paintmargin_entry.setObjectName(_("Margin")) self.paintmargin_entry.setObjectName(_("Margin"))
grid4.addWidget(marginlabel, 2, 0) grid4.addWidget(marginlabel, 2, 0)

View File

@ -515,6 +515,8 @@ class ToolPunchGerber(FlatCAMTool):
punch_method = self.method_punch.get_value() punch_method = self.method_punch.get_value()
new_options = deepcopy(grb_obj.options)
if punch_method == 'exc': if punch_method == 'exc':
# get the Excellon file whose geometry will create the punch holes # get the Excellon file whose geometry will create the punch holes
@ -574,7 +576,7 @@ class ToolPunchGerber(FlatCAMTool):
new_apertures[str(new_apid)] = deepcopy(ap_val) new_apertures[str(new_apid)] = deepcopy(ap_val)
def init_func(new_obj, app_obj): def init_func(new_obj, app_obj):
new_obj.options.update(grb_obj.options) new_obj.options.update(new_options)
new_obj.options['name'] = outname new_obj.options['name'] = outname
new_obj.fill_color = deepcopy(grb_obj.fill_color) new_obj.fill_color = deepcopy(grb_obj.fill_color)
new_obj.outline_color = deepcopy(grb_obj.outline_color) new_obj.outline_color = deepcopy(grb_obj.outline_color)
@ -688,7 +690,7 @@ class ToolPunchGerber(FlatCAMTool):
new_apertures[str(new_apid)] = deepcopy(ap_val) new_apertures[str(new_apid)] = deepcopy(ap_val)
def init_func(new_obj, app_obj): def init_func(new_obj, app_obj):
new_obj.options.update(grb_obj.options) new_obj.options.update(new_options)
new_obj.options['name'] = outname new_obj.options['name'] = outname
new_obj.fill_color = deepcopy(grb_obj.fill_color) new_obj.fill_color = deepcopy(grb_obj.fill_color)
new_obj.outline_color = deepcopy(grb_obj.outline_color) new_obj.outline_color = deepcopy(grb_obj.outline_color)
@ -830,7 +832,7 @@ class ToolPunchGerber(FlatCAMTool):
new_apertures[str(new_apid)] = deepcopy(ap_val) new_apertures[str(new_apid)] = deepcopy(ap_val)
def init_func(new_obj, app_obj): def init_func(new_obj, app_obj):
new_obj.options.update(grb_obj.options) new_obj.options.update(new_options)
new_obj.options['name'] = outname new_obj.options['name'] = outname
new_obj.fill_color = deepcopy(grb_obj.fill_color) new_obj.fill_color = deepcopy(grb_obj.fill_color)
new_obj.outline_color = deepcopy(grb_obj.outline_color) new_obj.outline_color = deepcopy(grb_obj.outline_color)
@ -969,7 +971,7 @@ class ToolPunchGerber(FlatCAMTool):
new_apertures[str(new_apid)] = deepcopy(ap_val) new_apertures[str(new_apid)] = deepcopy(ap_val)
def init_func(new_obj, app_obj): def init_func(new_obj, app_obj):
new_obj.options.update(grb_obj.options) new_obj.options.update(new_options)
new_obj.options['name'] = outname new_obj.options['name'] = outname
new_obj.fill_color = deepcopy(grb_obj.fill_color) new_obj.fill_color = deepcopy(grb_obj.fill_color)
new_obj.outline_color = deepcopy(grb_obj.outline_color) new_obj.outline_color = deepcopy(grb_obj.outline_color)

View File

@ -254,14 +254,14 @@ class ToolSub(FlatCAMTool):
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.set_tool_ui() self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Sub Tool"))
def set_tool_ui(self):
self.new_apertures.clear() self.new_apertures.clear()
self.new_tools.clear() self.new_tools.clear()
self.new_solid_geometry = [] self.new_solid_geometry = []
self.target_options.clear() self.target_options.clear()
self.app.ui.notebook.setTabText(2, _("Sub Tool"))
def set_tool_ui(self):
self.tools_frame.show() self.tools_frame.show()
self.close_paths_cb.setChecked(self.app.defaults["tools_sub_close_paths"]) self.close_paths_cb.setChecked(self.app.defaults["tools_sub_close_paths"])

View File

@ -39,3 +39,5 @@ from flatcamTools.ToolSub import ToolSub
from flatcamTools.ToolTransform import ToolTransform from flatcamTools.ToolTransform import ToolTransform
from flatcamTools.ToolPunchGerber import ToolPunchGerber from flatcamTools.ToolPunchGerber import ToolPunchGerber
from flatcamTools.ToolInvertGerber import ToolInvertGerber

BIN
share/invert16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

BIN
share/invert32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B