- In Gerber isolation changed the UI

- in Gerber isolation added the option to selectively isolate only certain polygons
This commit is contained in:
Marius Stanciu 2019-11-25 16:52:37 +02:00
parent aac4fd75ca
commit d5a9e0bb5a
6 changed files with 325 additions and 222 deletions

View File

@ -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'],

View File

@ -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."))

View File

@ -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

109
camlib.py
View File

@ -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"))

View File

@ -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("<b>%s</b>" % _("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("<b>%s</b>" % _("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('<b>%s</b>' % _('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):
"""

View File

@ -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("<b>%s:</b>" % _("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"
"<<WARNING>>: 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()