diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 77a89668..438dd48f 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -523,6 +523,7 @@ class App(QtCore.QObject):
"gerber_isooverlap": 0.00393701,
"gerber_milling_type": "cl",
"gerber_combine_passes": False,
+ "gerber_iso_scope": 'all',
"gerber_noncoppermargin": 0.00393701,
"gerber_noncopperrounded": False,
"gerber_bboxmargin": 0.00393701,
@@ -537,6 +538,7 @@ class App(QtCore.QObject):
"gerber_vtipdia": 0.1,
"gerber_vtipangle": 30,
"gerber_vcutz": -0.05,
+ "gerber_iso_type": "full",
"gerber_buffering": "full",
"gerber_simplification": False,
"gerber_simp_tolerance": 0.0005,
@@ -1077,6 +1079,7 @@ class App(QtCore.QObject):
"gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
"gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
"gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb,
+ "gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.iso_scope_radio,
"gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio,
"gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
"gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
@@ -1092,6 +1095,7 @@ class App(QtCore.QObject):
"gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner,
"gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner,
"gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner,
+ "gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio,
"gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
"gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
@@ -2424,6 +2428,9 @@ class App(QtCore.QObject):
# decide if we have a double click or single click
self.doubleclick = False
+ # store here the is_dragging value
+ self.event_is_dragging = False
+
# variable to store if a command is active (then the var is not None) and which one it is
self.command_active = None
# variable to store the status of moving selection action
@@ -8337,7 +8344,7 @@ class App(QtCore.QObject):
pan_button = 2
else:
pan_button = 3
- event_is_dragging = event.is_dragging
+ self.event_is_dragging = event.is_dragging
else:
event_pos = (event.xdata, event.ydata)
# Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
@@ -8345,7 +8352,7 @@ class App(QtCore.QObject):
pan_button = 3
else:
pan_button = 2
- event_is_dragging = self.plotcanvas.is_dragging
+ self.event_is_dragging = self.plotcanvas.is_dragging
# So it can receive key presses
self.plotcanvas.native.setFocus()
@@ -8355,7 +8362,7 @@ class App(QtCore.QObject):
if not origin_click:
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
- if event.button == pan_button and event_is_dragging == 1:
+ if event.button == pan_button and self.event_is_dragging == 1:
self.ui.popMenu.mouse_is_panning = True
return
@@ -8383,7 +8390,7 @@ class App(QtCore.QObject):
self.mouse = [pos[0], pos[1]]
# if the mouse is moved and the LMB is clicked then the action is a selection
- if event_is_dragging == 1 and event.button == 1:
+ if self.event_is_dragging == 1 and event.button == 1:
self.delete_selection_shape()
if dx < 0:
self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
diff --git a/FlatCAMObj.py b/FlatCAMObj.py
index 8f20afd1..c1775dad 100644
--- a/FlatCAMObj.py
+++ b/FlatCAMObj.py
@@ -597,7 +597,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"bboxmargin": 0.0,
"bboxrounded": False,
"aperture_display": False,
- "follow": False
+ "follow": False,
+ "iso_scope": 'all',
+ "iso_type": 'full'
})
# type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -618,6 +620,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# Number of decimals to be used by tools in this class
self.decimals = 4
+ # Mouse events
+ self.mr = self.app.mr
+
+ # list to store the polygons selected for isolation
+ self.poly_list = list()
+
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
@@ -662,7 +670,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb,
"aperture_display": self.ui.aperture_table_visibility_cb,
- "follow": self.ui.follow_cb
+ "follow": self.ui.follow_cb,
+ "iso_scope": self.ui.iso_scope_radio,
+ "iso_type": self.ui.iso_type_radio
})
# Fill form fields only on object create
@@ -672,8 +682,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
- self.ui.generate_ext_iso_button.clicked.connect(self.on_ext_iso_button_click)
- self.ui.generate_int_iso_button.clicked.connect(self.on_int_iso_button_click)
self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
@@ -710,8 +718,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.aperture_table_visibility_cb.hide()
self.ui.milling_type_label.hide()
self.ui.milling_type_radio.hide()
- self.ui.generate_ext_iso_button.hide()
- self.ui.generate_int_iso_button.hide()
+ self.ui.iso_type_radio.hide()
+
self.ui.follow_cb.hide()
self.ui.except_cb.setChecked(False)
self.ui.except_cb.hide()
@@ -978,42 +986,16 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.app.new_object("geometry", name, geo_init)
- def on_ext_iso_button_click(self, *args):
- obj = self.app.collection.get_active()
-
- def worker_task(obj, app_obj):
- with self.app.proc_container.new(_("Isolating...")):
- if self.ui.follow_cb.get_value() is True:
- obj.follow_geo()
- # in the end toggle the visibility of the origin object so we can see the generated Geometry
- obj.ui.plot_cb.toggle()
- else:
- app_obj.report_usage("gerber_on_iso_button")
- self.read_form()
- self.isolate(iso_type=0)
-
- self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
-
- def on_int_iso_button_click(self, *args):
- obj = self.app.collection.get_active()
-
- def worker_task(obj, app_obj):
- with self.app.proc_container.new(_("Isolating...")):
- if self.ui.follow_cb.get_value() is True:
- obj.follow_geo()
- # in the end toggle the visibility of the origin object so we can see the generated Geometry
- obj.ui.plot_cb.toggle()
- else:
- app_obj.report_usage("gerber_on_iso_button")
- self.read_form()
- self.isolate(iso_type=1)
-
- self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
-
def on_iso_button_click(self, *args):
obj = self.app.collection.get_active()
+ self.iso_type = 2
+ if self.ui.iso_type_radio.get_value() == 'ext':
+ self.iso_type = 0
+ if self.ui.iso_type_radio.get_value() == 'int':
+ self.iso_type = 1
+
def worker_task(obj, app_obj):
with self.app.proc_container.new(_("Isolating...")):
if self.ui.follow_cb.get_value() is True:
@@ -1023,7 +1005,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
else:
app_obj.report_usage("gerber_on_iso_button")
self.read_form()
- self.isolate()
+
+ iso_scope = 'all' if self.ui.iso_scope_radio == 'all' else 'single'
+ self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
@@ -1053,18 +1037,96 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
except Exception as e:
return "Operation failed: %s" % str(e)
- def isolate(self, iso_type=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
+ def isolate_handler(self, iso_type, iso_scope):
+
+ if iso_scope == 'all':
+ self.isolate(iso_type=iso_type)
+ else:
+ # disengage the grid snapping since it will be hard to find the drills on grid
+ if self.app.ui.grid_snap_btn.isChecked():
+ self.grid_status_memory = True
+ self.app.ui.grid_snap_btn.trigger()
+ else:
+ self.grid_status_memory = False
+
+ self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
+ if self.app.is_legacy is False:
+ self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+ else:
+ self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+
+ self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on polygon to isolate it."))
+
+ def on_mouse_click_release(self, event):
+ if self.app.is_legacy is False:
+ event_pos = event.pos
+ event_is_dragging = event.is_dragging
+ right_button = 2
+ else:
+ event_pos = (event.xdata, event.ydata)
+ event_is_dragging = self.app.plotcanvas.is_dragging
+ right_button = 3
+
+ try:
+ x = float(event_pos[0])
+ y = float(event_pos[1])
+ except TypeError:
+ return
+
+ event_pos = (x, y)
+ curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+
+ if event.button == 1:
+ clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
+
+ if clicked_poly:
+ self.poly_list.append(clicked_poly)
+ self.app.inform.emit(
+ '%s: %d. %s' % (_("Added polygon"),
+ int(len(self.poly_list)),
+ _("Click to start adding next polygon or right click to start isolation."))
+ )
+ else:
+ self.app.inform.emit(_("No polygon detected under click position. Try again."))
+
+ 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()
+
+ if self.app.is_legacy is False:
+ self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+ else:
+ self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+ self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+ self.app.on_mouse_click_release_over_plot)
+
+ self.isolate(iso_type=self.iso_type, geometry=self.poly_list)
+
+ 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):
"""
Creates an isolation routing geometry object in the project.
:param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
+ :param iso_scope: whether to isolate all polygons or single polygpns: 'all' = all, 'single' = one by one, single
:param dia: Tool diameter
:param passes: Number of tool widths to cut
:param overlap: Overlap between passes in fraction of tool diameter
:param outname: Base name of the output object
:return: None
"""
+
+ if geometry is None:
+ if follow:
+ work_geo = self.follow_geometry
+ else:
+ work_geo = self.solid_geometry
+ else:
+ work_geo = geometry
+
if dia is None:
dia = float(self.options["isotooldia"])
if passes is None:
@@ -1075,28 +1137,33 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
combine = self.options["combine_passes"]
else:
combine = bool(combine)
+
if milling_type is None:
milling_type = self.options["milling_type"]
if iso_type is None:
- self.iso_type = 2
+ iso_t = 2
else:
- self.iso_type = iso_type
+ iso_t = iso_type
base_name = self.options["name"]
- def generate_envelope(offset, invert, envelope_iso_type=2, follow=None, passes=0):
+ def generate_envelope(offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
# isolation_geometry produces an envelope that is going on the left of the geometry
# (the copper features). To leave the least amount of burrs on the features
# the tool needs to travel on the right side of the features (this is called conventional milling)
# the first pass is the one cutting all of the features, so it needs to be reversed
# the other passes overlap preceding ones and cut the left over copper. It is better for them
# to cut on the right side of the left over copper i.e on the left side of the features.
- try:
- geom = self.isolation_geometry(offset, iso_type=envelope_iso_type, follow=follow, passes=passes)
- except Exception as e:
- log.debug('FlatCAMGerber.isolate().generate_envelope() --> %s' % str(e))
- return 'fail'
+
+ if follow:
+ geom = self.isolation_geometry(offset, geometry=geometry, follow=follow)
+ else:
+ try:
+ geom = self.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, passes=nr_passes)
+ except Exception as e:
+ log.debug('FlatCAMGerber.isolate().generate_envelope() --> %s' % str(e))
+ return 'fail'
if invert:
try:
@@ -1121,24 +1188,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
return 'fail'
return geom
- # if invert:
- # try:
- # if type(geom) is MultiPolygon:
- # pl = []
- # for p in geom:
- # if p is not None:
- # pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
- # geom = MultiPolygon(pl)
- # elif type(geom) is Polygon and geom is not None:
- # geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
- # else:
- # log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
- # type(geom))
- # except Exception as e:
- # log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e))
- # return 'fail'
- # return geom
-
# if float(self.options["isotooldia"]) < 0:
# self.options["isotooldia"] = -self.options["isotooldia"]
@@ -1210,30 +1259,26 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
iso_offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
# if milling type is climb then the move is counter-clockwise around features
- if milling_type == 'cl':
- # geom = generate_envelope (offset, i == 0)
- geom = generate_envelope(iso_offset, 1, envelope_iso_type=self.iso_type, follow=follow,
- passes=i)
- else:
- geom = generate_envelope(iso_offset, 0, envelope_iso_type=self.iso_type, follow=follow,
- passes=i)
+ mill_t = 1 if milling_type == 'cl' else 0
+ geom = generate_envelope(iso_offset, mill_t, geometry=work_geo, env_iso_type=iso_t, follow=follow,
+ nr_passes=i)
+
if geom == 'fail':
- app_obj.inform.emit('[ERROR_NOTCL] %s' %
- _("Isolation geometry could not be generated."))
+ app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry.append(geom)
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
- if self.ui.tool_type_radio.get_value() == 'circular':
- new_cutz = self.app.defaults['geometry_cutz']
- new_vtipdia = self.app.defaults['geometry_vtipdia']
- new_vtipangle = self.app.defaults['geometry_vtipangle']
- tool_type = 'C1'
- else:
+ if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
+ else:
+ new_cutz = self.app.defaults['geometry_cutz']
+ new_vtipdia = self.app.defaults['geometry_vtipdia']
+ new_vtipangle = self.app.defaults['geometry_vtipangle']
+ tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
@@ -1280,7 +1325,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
- if not isinstance(geo_obj.solid_geometry, list):
+ if not isinstance(geo_obj.solid_geometry, list) and \
+ not isinstance(geo_obj.solid_geometry, MultiPolygon):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
@@ -1338,13 +1384,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
# if milling type is climb then the move is counter-clockwise around features
- if milling_type == 'cl':
- # geo_obj.solid_geometry = generate_envelope(offset, i == 0)
- geom = generate_envelope(offset, 1, envelope_iso_type=self.iso_type, follow=follow,
- passes=i)
- else:
- geom = generate_envelope(offset, 0, envelope_iso_type=self.iso_type, follow=follow,
- passes=i)
+ mill_t = 1 if milling_type == 'cl' else 0
+ mill_t = 1 if milling_type == 'cl' else 0
+ geom = generate_envelope(offset, mill_t, geometry=work_geo, env_iso_type=iso_t, follow=follow,
+ nr_passes=i)
+
if geom == 'fail':
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("Isolation geometry could not be generated."))
diff --git a/README.md b/README.md
index ff787596..586f47db 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+25.11.2019
+
+- In Gerber isolation changed the UI
+- in Gerber isolation added the option to selectively isolate only certain polygons
+
23.11.2019
- in Tool Fiducials added a new fiducial type: chess pattern
diff --git a/camlib.py b/camlib.py
index 9212d5cd..6d2501ce 100644
--- a/camlib.py
+++ b/camlib.py
@@ -897,7 +897,7 @@ class Geometry(object):
#
# return self.flat_geometry, self.flat_geometry_rtree
- def isolation_geometry(self, offset, iso_type=2, corner=None, follow=None, passes=0):
+ def isolation_geometry(self, offset, geometry=None, iso_type=2, corner=None, follow=None, passes=0):
"""
Creates contours around geometry at a given
offset distance.
@@ -916,73 +916,58 @@ class Geometry(object):
# graceful abort requested by the user
raise FlatCAMApp.GracefulException
- geo_iso = []
- if offset == 0:
- if follow:
- geo_iso = self.follow_geometry
- else:
- geo_iso = self.solid_geometry
+ geo_iso = list()
+
+ if follow:
+ return geometry
+
+ if geometry:
+ working_geo = geometry
else:
- if follow:
- geo_iso = self.follow_geometry
+ working_geo = self.solid_geometry
+
+ try:
+ geo_len = len(working_geo)
+ except TypeError:
+ geo_len = 1
+
+ old_disp_number = 0
+ pol_nr = 0
+ # yet, it can be done by issuing an unary_union in the end, thus getting rid of the overlapping geo
+ try:
+ for pol in working_geo:
+ if self.app.abort_flag:
+ # graceful abort requested by the user
+ raise FlatCAMApp.GracefulException
+ if offset == 0:
+ geo_iso.append(pol)
+ else:
+ corner_type = 1 if corner is None else corner
+ geo_iso.append(pol.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner_type))
+ pol_nr += 1
+ disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
+
+ if old_disp_number < disp_number <= 100:
+ self.app.proc_container.update_view_text(' %s %d: %d%%' %
+ (_("Pass"), int(passes + 1), int(disp_number)))
+ old_disp_number = disp_number
+ except TypeError:
+ # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
+ # MultiPolygon (not an iterable)
+ if offset == 0:
+ geo_iso.append(working_geo)
else:
- # if isinstance(self.solid_geometry, list):
- # temp_geo = cascaded_union(self.solid_geometry)
- # else:
- # temp_geo = self.solid_geometry
+ corner_type = 1 if corner is None else corner
+ geo_iso.append(working_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
+ join_style=corner_type))
- # Remember: do not make a buffer for each element in the solid_geometry because it will cut into
- # other copper features
- # if corner is None:
- # geo_iso = temp_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4))
- # else:
- # geo_iso = temp_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
- # join_style=corner)
-
- # variables to display the percentage of work done
- geo_len = 0
- try:
- for pol in self.solid_geometry:
- geo_len += 1
- except TypeError:
- geo_len = 1
- disp_number = 0
- old_disp_number = 0
- pol_nr = 0
- # yet, it can be done by issuing an unary_union in the end, thus getting rid of the overlapping geo
- try:
- for pol in self.solid_geometry:
- if self.app.abort_flag:
- # graceful abort requested by the user
- raise FlatCAMApp.GracefulException
- if corner is None:
- geo_iso.append(pol.buffer(offset, int(int(self.geo_steps_per_circle) / 4)))
- else:
- geo_iso.append(pol.buffer(offset, int(int(self.geo_steps_per_circle) / 4)),
- join_style=corner)
- pol_nr += 1
- disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
- if old_disp_number < disp_number <= 100:
- self.app.proc_container.update_view_text(' %s %d: %d%%' %
- (_("Pass"), int(passes + 1), int(disp_number)))
- old_disp_number = disp_number
- except TypeError:
- # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
- # MultiPolygon (not an iterable)
- if corner is None:
- geo_iso.append(self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)))
- else:
- geo_iso.append(self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)),
- join_style=corner)
- self.app.proc_container.update_view_text(' %s' % _("Buffering"))
- geo_iso = unary_union(geo_iso)
+ self.app.proc_container.update_view_text(' %s' % _("Buffering"))
+ geo_iso = unary_union(geo_iso)
self.app.proc_container.update_view_text('')
# end of replaced block
- if follow:
- return geo_iso
- elif iso_type == 2:
+
+ if iso_type == 2:
return geo_iso
elif iso_type == 0:
self.app.proc_container.update_view_text(' %s' % _("Get Exteriors"))
diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py
index 4db16297..23e62676 100644
--- a/flatcamGUI/ObjectUI.py
+++ b/flatcamGUI/ObjectUI.py
@@ -281,6 +281,7 @@ class GerberObjectUI(ObjectUI):
self.custom_box.addLayout(grid1)
grid1.setColumnStretch(0, 0)
grid1.setColumnStretch(1, 1)
+ grid1.setColumnStretch(2, 1)
# Tool Type
self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
@@ -290,8 +291,8 @@ class GerberObjectUI(ObjectUI):
"When the 'V-shape' is selected then the tool\n"
"diameter will depend on the chosen cut depth.")
)
- self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
- {'label': 'V-Shape', 'value': 'v'}])
+ self.tool_type_radio = RadioSet([{'label': _('Circular'), 'value': 'circular'},
+ {'label': _('V-Shape'), 'value': 'v'}])
grid1.addWidget(self.tool_type_label, 0, 0)
grid1.addWidget(self.tool_type_radio, 0, 1, 1, 2)
@@ -396,7 +397,7 @@ class GerberObjectUI(ObjectUI):
grid1.addWidget(self.milling_type_radio, 7, 1, 1, 2)
# combine all passes CB
- self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
+ self.combine_passes_cb = FCCheckBox(label=_('Combine'))
self.combine_passes_cb.setToolTip(
_("Combine all passes into one object")
)
@@ -406,15 +407,15 @@ class GerberObjectUI(ObjectUI):
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace."))
+ grid1.addWidget(self.combine_passes_cb, 8, 0)
# avoid an area from isolation
self.except_cb = FCCheckBox(label=_('Except'))
+ grid1.addWidget(self.follow_cb, 8, 1)
+
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
"by checking this, the area of the object bellow\n"
"will be subtracted from the isolation geometry."))
-
- grid1.addWidget(self.combine_passes_cb, 8, 0)
- grid1.addWidget(self.follow_cb, 8, 1)
grid1.addWidget(self.except_cb, 8, 2)
# ## Form Layout
@@ -454,8 +455,50 @@ class GerberObjectUI(ObjectUI):
form_layout.addRow(self.obj_label, self.obj_combo)
- self.gen_iso_label = QtWidgets.QLabel("%s" % _("Generate Isolation Geometry"))
- self.gen_iso_label.setToolTip(
+ # ---------------------------------------------- #
+ # --------- Isolation scope -------------------- #
+ # ---------------------------------------------- #
+ self.iso_scope_label = QtWidgets.QLabel('%s:' % _('Scope'))
+ self.iso_scope_label.setToolTip(
+ _("Isolation scope. Choose what to isolate:\n"
+ "- 'All' -> Isolate all the polygons in the object\n"
+ "- 'Single' -> Isolate a single polygon.")
+ )
+ self.iso_scope_radio = RadioSet([{'label': _('All'), 'value': 'all'},
+ {'label': _('Single'), 'value': 'single'}])
+
+ grid1.addWidget(self.iso_scope_label, 10, 0)
+ grid1.addWidget(self.iso_scope_radio, 10, 1, 1, 2)
+
+ # ---------------------------------------------- #
+ # --------- Isolation type -------------------- #
+ # ---------------------------------------------- #
+ self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
+ self.iso_type_label.setToolTip(
+ _("Choose how the isolation will be executed:\n"
+ "- 'Full' -> complete isolation of polygons\n"
+ "- 'Ext' -> will isolate only on the outside\n"
+ "- 'Int' -> will isolate only on the inside\n"
+ "'Exterior' isolation is almost always possible\n"
+ "(with the right tool) but 'Interior'\n"
+ "isolation can be done only when there is an opening\n"
+ "inside of the polygon (e.g polygon is a 'doughnut' shape).")
+ )
+ self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
+ {'label': _('Ext'), 'value': 'ext'},
+ {'label': _('Int'), 'value': 'int'}])
+
+ grid1.addWidget(self.iso_type_label, 11, 0)
+ grid1.addWidget(self.iso_type_radio, 11, 1, 1, 2)
+
+ self.generate_iso_button = QtWidgets.QPushButton("%s" % _("Generate Isolation Geometry"))
+ self.generate_iso_button.setStyleSheet("""
+ QPushButton
+ {
+ font-weight: bold;
+ }
+ """)
+ self.generate_iso_button.setToolTip(
_("Create a Geometry object with toolpaths to cut \n"
"isolation outside, inside or on both sides of the\n"
"object. For a Gerber object outside means outside\n"
@@ -466,7 +509,7 @@ class GerberObjectUI(ObjectUI):
"inside the actual Gerber feature, use a negative tool\n"
"diameter above.")
)
- grid1.addWidget(self.gen_iso_label, 10, 0, 1, 3)
+ grid1.addWidget(self.generate_iso_button, 12, 0, 1, 3)
self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
self.create_buffer_button.setToolTip(
@@ -475,48 +518,15 @@ class GerberObjectUI(ObjectUI):
"Clicking this will create the buffered geometry\n"
"required for isolation.")
)
- grid1.addWidget(self.create_buffer_button, 11, 0, 1, 3)
-
- self.generate_iso_button = QtWidgets.QPushButton(_('FULL Geo'))
- self.generate_iso_button.setToolTip(
- _("Create the Geometry Object\n"
- "for isolation routing. It contains both\n"
- "the interiors and exteriors geometry.")
- )
- grid1.addWidget(self.generate_iso_button, 12, 0)
-
- hlay_1 = QtWidgets.QHBoxLayout()
- grid1.addLayout(hlay_1, 12, 1, 1, 2)
-
- self.generate_ext_iso_button = QtWidgets.QPushButton(_('Ext Geo'))
- self.generate_ext_iso_button.setToolTip(
- _("Create the Geometry Object\n"
- "for isolation routing containing\n"
- "only the exteriors geometry.")
- )
- # self.generate_ext_iso_button.setMinimumWidth(100)
- hlay_1.addWidget(self.generate_ext_iso_button)
-
- self.generate_int_iso_button = QtWidgets.QPushButton(_('Int Geo'))
- self.generate_int_iso_button.setToolTip(
- _("Create the Geometry Object\n"
- "for isolation routing containing\n"
- "only the interiors geometry.")
- )
- # self.generate_ext_iso_button.setMinimumWidth(90)
- hlay_1.addWidget(self.generate_int_iso_button)
+ grid1.addWidget(self.create_buffer_button, 13, 0, 1, 2)
self.ohis_iso = OptionalHideInputSection(
self.except_cb,
[self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
logic=True
)
- # when the follow checkbox is checked then the exteriors and interiors isolation generation buttons
- # are disabled as is doesn't make sense to have them enabled due of the nature of "follow"
- self.ois_iso = OptionalInputSection(self.follow_cb,
- [self.generate_int_iso_button, self.generate_ext_iso_button], logic=False)
- grid1.addWidget(QtWidgets.QLabel(''), 13, 0)
+ grid1.addWidget(QtWidgets.QLabel(''), 14, 0)
# ###########################################
# ########## NEW GRID #######################
@@ -562,6 +572,11 @@ class GerberObjectUI(ObjectUI):
grid2.addWidget(self.board_cutout_label, 2, 0)
grid2.addWidget(self.generate_cutout_button, 2, 1)
+ separator_line = QtWidgets.QFrame()
+ separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ grid2.addWidget(separator_line, 3, 0, 1, 2)
+
# ## Non-copper regions
self.noncopper_label = QtWidgets.QLabel("%s" % _("Non-copper regions"))
self.noncopper_label.setToolTip(
@@ -572,7 +587,7 @@ class GerberObjectUI(ObjectUI):
"copper from a specified region.")
)
- grid2.addWidget(self.noncopper_label, 3, 0, 1, 2)
+ grid2.addWidget(self.noncopper_label, 4, 0, 1, 2)
# Margin
bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
@@ -588,8 +603,8 @@ class GerberObjectUI(ObjectUI):
self.noncopper_margin_entry.set_precision(self.decimals)
self.noncopper_margin_entry.setSingleStep(0.1)
- grid2.addWidget(bmlabel, 4, 0)
- grid2.addWidget(self.noncopper_margin_entry, 4, 1)
+ grid2.addWidget(bmlabel, 5, 0)
+ grid2.addWidget(self.noncopper_margin_entry, 5, 1)
# Rounded corners
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
@@ -599,8 +614,13 @@ class GerberObjectUI(ObjectUI):
self.noncopper_rounded_cb.setMinimumWidth(90)
self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
- grid2.addWidget(self.noncopper_rounded_cb, 5, 0)
- grid2.addWidget(self.generate_noncopper_button, 5, 1)
+ grid2.addWidget(self.noncopper_rounded_cb, 6, 0)
+ grid2.addWidget(self.generate_noncopper_button, 6, 1)
+
+ separator_line1 = QtWidgets.QFrame()
+ separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+ grid2.addWidget(separator_line1, 7, 0, 1, 2)
# ## Bounding box
self.boundingbox_label = QtWidgets.QLabel('%s' % _('Bounding Box'))
@@ -609,7 +629,7 @@ class GerberObjectUI(ObjectUI):
"Square shape.")
)
- grid2.addWidget(self.boundingbox_label, 6, 0, 1, 2)
+ grid2.addWidget(self.boundingbox_label, 8, 0, 1, 2)
bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
bbmargin.setToolTip(
@@ -622,8 +642,8 @@ class GerberObjectUI(ObjectUI):
self.bbmargin_entry.set_precision(self.decimals)
self.bbmargin_entry.setSingleStep(0.1)
- grid2.addWidget(bbmargin, 7, 0)
- grid2.addWidget(self.bbmargin_entry, 7, 1)
+ grid2.addWidget(bbmargin, 9, 0)
+ grid2.addWidget(self.bbmargin_entry, 9, 1)
self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
self.bbrounded_cb.setToolTip(
@@ -638,9 +658,13 @@ class GerberObjectUI(ObjectUI):
self.generate_bb_button.setToolTip(
_("Generate the Geometry object.")
)
- grid2.addWidget(self.bbrounded_cb, 8, 0)
- grid2.addWidget(self.generate_bb_button, 8, 1)
+ grid2.addWidget(self.bbrounded_cb, 10, 0)
+ grid2.addWidget(self.generate_bb_button, 10, 1)
+ separator_line2 = QtWidgets.QFrame()
+ separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
+ grid2.addWidget(separator_line2, 11, 0, 1, 2)
class ExcellonObjectUI(ObjectUI):
"""
diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py
index 2c704b80..defa40fc 100644
--- a/flatcamGUI/PreferencesUI.py
+++ b/flatcamGUI/PreferencesUI.py
@@ -1344,9 +1344,10 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
_("Width of the isolation gap in\n"
"number (integer) of tool widths.")
)
- grid0.addWidget(passlabel, 1, 0)
self.iso_width_entry = FCSpinner()
self.iso_width_entry.setRange(1, 999)
+
+ grid0.addWidget(passlabel, 1, 0)
grid0.addWidget(self.iso_width_entry, 1, 1)
# Pass overlap
@@ -1356,14 +1357,28 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
"Example:\n"
"A value here of 0.25 means an overlap of 25%% from the tool diameter found above.")
)
- grid0.addWidget(overlabel, 2, 0)
self.iso_overlap_entry = FCDoubleSpinner()
self.iso_overlap_entry.set_precision(3)
self.iso_overlap_entry.setWrapping(True)
self.iso_overlap_entry.setRange(0.000, 0.999)
self.iso_overlap_entry.setSingleStep(0.1)
+
+ grid0.addWidget(overlabel, 2, 0)
grid0.addWidget(self.iso_overlap_entry, 2, 1)
+ # Isolation Scope
+ self.iso_scope_label = QtWidgets.QLabel('%s:' % _('Scope'))
+ self.iso_scope_label.setToolTip(
+ _("Isolation scope. Choose what to isolate:\n"
+ "- 'All' -> Isolate all the polygons in the object\n"
+ "- 'Single' -> Isolate a single polygon.")
+ )
+ self.iso_scope_radio = RadioSet([{'label': _('All'), 'value': 'all'},
+ {'label': _('Single'), 'value': 'single'}])
+
+ grid0.addWidget(self.iso_scope_label, 3, 0)
+ grid0.addWidget(self.iso_scope_radio, 3, 1, 1, 2)
+
# Milling Type
milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
milling_type_label.setToolTip(
@@ -1371,17 +1386,17 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
- grid0.addWidget(milling_type_label, 3, 0)
+ grid0.addWidget(milling_type_label, 4, 0)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conv.'), 'value': 'cv'}])
- grid0.addWidget(self.milling_type_radio, 3, 1)
+ grid0.addWidget(self.milling_type_radio, 4, 1)
# Combine passes
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
self.combine_passes_cb.setToolTip(
_("Combine all passes into one object")
)
- grid0.addWidget(self.combine_passes_cb, 4, 0, 1, 2)
+ grid0.addWidget(self.combine_passes_cb, 5, 0, 1, 2)
# ## Clear non-copper regions
self.clearcopper_label = QtWidgets.QLabel("%s:" % _("Non-copper regions"))
@@ -1537,9 +1552,29 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
self.cutz_spinner.set_range(-99.9999, -0.0001)
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)
+ # Isolation Type
+ self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
+ self.iso_type_label.setToolTip(
+ _("Choose how the isolation will be executed:\n"
+ "- 'Full' -> complete isolation of polygons\n"
+ "- 'Ext' -> will isolate only on the outside\n"
+ "- 'Int' -> will isolate only on the inside\n"
+ "'Exterior' isolation is almost always possible\n"
+ "(with the right tool) but 'Interior'\n"
+ "isolation can be done only when there is an opening\n"
+ "inside of the polygon (e.g polygon is a 'doughnut' shape).")
+ )
+ self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
+ {'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)
+
# Buffering Type
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
buffering_label.setToolTip(
@@ -1550,8 +1585,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
)
self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
{'label': _('Full'), 'value': 'full'}])
- grid0.addWidget(buffering_label, 6, 0)
- grid0.addWidget(self.buffering_radio, 6, 1)
+ grid0.addWidget(buffering_label, 7, 0)
+ grid0.addWidget(self.buffering_radio, 7, 1)
# Simplification
self.simplify_cb = FCCheckBox(label=_('Simplify'))
@@ -1560,7 +1595,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
"loaded with simplification having a set tolerance.\n"
"<>: Don't change this unless you know what you are doing !!!")
)
- grid0.addWidget(self.simplify_cb, 7, 0, 1, 2)
+ grid0.addWidget(self.simplify_cb, 8, 0, 1, 2)
# Simplification tolerance
self.simplification_tol_label = QtWidgets.QLabel(_('Tolerance'))
@@ -1572,11 +1607,14 @@ 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, 8, 0)
- grid0.addWidget(self.simplification_tol_spinner, 8, 1)
- self.ois_simplif = OptionalInputSection(self.simplify_cb,
- [self.simplification_tol_label, self.simplification_tol_spinner],
- logic=True)
+ grid0.addWidget(self.simplification_tol_label, 9, 0)
+ grid0.addWidget(self.simplification_tol_spinner, 9, 1)
+ self.ois_simplif = OptionalInputSection(
+ self.simplify_cb,
+ [
+ self.simplification_tol_label, self.simplification_tol_spinner
+ ],
+ logic=True)
self.layout.addStretch()