- In Gerber isolation changed the UI
- in Gerber isolation added the option to selectively isolate only certain polygons
This commit is contained in:
parent
aac4fd75ca
commit
d5a9e0bb5a
|
@ -523,6 +523,7 @@ class App(QtCore.QObject):
|
||||||
"gerber_isooverlap": 0.00393701,
|
"gerber_isooverlap": 0.00393701,
|
||||||
"gerber_milling_type": "cl",
|
"gerber_milling_type": "cl",
|
||||||
"gerber_combine_passes": False,
|
"gerber_combine_passes": False,
|
||||||
|
"gerber_iso_scope": 'all',
|
||||||
"gerber_noncoppermargin": 0.00393701,
|
"gerber_noncoppermargin": 0.00393701,
|
||||||
"gerber_noncopperrounded": False,
|
"gerber_noncopperrounded": False,
|
||||||
"gerber_bboxmargin": 0.00393701,
|
"gerber_bboxmargin": 0.00393701,
|
||||||
|
@ -537,6 +538,7 @@ class App(QtCore.QObject):
|
||||||
"gerber_vtipdia": 0.1,
|
"gerber_vtipdia": 0.1,
|
||||||
"gerber_vtipangle": 30,
|
"gerber_vtipangle": 30,
|
||||||
"gerber_vcutz": -0.05,
|
"gerber_vcutz": -0.05,
|
||||||
|
"gerber_iso_type": "full",
|
||||||
"gerber_buffering": "full",
|
"gerber_buffering": "full",
|
||||||
"gerber_simplification": False,
|
"gerber_simplification": False,
|
||||||
"gerber_simp_tolerance": 0.0005,
|
"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_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_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_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_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_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,
|
"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_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_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_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_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,
|
"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
|
# decide if we have a double click or single click
|
||||||
self.doubleclick = False
|
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
|
# variable to store if a command is active (then the var is not None) and which one it is
|
||||||
self.command_active = None
|
self.command_active = None
|
||||||
# variable to store the status of moving selection action
|
# variable to store the status of moving selection action
|
||||||
|
@ -8337,7 +8344,7 @@ class App(QtCore.QObject):
|
||||||
pan_button = 2
|
pan_button = 2
|
||||||
else:
|
else:
|
||||||
pan_button = 3
|
pan_button = 3
|
||||||
event_is_dragging = event.is_dragging
|
self.event_is_dragging = event.is_dragging
|
||||||
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
|
||||||
|
@ -8345,7 +8352,7 @@ class App(QtCore.QObject):
|
||||||
pan_button = 3
|
pan_button = 3
|
||||||
else:
|
else:
|
||||||
pan_button = 2
|
pan_button = 2
|
||||||
event_is_dragging = self.plotcanvas.is_dragging
|
self.event_is_dragging = self.plotcanvas.is_dragging
|
||||||
|
|
||||||
# So it can receive key presses
|
# So it can receive key presses
|
||||||
self.plotcanvas.native.setFocus()
|
self.plotcanvas.native.setFocus()
|
||||||
|
@ -8355,7 +8362,7 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
if not origin_click:
|
if not origin_click:
|
||||||
# if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
|
# 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
|
self.ui.popMenu.mouse_is_panning = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -8383,7 +8390,7 @@ class App(QtCore.QObject):
|
||||||
self.mouse = [pos[0], pos[1]]
|
self.mouse = [pos[0], pos[1]]
|
||||||
|
|
||||||
# if the mouse is moved and the LMB is clicked then the action is a selection
|
# 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()
|
self.delete_selection_shape()
|
||||||
if dx < 0:
|
if dx < 0:
|
||||||
self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
|
self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
|
||||||
|
|
222
FlatCAMObj.py
222
FlatCAMObj.py
|
@ -597,7 +597,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
"bboxmargin": 0.0,
|
"bboxmargin": 0.0,
|
||||||
"bboxrounded": False,
|
"bboxrounded": False,
|
||||||
"aperture_display": 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)
|
# 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
|
# Number of decimals to be used by tools in this class
|
||||||
self.decimals = 4
|
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
|
# Attributes to be included in serialization
|
||||||
# Always append to it because it carries contents
|
# Always append to it because it carries contents
|
||||||
# from predecessors.
|
# from predecessors.
|
||||||
|
@ -662,7 +670,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
"bboxmargin": self.ui.bbmargin_entry,
|
"bboxmargin": self.ui.bbmargin_entry,
|
||||||
"bboxrounded": self.ui.bbrounded_cb,
|
"bboxrounded": self.ui.bbrounded_cb,
|
||||||
"aperture_display": self.ui.aperture_table_visibility_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
|
# 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.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||||
self.ui.solid_cb.stateChanged.connect(self.on_solid_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.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_iso_button.clicked.connect(self.on_iso_button_click)
|
||||||
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
|
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
|
||||||
self.ui.generate_cutout_button.clicked.connect(self.app.cutout_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.aperture_table_visibility_cb.hide()
|
||||||
self.ui.milling_type_label.hide()
|
self.ui.milling_type_label.hide()
|
||||||
self.ui.milling_type_radio.hide()
|
self.ui.milling_type_radio.hide()
|
||||||
self.ui.generate_ext_iso_button.hide()
|
self.ui.iso_type_radio.hide()
|
||||||
self.ui.generate_int_iso_button.hide()
|
|
||||||
self.ui.follow_cb.hide()
|
self.ui.follow_cb.hide()
|
||||||
self.ui.except_cb.setChecked(False)
|
self.ui.except_cb.setChecked(False)
|
||||||
self.ui.except_cb.hide()
|
self.ui.except_cb.hide()
|
||||||
|
@ -978,42 +986,16 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
|
|
||||||
self.app.new_object("geometry", name, geo_init)
|
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):
|
def on_iso_button_click(self, *args):
|
||||||
|
|
||||||
obj = self.app.collection.get_active()
|
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):
|
def worker_task(obj, app_obj):
|
||||||
with self.app.proc_container.new(_("Isolating...")):
|
with self.app.proc_container.new(_("Isolating...")):
|
||||||
if self.ui.follow_cb.get_value() is True:
|
if self.ui.follow_cb.get_value() is True:
|
||||||
|
@ -1023,7 +1005,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
else:
|
else:
|
||||||
app_obj.report_usage("gerber_on_iso_button")
|
app_obj.report_usage("gerber_on_iso_button")
|
||||||
self.read_form()
|
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]})
|
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
|
||||||
|
|
||||||
|
@ -1053,18 +1037,96 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return "Operation failed: %s" % str(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):
|
milling_type=None, follow=None, plot=True):
|
||||||
"""
|
"""
|
||||||
Creates an isolation routing geometry object in the project.
|
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_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 dia: Tool diameter
|
||||||
:param passes: Number of tool widths to cut
|
:param passes: Number of tool widths to cut
|
||||||
:param overlap: Overlap between passes in fraction of tool diameter
|
:param overlap: Overlap between passes in fraction of tool diameter
|
||||||
:param outname: Base name of the output object
|
:param outname: Base name of the output object
|
||||||
:return: None
|
: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:
|
if dia is None:
|
||||||
dia = float(self.options["isotooldia"])
|
dia = float(self.options["isotooldia"])
|
||||||
if passes is None:
|
if passes is None:
|
||||||
|
@ -1075,28 +1137,33 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
combine = self.options["combine_passes"]
|
combine = self.options["combine_passes"]
|
||||||
else:
|
else:
|
||||||
combine = bool(combine)
|
combine = bool(combine)
|
||||||
|
|
||||||
if milling_type is None:
|
if milling_type is None:
|
||||||
milling_type = self.options["milling_type"]
|
milling_type = self.options["milling_type"]
|
||||||
|
|
||||||
if iso_type is None:
|
if iso_type is None:
|
||||||
self.iso_type = 2
|
iso_t = 2
|
||||||
else:
|
else:
|
||||||
self.iso_type = iso_type
|
iso_t = iso_type
|
||||||
|
|
||||||
base_name = self.options["name"]
|
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
|
# 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 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 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 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
|
# 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.
|
# 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)
|
if follow:
|
||||||
except Exception as e:
|
geom = self.isolation_geometry(offset, geometry=geometry, follow=follow)
|
||||||
log.debug('FlatCAMGerber.isolate().generate_envelope() --> %s' % str(e))
|
else:
|
||||||
return 'fail'
|
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:
|
if invert:
|
||||||
try:
|
try:
|
||||||
|
@ -1121,24 +1188,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
return 'fail'
|
return 'fail'
|
||||||
return geom
|
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:
|
# if float(self.options["isotooldia"]) < 0:
|
||||||
# self.options["isotooldia"] = -self.options["isotooldia"]
|
# 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)
|
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 is climb then the move is counter-clockwise around features
|
||||||
if milling_type == 'cl':
|
mill_t = 1 if milling_type == 'cl' else 0
|
||||||
# geom = generate_envelope (offset, i == 0)
|
geom = generate_envelope(iso_offset, mill_t, geometry=work_geo, env_iso_type=iso_t, follow=follow,
|
||||||
geom = generate_envelope(iso_offset, 1, envelope_iso_type=self.iso_type, follow=follow,
|
nr_passes=i)
|
||||||
passes=i)
|
|
||||||
else:
|
|
||||||
geom = generate_envelope(iso_offset, 0, envelope_iso_type=self.iso_type, follow=follow,
|
|
||||||
passes=i)
|
|
||||||
if geom == 'fail':
|
if geom == 'fail':
|
||||||
app_obj.inform.emit('[ERROR_NOTCL] %s' %
|
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
|
||||||
_("Isolation geometry could not be generated."))
|
|
||||||
return 'fail'
|
return 'fail'
|
||||||
geo_obj.solid_geometry.append(geom)
|
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
|
# 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':
|
if self.ui.tool_type_radio.get_value() == 'v':
|
||||||
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:
|
|
||||||
new_cutz = self.ui.cutz_spinner.get_value()
|
new_cutz = self.ui.cutz_spinner.get_value()
|
||||||
new_vtipdia = self.ui.tipdia_spinner.get_value()
|
new_vtipdia = self.ui.tipdia_spinner.get_value()
|
||||||
new_vtipangle = self.ui.tipangle_spinner.get_value()
|
new_vtipangle = self.ui.tipangle_spinner.get_value()
|
||||||
tool_type = 'V'
|
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
|
# store here the default data for Geometry Data
|
||||||
default_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
|
# 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
|
# of the list then we have an empty solid_geometry which should raise a Custom Exception
|
||||||
empty_cnt = 0
|
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]
|
geo_obj.solid_geometry = [geo_obj.solid_geometry]
|
||||||
|
|
||||||
for g in 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"])
|
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||||
|
|
||||||
# if milling type is climb then the move is counter-clockwise around features
|
# if milling type is climb then the move is counter-clockwise around features
|
||||||
if milling_type == 'cl':
|
mill_t = 1 if milling_type == 'cl' else 0
|
||||||
# geo_obj.solid_geometry = generate_envelope(offset, i == 0)
|
mill_t = 1 if milling_type == 'cl' else 0
|
||||||
geom = generate_envelope(offset, 1, envelope_iso_type=self.iso_type, follow=follow,
|
geom = generate_envelope(offset, mill_t, geometry=work_geo, env_iso_type=iso_t, follow=follow,
|
||||||
passes=i)
|
nr_passes=i)
|
||||||
else:
|
|
||||||
geom = generate_envelope(offset, 0, envelope_iso_type=self.iso_type, follow=follow,
|
|
||||||
passes=i)
|
|
||||||
if geom == 'fail':
|
if geom == 'fail':
|
||||||
app_obj.inform.emit('[ERROR_NOTCL] %s' %
|
app_obj.inform.emit('[ERROR_NOTCL] %s' %
|
||||||
_("Isolation geometry could not be generated."))
|
_("Isolation geometry could not be generated."))
|
||||||
|
|
|
@ -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
|
23.11.2019
|
||||||
|
|
||||||
- in Tool Fiducials added a new fiducial type: chess pattern
|
- in Tool Fiducials added a new fiducial type: chess pattern
|
||||||
|
|
109
camlib.py
109
camlib.py
|
@ -897,7 +897,7 @@ class Geometry(object):
|
||||||
#
|
#
|
||||||
# return self.flat_geometry, self.flat_geometry_rtree
|
# 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
|
Creates contours around geometry at a given
|
||||||
offset distance.
|
offset distance.
|
||||||
|
@ -916,73 +916,58 @@ class Geometry(object):
|
||||||
# graceful abort requested by the user
|
# graceful abort requested by the user
|
||||||
raise FlatCAMApp.GracefulException
|
raise FlatCAMApp.GracefulException
|
||||||
|
|
||||||
geo_iso = []
|
geo_iso = list()
|
||||||
if offset == 0:
|
|
||||||
if follow:
|
if follow:
|
||||||
geo_iso = self.follow_geometry
|
return geometry
|
||||||
else:
|
|
||||||
geo_iso = self.solid_geometry
|
if geometry:
|
||||||
|
working_geo = geometry
|
||||||
else:
|
else:
|
||||||
if follow:
|
working_geo = self.solid_geometry
|
||||||
geo_iso = self.follow_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:
|
else:
|
||||||
# if isinstance(self.solid_geometry, list):
|
corner_type = 1 if corner is None else corner
|
||||||
# temp_geo = cascaded_union(self.solid_geometry)
|
geo_iso.append(working_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
|
||||||
# else:
|
join_style=corner_type))
|
||||||
# temp_geo = self.solid_geometry
|
|
||||||
|
|
||||||
# Remember: do not make a buffer for each element in the solid_geometry because it will cut into
|
self.app.proc_container.update_view_text(' %s' % _("Buffering"))
|
||||||
# other copper features
|
geo_iso = unary_union(geo_iso)
|
||||||
# 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('')
|
self.app.proc_container.update_view_text('')
|
||||||
# end of replaced block
|
# end of replaced block
|
||||||
if follow:
|
|
||||||
return geo_iso
|
if iso_type == 2:
|
||||||
elif iso_type == 2:
|
|
||||||
return geo_iso
|
return geo_iso
|
||||||
elif iso_type == 0:
|
elif iso_type == 0:
|
||||||
self.app.proc_container.update_view_text(' %s' % _("Get Exteriors"))
|
self.app.proc_container.update_view_text(' %s' % _("Get Exteriors"))
|
||||||
|
|
|
@ -281,6 +281,7 @@ class GerberObjectUI(ObjectUI):
|
||||||
self.custom_box.addLayout(grid1)
|
self.custom_box.addLayout(grid1)
|
||||||
grid1.setColumnStretch(0, 0)
|
grid1.setColumnStretch(0, 0)
|
||||||
grid1.setColumnStretch(1, 1)
|
grid1.setColumnStretch(1, 1)
|
||||||
|
grid1.setColumnStretch(2, 1)
|
||||||
|
|
||||||
# Tool Type
|
# Tool Type
|
||||||
self.tool_type_label = QtWidgets.QLabel('%s:' % _('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"
|
"When the 'V-shape' is selected then the tool\n"
|
||||||
"diameter will depend on the chosen cut depth.")
|
"diameter will depend on the chosen cut depth.")
|
||||||
)
|
)
|
||||||
self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
|
self.tool_type_radio = RadioSet([{'label': _('Circular'), 'value': 'circular'},
|
||||||
{'label': 'V-Shape', 'value': 'v'}])
|
{'label': _('V-Shape'), 'value': 'v'}])
|
||||||
|
|
||||||
grid1.addWidget(self.tool_type_label, 0, 0)
|
grid1.addWidget(self.tool_type_label, 0, 0)
|
||||||
grid1.addWidget(self.tool_type_radio, 0, 1, 1, 2)
|
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)
|
grid1.addWidget(self.milling_type_radio, 7, 1, 1, 2)
|
||||||
|
|
||||||
# combine all passes CB
|
# combine all passes CB
|
||||||
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
|
self.combine_passes_cb = FCCheckBox(label=_('Combine'))
|
||||||
self.combine_passes_cb.setToolTip(
|
self.combine_passes_cb.setToolTip(
|
||||||
_("Combine all passes into one object")
|
_("Combine all passes into one object")
|
||||||
)
|
)
|
||||||
|
@ -406,15 +407,15 @@ class GerberObjectUI(ObjectUI):
|
||||||
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
|
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
|
||||||
"This means that it will cut through\n"
|
"This means that it will cut through\n"
|
||||||
"the middle of the trace."))
|
"the middle of the trace."))
|
||||||
|
grid1.addWidget(self.combine_passes_cb, 8, 0)
|
||||||
|
|
||||||
# avoid an area from isolation
|
# avoid an area from isolation
|
||||||
self.except_cb = FCCheckBox(label=_('Except'))
|
self.except_cb = FCCheckBox(label=_('Except'))
|
||||||
|
grid1.addWidget(self.follow_cb, 8, 1)
|
||||||
|
|
||||||
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
|
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
|
||||||
"by checking this, the area of the object bellow\n"
|
"by checking this, the area of the object bellow\n"
|
||||||
"will be subtracted from the isolation geometry."))
|
"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)
|
grid1.addWidget(self.except_cb, 8, 2)
|
||||||
|
|
||||||
# ## Form Layout
|
# ## Form Layout
|
||||||
|
@ -454,8 +455,50 @@ class GerberObjectUI(ObjectUI):
|
||||||
|
|
||||||
form_layout.addRow(self.obj_label, self.obj_combo)
|
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"
|
_("Create a Geometry object with toolpaths to cut \n"
|
||||||
"isolation outside, inside or on both sides of the\n"
|
"isolation outside, inside or on both sides of the\n"
|
||||||
"object. For a Gerber object outside means outside\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"
|
"inside the actual Gerber feature, use a negative tool\n"
|
||||||
"diameter above.")
|
"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 = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
|
||||||
self.create_buffer_button.setToolTip(
|
self.create_buffer_button.setToolTip(
|
||||||
|
@ -475,48 +518,15 @@ class GerberObjectUI(ObjectUI):
|
||||||
"Clicking this will create the buffered geometry\n"
|
"Clicking this will create the buffered geometry\n"
|
||||||
"required for isolation.")
|
"required for isolation.")
|
||||||
)
|
)
|
||||||
grid1.addWidget(self.create_buffer_button, 11, 0, 1, 3)
|
grid1.addWidget(self.create_buffer_button, 13, 0, 1, 2)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.ohis_iso = OptionalHideInputSection(
|
self.ohis_iso = OptionalHideInputSection(
|
||||||
self.except_cb,
|
self.except_cb,
|
||||||
[self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
|
[self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
|
||||||
logic=True
|
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 #######################
|
# ########## NEW GRID #######################
|
||||||
|
@ -562,6 +572,11 @@ class GerberObjectUI(ObjectUI):
|
||||||
grid2.addWidget(self.board_cutout_label, 2, 0)
|
grid2.addWidget(self.board_cutout_label, 2, 0)
|
||||||
grid2.addWidget(self.generate_cutout_button, 2, 1)
|
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
|
# ## Non-copper regions
|
||||||
self.noncopper_label = QtWidgets.QLabel("<b>%s</b>" % _("Non-copper regions"))
|
self.noncopper_label = QtWidgets.QLabel("<b>%s</b>" % _("Non-copper regions"))
|
||||||
self.noncopper_label.setToolTip(
|
self.noncopper_label.setToolTip(
|
||||||
|
@ -572,7 +587,7 @@ class GerberObjectUI(ObjectUI):
|
||||||
"copper from a specified region.")
|
"copper from a specified region.")
|
||||||
)
|
)
|
||||||
|
|
||||||
grid2.addWidget(self.noncopper_label, 3, 0, 1, 2)
|
grid2.addWidget(self.noncopper_label, 4, 0, 1, 2)
|
||||||
|
|
||||||
# Margin
|
# Margin
|
||||||
bmlabel = QtWidgets.QLabel('%s:' % _('Boundary 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.set_precision(self.decimals)
|
||||||
self.noncopper_margin_entry.setSingleStep(0.1)
|
self.noncopper_margin_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
grid2.addWidget(bmlabel, 4, 0)
|
grid2.addWidget(bmlabel, 5, 0)
|
||||||
grid2.addWidget(self.noncopper_margin_entry, 4, 1)
|
grid2.addWidget(self.noncopper_margin_entry, 5, 1)
|
||||||
|
|
||||||
# Rounded corners
|
# Rounded corners
|
||||||
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
|
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
|
||||||
|
@ -599,8 +614,13 @@ class GerberObjectUI(ObjectUI):
|
||||||
self.noncopper_rounded_cb.setMinimumWidth(90)
|
self.noncopper_rounded_cb.setMinimumWidth(90)
|
||||||
|
|
||||||
self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
|
self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
|
||||||
grid2.addWidget(self.noncopper_rounded_cb, 5, 0)
|
grid2.addWidget(self.noncopper_rounded_cb, 6, 0)
|
||||||
grid2.addWidget(self.generate_noncopper_button, 5, 1)
|
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
|
# ## Bounding box
|
||||||
self.boundingbox_label = QtWidgets.QLabel('<b>%s</b>' % _('Bounding Box'))
|
self.boundingbox_label = QtWidgets.QLabel('<b>%s</b>' % _('Bounding Box'))
|
||||||
|
@ -609,7 +629,7 @@ class GerberObjectUI(ObjectUI):
|
||||||
"Square shape.")
|
"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 = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
|
||||||
bbmargin.setToolTip(
|
bbmargin.setToolTip(
|
||||||
|
@ -622,8 +642,8 @@ class GerberObjectUI(ObjectUI):
|
||||||
self.bbmargin_entry.set_precision(self.decimals)
|
self.bbmargin_entry.set_precision(self.decimals)
|
||||||
self.bbmargin_entry.setSingleStep(0.1)
|
self.bbmargin_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
grid2.addWidget(bbmargin, 7, 0)
|
grid2.addWidget(bbmargin, 9, 0)
|
||||||
grid2.addWidget(self.bbmargin_entry, 7, 1)
|
grid2.addWidget(self.bbmargin_entry, 9, 1)
|
||||||
|
|
||||||
self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
|
self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
|
||||||
self.bbrounded_cb.setToolTip(
|
self.bbrounded_cb.setToolTip(
|
||||||
|
@ -638,9 +658,13 @@ class GerberObjectUI(ObjectUI):
|
||||||
self.generate_bb_button.setToolTip(
|
self.generate_bb_button.setToolTip(
|
||||||
_("Generate the Geometry object.")
|
_("Generate the Geometry object.")
|
||||||
)
|
)
|
||||||
grid2.addWidget(self.bbrounded_cb, 8, 0)
|
grid2.addWidget(self.bbrounded_cb, 10, 0)
|
||||||
grid2.addWidget(self.generate_bb_button, 8, 1)
|
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):
|
class ExcellonObjectUI(ObjectUI):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1344,9 +1344,10 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
|
||||||
_("Width of the isolation gap in\n"
|
_("Width of the isolation gap in\n"
|
||||||
"number (integer) of tool widths.")
|
"number (integer) of tool widths.")
|
||||||
)
|
)
|
||||||
grid0.addWidget(passlabel, 1, 0)
|
|
||||||
self.iso_width_entry = FCSpinner()
|
self.iso_width_entry = FCSpinner()
|
||||||
self.iso_width_entry.setRange(1, 999)
|
self.iso_width_entry.setRange(1, 999)
|
||||||
|
|
||||||
|
grid0.addWidget(passlabel, 1, 0)
|
||||||
grid0.addWidget(self.iso_width_entry, 1, 1)
|
grid0.addWidget(self.iso_width_entry, 1, 1)
|
||||||
|
|
||||||
# Pass overlap
|
# Pass overlap
|
||||||
|
@ -1356,14 +1357,28 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
|
||||||
"Example:\n"
|
"Example:\n"
|
||||||
"A value here of 0.25 means an overlap of 25%% from the tool diameter found above.")
|
"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 = FCDoubleSpinner()
|
||||||
self.iso_overlap_entry.set_precision(3)
|
self.iso_overlap_entry.set_precision(3)
|
||||||
self.iso_overlap_entry.setWrapping(True)
|
self.iso_overlap_entry.setWrapping(True)
|
||||||
self.iso_overlap_entry.setRange(0.000, 0.999)
|
self.iso_overlap_entry.setRange(0.000, 0.999)
|
||||||
self.iso_overlap_entry.setSingleStep(0.1)
|
self.iso_overlap_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
grid0.addWidget(overlabel, 2, 0)
|
||||||
grid0.addWidget(self.iso_overlap_entry, 2, 1)
|
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
|
||||||
milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
|
milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
|
||||||
milling_type_label.setToolTip(
|
milling_type_label.setToolTip(
|
||||||
|
@ -1371,17 +1386,17 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
|
||||||
"- climb / best for precision milling and to reduce tool usage\n"
|
"- climb / best for precision milling and to reduce tool usage\n"
|
||||||
"- conventional / useful when there is no backlash compensation")
|
"- 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'},
|
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
|
||||||
{'label': _('Conv.'), 'value': 'cv'}])
|
{'label': _('Conv.'), 'value': 'cv'}])
|
||||||
grid0.addWidget(self.milling_type_radio, 3, 1)
|
grid0.addWidget(self.milling_type_radio, 4, 1)
|
||||||
|
|
||||||
# Combine passes
|
# Combine passes
|
||||||
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
|
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
|
||||||
self.combine_passes_cb.setToolTip(
|
self.combine_passes_cb.setToolTip(
|
||||||
_("Combine all passes into one object")
|
_("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
|
# ## Clear non-copper regions
|
||||||
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("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.set_range(-99.9999, -0.0001)
|
||||||
self.cutz_spinner.setSingleStep(0.1)
|
self.cutz_spinner.setSingleStep(0.1)
|
||||||
self.cutz_spinner.setWrapping(True)
|
self.cutz_spinner.setWrapping(True)
|
||||||
|
|
||||||
grid0.addWidget(self.cutzlabel, 5, 0)
|
grid0.addWidget(self.cutzlabel, 5, 0)
|
||||||
grid0.addWidget(self.cutz_spinner, 5, 1, 1, 2)
|
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 Type
|
||||||
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
|
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
|
||||||
buffering_label.setToolTip(
|
buffering_label.setToolTip(
|
||||||
|
@ -1550,8 +1585,8 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
|
||||||
)
|
)
|
||||||
self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
|
self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
|
||||||
{'label': _('Full'), 'value': 'full'}])
|
{'label': _('Full'), 'value': 'full'}])
|
||||||
grid0.addWidget(buffering_label, 6, 0)
|
grid0.addWidget(buffering_label, 7, 0)
|
||||||
grid0.addWidget(self.buffering_radio, 6, 1)
|
grid0.addWidget(self.buffering_radio, 7, 1)
|
||||||
|
|
||||||
# Simplification
|
# Simplification
|
||||||
self.simplify_cb = FCCheckBox(label=_('Simplify'))
|
self.simplify_cb = FCCheckBox(label=_('Simplify'))
|
||||||
|
@ -1560,7 +1595,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
|
||||||
"loaded with simplification having a set tolerance.\n"
|
"loaded with simplification having a set tolerance.\n"
|
||||||
"<<WARNING>>: Don't change this unless you know what you are doing !!!")
|
"<<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
|
# Simplification tolerance
|
||||||
self.simplification_tol_label = QtWidgets.QLabel(_('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.setRange(0.00000, 0.01000)
|
||||||
self.simplification_tol_spinner.setSingleStep(0.0001)
|
self.simplification_tol_spinner.setSingleStep(0.0001)
|
||||||
|
|
||||||
grid0.addWidget(self.simplification_tol_label, 8, 0)
|
grid0.addWidget(self.simplification_tol_label, 9, 0)
|
||||||
grid0.addWidget(self.simplification_tol_spinner, 8, 1)
|
grid0.addWidget(self.simplification_tol_spinner, 9, 1)
|
||||||
self.ois_simplif = OptionalInputSection(self.simplify_cb,
|
self.ois_simplif = OptionalInputSection(
|
||||||
[self.simplification_tol_label, self.simplification_tol_spinner],
|
self.simplify_cb,
|
||||||
logic=True)
|
[
|
||||||
|
self.simplification_tol_label, self.simplification_tol_spinner
|
||||||
|
],
|
||||||
|
logic=True)
|
||||||
|
|
||||||
self.layout.addStretch()
|
self.layout.addStretch()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue