- Tool Punch Gerber - updated the UI
- Tool Panelize - updated the UI - Tool Extract Drills - updated the UI - Tool QRcode - updated the UI - Tool SolderPaste - updated the UI - Tool DblSided - updated the UI
This commit is contained in:
parent
651b3137e9
commit
0a64b02397
|
@ -17,6 +17,12 @@ CHANGELOG for FlatCAM beta
|
|||
- more typos fixed in Excellon parser, slots processing
|
||||
- fixed Extract Drills Tool to work with the new Excellon data format
|
||||
- minor fix in App Tools that were updated to have UI in a separate class
|
||||
- Tool Punch Gerber - updated the UI
|
||||
- Tool Panelize - updated the UI
|
||||
- Tool Extract Drills - updated the UI
|
||||
- Tool QRcode - updated the UI
|
||||
- Tool SolderPaste - updated the UI
|
||||
- Tool DblSided - updated the UI
|
||||
|
||||
15.06.2020
|
||||
|
||||
|
|
|
@ -23,12 +23,441 @@ log = logging.getLogger('base')
|
|||
|
||||
class DblSidedTool(AppTool):
|
||||
|
||||
toolName = _("2-Sided PCB")
|
||||
|
||||
def __init__(self, app):
|
||||
AppTool.__init__(self, app)
|
||||
self.decimals = self.app.decimals
|
||||
|
||||
# #############################################################################
|
||||
# ######################### Tool GUI ##########################################
|
||||
# #############################################################################
|
||||
self.ui = DsidedUI(layout=self.layout, app=self.app)
|
||||
self.toolName = self.ui.toolName
|
||||
|
||||
# ## Signals
|
||||
self.ui.mirror_gerber_button.clicked.connect(self.on_mirror_gerber)
|
||||
self.ui.mirror_exc_button.clicked.connect(self.on_mirror_exc)
|
||||
self.ui.mirror_geo_button.clicked.connect(self.on_mirror_geo)
|
||||
|
||||
self.ui.add_point_button.clicked.connect(self.on_point_add)
|
||||
self.ui.add_drill_point_button.clicked.connect(self.on_drill_add)
|
||||
self.ui.delete_drill_point_button.clicked.connect(self.on_drill_delete_last)
|
||||
self.ui.box_type_radio.activated_custom.connect(self.on_combo_box_type)
|
||||
|
||||
self.ui.axis_location.group_toggle_fn = self.on_toggle_pointbox
|
||||
|
||||
self.ui.point_entry.textChanged.connect(lambda val: self.ui.align_ref_label_val.set_value(val))
|
||||
|
||||
self.ui.xmin_btn.clicked.connect(self.on_xmin_clicked)
|
||||
self.ui.ymin_btn.clicked.connect(self.on_ymin_clicked)
|
||||
self.ui.xmax_btn.clicked.connect(self.on_xmax_clicked)
|
||||
self.ui.ymax_btn.clicked.connect(self.on_ymax_clicked)
|
||||
|
||||
self.ui.center_btn.clicked.connect(
|
||||
lambda: self.ui.point_entry.set_value(self.ui.center_entry.get_value())
|
||||
)
|
||||
|
||||
self.ui.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
|
||||
self.ui.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
|
||||
|
||||
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
||||
|
||||
self.drill_values = ""
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+D', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("Tool2Sided()")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("2-Sided Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.reset_fields()
|
||||
|
||||
self.ui.point_entry.set_value("")
|
||||
self.ui.alignment_holes.set_value("")
|
||||
|
||||
self.ui.mirror_axis.set_value(self.app.defaults["tools_2sided_mirror_axis"])
|
||||
self.ui.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
|
||||
self.ui.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
|
||||
self.ui.align_axis_radio.set_value(self.app.defaults["tools_2sided_allign_axis"])
|
||||
|
||||
self.ui.xmin_entry.set_value(0.0)
|
||||
self.ui.ymin_entry.set_value(0.0)
|
||||
self.ui.xmax_entry.set_value(0.0)
|
||||
self.ui.ymax_entry.set_value(0.0)
|
||||
self.ui.center_entry.set_value('')
|
||||
|
||||
self.ui.align_ref_label_val.set_value('%.*f' % (self.decimals, 0.0))
|
||||
|
||||
# run once to make sure that the obj_type attribute is updated in the FCComboBox
|
||||
self.ui.box_type_radio.set_value('grb')
|
||||
self.on_combo_box_type('grb')
|
||||
|
||||
def on_combo_box_type(self, val):
|
||||
obj_type = {'grb': 0, 'exc': 1, 'geo': 2}[val]
|
||||
self.ui.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.ui.box_combo.setCurrentIndex(0)
|
||||
self.ui.box_combo.obj_type = {
|
||||
"grb": "Gerber", "exc": "Excellon", "geo": "Geometry"}[val]
|
||||
|
||||
def on_create_alignment_holes(self):
|
||||
axis = self.ui.align_axis_radio.get_value()
|
||||
mode = self.ui.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
except TypeError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' reference is selected and 'Point' coordinates "
|
||||
"are missing. Add them and retry."))
|
||||
return
|
||||
else:
|
||||
selection_index = self.ui.box_combo.currentIndex()
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.exc_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
model_index = self.app.collection.index(selection_index, 0,
|
||||
self.ui.geo_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
self.app.inform.emit(
|
||||
'[WARNING_NOTCL] %s' % _("There is no Box reference object loaded. Load one and retry."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
|
||||
|
||||
dia = float(self.drill_dia.get_value())
|
||||
if dia == '':
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("No value or wrong format in Drill Dia entry. Add it and retry."))
|
||||
return
|
||||
|
||||
tools = {}
|
||||
tools[1] = {}
|
||||
tools[1]["tooldia"] = dia
|
||||
tools[1]['solid_geometry'] = []
|
||||
|
||||
# holes = self.alignment_holes.get_value()
|
||||
holes = eval('[{}]'.format(self.ui.alignment_holes.text()))
|
||||
if not holes:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Alignment Drill Coordinates to use. "
|
||||
"Add them and retry."))
|
||||
return
|
||||
|
||||
for hole in holes:
|
||||
point = Point(hole)
|
||||
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||
|
||||
tools[1]['drills'] = [point, point_mirror]
|
||||
tools[1]['solid_geometry'].append(point)
|
||||
tools[1]['solid_geometry'].append(point_mirror)
|
||||
|
||||
def obj_init(obj_inst, app_inst):
|
||||
obj_inst.tools = tools
|
||||
obj_inst.create_geometry()
|
||||
obj_inst.source_file = app_inst.export_excellon(obj_name=obj_inst.options['name'], local_use=obj_inst,
|
||||
filename=None, use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object("excellon", "Alignment Drills", obj_init)
|
||||
self.drill_values = ''
|
||||
self.app.inform.emit('[success] %s' % _("Excellon object with alignment drills created..."))
|
||||
|
||||
def on_mirror_gerber(self):
|
||||
selection_index = self.ui.gerber_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'gerber':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.ui.mirror_axis.get_value()
|
||||
mode = self.ui.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
except TypeError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Point coordinates in the Point field. "
|
||||
"Add coords and try again ..."))
|
||||
return
|
||||
|
||||
else:
|
||||
selection_index_box = self.ui.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.ui.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Gerber %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_mirror_exc(self):
|
||||
selection_index = self.ui.exc_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.exc_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'excellon':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.ui.mirror_axis.get_value()
|
||||
mode = self.ui.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
except Exception as e:
|
||||
log.debug("DblSidedTool.on_mirror_geo() --> %s" % str(e))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Point coordinates in the Point field. "
|
||||
"Add coords and try again ..."))
|
||||
return
|
||||
else:
|
||||
selection_index_box = self.ui.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.ui.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception as e:
|
||||
log.debug("DblSidedTool.on_mirror_geo() --> %s" % str(e))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Excellon %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_mirror_geo(self):
|
||||
selection_index = self.ui.geo_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.geo_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'geometry':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.ui.mirror_axis.get_value()
|
||||
mode = self.ui.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
else:
|
||||
selection_index_box = self.ui.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.ui.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Geometry %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_point_add(self):
|
||||
val = self.app.defaults["global_point_clipboard_format"] % \
|
||||
(self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])
|
||||
self.ui.point_entry.set_value(val)
|
||||
|
||||
def on_drill_add(self):
|
||||
self.drill_values += (self.app.defaults["global_point_clipboard_format"] %
|
||||
(self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])) + ','
|
||||
self.ui.alignment_holes.set_value(self.drill_values)
|
||||
|
||||
def on_drill_delete_last(self):
|
||||
drill_values_without_last_tupple = self.drill_values.rpartition('(')[0]
|
||||
self.drill_values = drill_values_without_last_tupple
|
||||
self.ui.alignment_holes.set_value(self.drill_values)
|
||||
|
||||
def on_toggle_pointbox(self):
|
||||
if self.ui.axis_location.get_value() == "point":
|
||||
self.ui.point_entry.show()
|
||||
self.ui.add_point_button.show()
|
||||
self.ui.box_type_label.hide()
|
||||
self.ui.box_type_radio.hide()
|
||||
self.ui.box_combo.hide()
|
||||
|
||||
self.ui.align_ref_label_val.set_value(self.ui.point_entry.get_value())
|
||||
else:
|
||||
self.ui.point_entry.hide()
|
||||
self.ui.add_point_button.hide()
|
||||
|
||||
self.ui.box_type_label.show()
|
||||
self.ui.box_type_radio.show()
|
||||
self.ui.box_combo.show()
|
||||
|
||||
self.ui.align_ref_label_val.set_value("Box centroid")
|
||||
|
||||
def on_bbox_coordinates(self):
|
||||
|
||||
xmin = Inf
|
||||
ymin = Inf
|
||||
xmax = -Inf
|
||||
ymax = -Inf
|
||||
|
||||
obj_list = self.app.collection.get_selected()
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
|
||||
return
|
||||
|
||||
for obj in obj_list:
|
||||
try:
|
||||
gxmin, gymin, gxmax, gymax = obj.bounds()
|
||||
xmin = min([xmin, gxmin])
|
||||
ymin = min([ymin, gymin])
|
||||
xmax = max([xmax, gxmax])
|
||||
ymax = max([ymax, gymax])
|
||||
except Exception as e:
|
||||
log.warning("DEV WARNING: Tried to get bounds of empty geometry in DblSidedTool. %s" % str(e))
|
||||
|
||||
self.ui.xmin_entry.set_value(xmin)
|
||||
self.ui.ymin_entry.set_value(ymin)
|
||||
self.ui.xmax_entry.set_value(xmax)
|
||||
self.ui.ymax_entry.set_value(ymax)
|
||||
cx = '%.*f' % (self.decimals, (((xmax - xmin) / 2.0) + xmin))
|
||||
cy = '%.*f' % (self.decimals, (((ymax - ymin) / 2.0) + ymin))
|
||||
val_txt = '(%s, %s)' % (cx, cy)
|
||||
|
||||
self.ui.center_entry.set_value(val_txt)
|
||||
self.ui.axis_location.set_value('point')
|
||||
self.ui.point_entry.set_value(val_txt)
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
def on_xmin_clicked(self):
|
||||
xmin = self.ui.xmin_entry.get_value()
|
||||
self.ui.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, py)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, 0.0)
|
||||
self.ui.point_entry.set_value(val)
|
||||
|
||||
def on_ymin_clicked(self):
|
||||
ymin = self.ui.ymin_entry.get_value()
|
||||
self.ui.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymin)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymin)
|
||||
self.ui.point_entry.set_value(val)
|
||||
|
||||
def on_xmax_clicked(self):
|
||||
xmax = self.ui.xmax_entry.get_value()
|
||||
self.ui.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, py)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, 0.0)
|
||||
self.ui.point_entry.set_value(val)
|
||||
|
||||
def on_ymax_clicked(self):
|
||||
ymax = self.ui.ymax_entry.get_value()
|
||||
self.ui.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.ui.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymax)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymax)
|
||||
self.ui.point_entry.set_value(val)
|
||||
|
||||
def reset_fields(self):
|
||||
self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.ui.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
|
||||
self.ui.geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
|
||||
self.ui.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
self.ui.gerber_object_combo.setCurrentIndex(0)
|
||||
self.ui.exc_object_combo.setCurrentIndex(0)
|
||||
self.ui.geo_object_combo.setCurrentIndex(0)
|
||||
self.ui.box_combo.setCurrentIndex(0)
|
||||
self.ui.box_type_radio.set_value('grb')
|
||||
|
||||
self.drill_values = ""
|
||||
self.ui.align_ref_label_val.set_value('')
|
||||
|
||||
|
||||
class DsidedUI:
|
||||
|
||||
toolName = _("2-Sided PCB")
|
||||
|
||||
def __init__(self, layout, app):
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
self.layout = layout
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
|
@ -480,417 +909,22 @@ class DblSidedTool(AppTool):
|
|||
""")
|
||||
self.layout.addWidget(self.reset_button)
|
||||
|
||||
# ## Signals
|
||||
self.mirror_gerber_button.clicked.connect(self.on_mirror_gerber)
|
||||
self.mirror_exc_button.clicked.connect(self.on_mirror_exc)
|
||||
self.mirror_geo_button.clicked.connect(self.on_mirror_geo)
|
||||
# #################################### FINSIHED GUI ###########################
|
||||
# #############################################################################
|
||||
|
||||
self.add_point_button.clicked.connect(self.on_point_add)
|
||||
self.add_drill_point_button.clicked.connect(self.on_drill_add)
|
||||
self.delete_drill_point_button.clicked.connect(self.on_drill_delete_last)
|
||||
self.box_type_radio.activated_custom.connect(self.on_combo_box_type)
|
||||
|
||||
self.axis_location.group_toggle_fn = self.on_toggle_pointbox
|
||||
|
||||
self.point_entry.textChanged.connect(lambda val: self.align_ref_label_val.set_value(val))
|
||||
|
||||
self.xmin_btn.clicked.connect(self.on_xmin_clicked)
|
||||
self.ymin_btn.clicked.connect(self.on_ymin_clicked)
|
||||
self.xmax_btn.clicked.connect(self.on_xmax_clicked)
|
||||
self.ymax_btn.clicked.connect(self.on_ymax_clicked)
|
||||
|
||||
self.center_btn.clicked.connect(
|
||||
lambda: self.point_entry.set_value(self.center_entry.get_value())
|
||||
)
|
||||
|
||||
self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
|
||||
self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
|
||||
|
||||
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||
|
||||
self.drill_values = ""
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+D', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("Tool2Sided()")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
def confirmation_message(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
|
||||
self.decimals,
|
||||
minval,
|
||||
self.decimals,
|
||||
maxval), False)
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
||||
def confirmation_message_int(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
|
||||
(_("Edited value is out of range"), minval, maxval), False)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("2-Sided Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.reset_fields()
|
||||
|
||||
self.point_entry.set_value("")
|
||||
self.alignment_holes.set_value("")
|
||||
|
||||
self.mirror_axis.set_value(self.app.defaults["tools_2sided_mirror_axis"])
|
||||
self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
|
||||
self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
|
||||
self.align_axis_radio.set_value(self.app.defaults["tools_2sided_allign_axis"])
|
||||
|
||||
self.xmin_entry.set_value(0.0)
|
||||
self.ymin_entry.set_value(0.0)
|
||||
self.xmax_entry.set_value(0.0)
|
||||
self.ymax_entry.set_value(0.0)
|
||||
self.center_entry.set_value('')
|
||||
|
||||
self.align_ref_label_val.set_value('%.*f' % (self.decimals, 0.0))
|
||||
|
||||
# run once to make sure that the obj_type attribute is updated in the FCComboBox
|
||||
self.box_type_radio.set_value('grb')
|
||||
self.on_combo_box_type('grb')
|
||||
|
||||
def on_combo_box_type(self, val):
|
||||
obj_type = {'grb': 0, 'exc': 1, 'geo': 2}[val]
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.setCurrentIndex(0)
|
||||
self.box_combo.obj_type = {
|
||||
"grb": "Gerber", "exc": "Excellon", "geo": "Geometry"}[val]
|
||||
|
||||
def on_create_alignment_holes(self):
|
||||
axis = self.align_axis_radio.get_value()
|
||||
mode = self.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
except TypeError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' reference is selected and 'Point' coordinates "
|
||||
"are missing. Add them and retry."))
|
||||
return
|
||||
else:
|
||||
selection_index = self.box_combo.currentIndex()
|
||||
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
model_index = self.app.collection.index(selection_index, 0, self.exc_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
model_index = self.app.collection.index(selection_index, 0,
|
||||
self.geo_object_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index.internalPointer().obj
|
||||
except AttributeError:
|
||||
self.app.inform.emit(
|
||||
'[WARNING_NOTCL] %s' % _("There is no Box reference object loaded. Load one and retry."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
|
||||
|
||||
dia = float(self.drill_dia.get_value())
|
||||
if dia == '':
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("No value or wrong format in Drill Dia entry. Add it and retry."))
|
||||
return
|
||||
|
||||
tools = {}
|
||||
tools[1] = {}
|
||||
tools[1]["tooldia"] = dia
|
||||
tools[1]['solid_geometry'] = []
|
||||
|
||||
# holes = self.alignment_holes.get_value()
|
||||
holes = eval('[{}]'.format(self.alignment_holes.text()))
|
||||
if not holes:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Alignment Drill Coordinates to use. "
|
||||
"Add them and retry."))
|
||||
return
|
||||
|
||||
for hole in holes:
|
||||
point = Point(hole)
|
||||
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||
|
||||
tools[1]['drills'] = [point, point_mirror]
|
||||
tools[1]['solid_geometry'].append(point)
|
||||
tools[1]['solid_geometry'].append(point_mirror)
|
||||
|
||||
def obj_init(obj_inst, app_inst):
|
||||
obj_inst.tools = tools
|
||||
obj_inst.create_geometry()
|
||||
obj_inst.source_file = app_inst.export_excellon(obj_name=obj_inst.options['name'], local_use=obj_inst,
|
||||
filename=None, use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object("excellon", "Alignment Drills", obj_init)
|
||||
self.drill_values = ''
|
||||
self.app.inform.emit('[success] %s' % _("Excellon object with alignment drills created..."))
|
||||
|
||||
def on_mirror_gerber(self):
|
||||
selection_index = self.gerber_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'gerber':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.mirror_axis.get_value()
|
||||
mode = self.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
except TypeError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Point coordinates in the Point field. "
|
||||
"Add coords and try again ..."))
|
||||
return
|
||||
|
||||
else:
|
||||
selection_index_box = self.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Gerber %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_mirror_exc(self):
|
||||
selection_index = self.exc_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.exc_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'excellon':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.mirror_axis.get_value()
|
||||
mode = self.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
except Exception as e:
|
||||
log.debug("DblSidedTool.on_mirror_geo() --> %s" % str(e))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There are no Point coordinates in the Point field. "
|
||||
"Add coords and try again ..."))
|
||||
return
|
||||
else:
|
||||
selection_index_box = self.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception as e:
|
||||
log.debug("DblSidedTool.on_mirror_geo() --> %s" % str(e))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Excellon %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_mirror_geo(self):
|
||||
selection_index = self.geo_object_combo.currentIndex()
|
||||
# fcobj = self.app.collection.object_list[selection_index]
|
||||
model_index = self.app.collection.index(selection_index, 0, self.geo_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ..."))
|
||||
return
|
||||
|
||||
if fcobj.kind != 'geometry':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored."))
|
||||
return
|
||||
|
||||
axis = self.mirror_axis.get_value()
|
||||
mode = self.axis_location.get_value()
|
||||
|
||||
if mode == "point":
|
||||
px, py = self.point_entry.get_value()
|
||||
else:
|
||||
selection_index_box = self.box_combo.currentIndex()
|
||||
model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex())
|
||||
try:
|
||||
bb_obj = model_index_box.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ..."))
|
||||
return
|
||||
|
||||
xmin, ymin, xmax, ymax = bb_obj.bounds()
|
||||
px = 0.5 * (xmin + xmax)
|
||||
py = 0.5 * (ymin + ymax)
|
||||
|
||||
fcobj.mirror(axis, [px, py])
|
||||
self.app.app_obj.object_changed.emit(fcobj)
|
||||
fcobj.plot()
|
||||
self.app.inform.emit('[success] Geometry %s %s...' % (str(fcobj.options['name']), _("was mirrored")))
|
||||
|
||||
def on_point_add(self):
|
||||
val = self.app.defaults["global_point_clipboard_format"] % \
|
||||
(self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])
|
||||
self.point_entry.set_value(val)
|
||||
|
||||
def on_drill_add(self):
|
||||
self.drill_values += (self.app.defaults["global_point_clipboard_format"] %
|
||||
(self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])) + ','
|
||||
self.alignment_holes.set_value(self.drill_values)
|
||||
|
||||
def on_drill_delete_last(self):
|
||||
drill_values_without_last_tupple = self.drill_values.rpartition('(')[0]
|
||||
self.drill_values = drill_values_without_last_tupple
|
||||
self.alignment_holes.set_value(self.drill_values)
|
||||
|
||||
def on_toggle_pointbox(self):
|
||||
if self.axis_location.get_value() == "point":
|
||||
self.point_entry.show()
|
||||
self.add_point_button.show()
|
||||
self.box_type_label.hide()
|
||||
self.box_type_radio.hide()
|
||||
self.box_combo.hide()
|
||||
|
||||
self.align_ref_label_val.set_value(self.point_entry.get_value())
|
||||
else:
|
||||
self.point_entry.hide()
|
||||
self.add_point_button.hide()
|
||||
|
||||
self.box_type_label.show()
|
||||
self.box_type_radio.show()
|
||||
self.box_combo.show()
|
||||
|
||||
self.align_ref_label_val.set_value("Box centroid")
|
||||
|
||||
def on_bbox_coordinates(self):
|
||||
|
||||
xmin = Inf
|
||||
ymin = Inf
|
||||
xmax = -Inf
|
||||
ymax = -Inf
|
||||
|
||||
obj_list = self.app.collection.get_selected()
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
|
||||
return
|
||||
|
||||
for obj in obj_list:
|
||||
try:
|
||||
gxmin, gymin, gxmax, gymax = obj.bounds()
|
||||
xmin = min([xmin, gxmin])
|
||||
ymin = min([ymin, gymin])
|
||||
xmax = max([xmax, gxmax])
|
||||
ymax = max([ymax, gymax])
|
||||
except Exception as e:
|
||||
log.warning("DEV WARNING: Tried to get bounds of empty geometry in DblSidedTool. %s" % str(e))
|
||||
|
||||
self.xmin_entry.set_value(xmin)
|
||||
self.ymin_entry.set_value(ymin)
|
||||
self.xmax_entry.set_value(xmax)
|
||||
self.ymax_entry.set_value(ymax)
|
||||
cx = '%.*f' % (self.decimals, (((xmax - xmin) / 2.0) + xmin))
|
||||
cy = '%.*f' % (self.decimals, (((ymax - ymin) / 2.0) + ymin))
|
||||
val_txt = '(%s, %s)' % (cx, cy)
|
||||
|
||||
self.center_entry.set_value(val_txt)
|
||||
self.axis_location.set_value('point')
|
||||
self.point_entry.set_value(val_txt)
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
def on_xmin_clicked(self):
|
||||
xmin = self.xmin_entry.get_value()
|
||||
self.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, py)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmin, self.decimals, 0.0)
|
||||
self.point_entry.set_value(val)
|
||||
|
||||
def on_ymin_clicked(self):
|
||||
ymin = self.ymin_entry.get_value()
|
||||
self.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymin)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymin)
|
||||
self.point_entry.set_value(val)
|
||||
|
||||
def on_xmax_clicked(self):
|
||||
xmax = self.xmax_entry.get_value()
|
||||
self.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, py)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, xmax, self.decimals, 0.0)
|
||||
self.point_entry.set_value(val)
|
||||
|
||||
def on_ymax_clicked(self):
|
||||
ymax = self.ymax_entry.get_value()
|
||||
self.axis_location.set_value('point')
|
||||
|
||||
try:
|
||||
px, py = self.point_entry.get_value()
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, px, self.decimals, ymax)
|
||||
except TypeError:
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.decimals, 0.0, self.decimals, ymax)
|
||||
self.point_entry.set_value(val)
|
||||
|
||||
def reset_fields(self):
|
||||
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
|
||||
self.geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
self.gerber_object_combo.setCurrentIndex(0)
|
||||
self.exc_object_combo.setCurrentIndex(0)
|
||||
self.geo_object_combo.setCurrentIndex(0)
|
||||
self.box_combo.setCurrentIndex(0)
|
||||
self.box_type_radio.set_value('grb')
|
||||
|
||||
self.drill_values = ""
|
||||
self.align_ref_label_val.set_value('')
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
|
|
@ -29,14 +29,232 @@ log = logging.getLogger('base')
|
|||
|
||||
class ToolEtchCompensation(AppTool):
|
||||
|
||||
toolName = _("Etch Compensation Tool")
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
# #############################################################################
|
||||
# ######################### Tool GUI ##########################################
|
||||
# #############################################################################
|
||||
self.ui = EtchUI(layout=self.layout, app=self.app)
|
||||
self.toolName = self.ui.toolName
|
||||
|
||||
self.ui.compensate_btn.clicked.connect(self.on_compensate)
|
||||
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
||||
self.ui.ratio_radio.activated_custom.connect(self.on_ratio_change)
|
||||
|
||||
self.ui.oz_entry.textChanged.connect(self.on_oz_conversion)
|
||||
self.ui.mils_entry.textChanged.connect(self.on_mils_conversion)
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("ToolEtchCompensation()")
|
||||
log.debug("ToolEtchCompensation() is running ...")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Etch Compensation Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.ui.thick_entry.set_value(18.0)
|
||||
self.ui.ratio_radio.set_value('factor')
|
||||
|
||||
def on_ratio_change(self, val):
|
||||
"""
|
||||
Called on activated_custom signal of the RadioSet GUI element self.radio_ratio
|
||||
|
||||
:param val: 'c' or 'p': 'c' means custom factor and 'p' means preselected etchants
|
||||
:type val: str
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
if val == 'factor':
|
||||
self.ui.etchants_label.hide()
|
||||
self.ui.etchants_combo.hide()
|
||||
self.ui.factor_label.show()
|
||||
self.ui.factor_entry.show()
|
||||
self.ui.offset_label.hide()
|
||||
self.ui.offset_entry.hide()
|
||||
elif val == 'etch_list':
|
||||
self.ui.etchants_label.show()
|
||||
self.ui.etchants_combo.show()
|
||||
self.ui.factor_label.hide()
|
||||
self.ui.factor_entry.hide()
|
||||
self.ui.offset_label.hide()
|
||||
self.ui.offset_entry.hide()
|
||||
else:
|
||||
self.ui.etchants_label.hide()
|
||||
self.ui.etchants_combo.hide()
|
||||
self.ui.factor_label.hide()
|
||||
self.ui.factor_entry.hide()
|
||||
self.ui.offset_label.show()
|
||||
self.ui.offset_entry.show()
|
||||
|
||||
def on_oz_conversion(self, txt):
|
||||
try:
|
||||
val = eval(txt)
|
||||
# oz thickness to mils by multiplying with 1.37
|
||||
# mils to microns by multiplying with 25.4
|
||||
val *= 34.798
|
||||
except Exception:
|
||||
self.ui.oz_to_um_entry.set_value('')
|
||||
return
|
||||
self.ui.oz_to_um_entry.set_value(val, self.decimals)
|
||||
|
||||
def on_mils_conversion(self, txt):
|
||||
try:
|
||||
val = eval(txt)
|
||||
val *= 25.4
|
||||
except Exception:
|
||||
self.ui.mils_to_um_entry.set_value('')
|
||||
return
|
||||
self.ui.mils_to_um_entry.set_value(val, self.decimals)
|
||||
|
||||
def on_compensate(self):
|
||||
log.debug("ToolEtchCompensation.on_compensate()")
|
||||
|
||||
ratio_type = self.ui.ratio_radio.get_value()
|
||||
thickness = self.ui.thick_entry.get_value() / 1000 # in microns
|
||||
|
||||
grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
|
||||
obj_name = self.ui.gerber_combo.currentText()
|
||||
|
||||
outname = obj_name + "_comp"
|
||||
|
||||
# Get source object.
|
||||
try:
|
||||
grb_obj = self.app.collection.get_by_name(obj_name)
|
||||
except Exception as e:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
|
||||
return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
|
||||
|
||||
if grb_obj is None:
|
||||
if obj_name == '':
|
||||
obj_name = 'None'
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
if ratio_type == 'factor':
|
||||
etch_factor = 1 / self.ui.factor_entry.get_value()
|
||||
offset = thickness / etch_factor
|
||||
elif ratio_type == 'etch_list':
|
||||
etchant = self.ui.etchants_combo.get_value()
|
||||
if etchant == "CuCl2":
|
||||
etch_factor = 0.33
|
||||
else:
|
||||
etch_factor = 0.25
|
||||
offset = thickness / etch_factor
|
||||
else:
|
||||
offset = self.ui.offset_entry.get_value() / 1000 # in microns
|
||||
|
||||
try:
|
||||
__ = iter(grb_obj.solid_geometry)
|
||||
except TypeError:
|
||||
grb_obj.solid_geometry = list(grb_obj.solid_geometry)
|
||||
|
||||
new_solid_geometry = []
|
||||
|
||||
for poly in grb_obj.solid_geometry:
|
||||
new_solid_geometry.append(poly.buffer(offset, int(grb_circle_steps)))
|
||||
new_solid_geometry = unary_union(new_solid_geometry)
|
||||
|
||||
new_options = {}
|
||||
for opt in grb_obj.options:
|
||||
new_options[opt] = deepcopy(grb_obj.options[opt])
|
||||
|
||||
new_apertures = deepcopy(grb_obj.apertures)
|
||||
|
||||
# update the apertures attributes (keys in the apertures dict)
|
||||
for ap in new_apertures:
|
||||
type = new_apertures[ap]['type']
|
||||
for k in new_apertures[ap]:
|
||||
if type == 'R' or type == 'O':
|
||||
if k == 'width' or k == 'height':
|
||||
new_apertures[ap][k] += offset
|
||||
else:
|
||||
if k == 'size' or k == 'width' or k == 'height':
|
||||
new_apertures[ap][k] += offset
|
||||
|
||||
if k == 'geometry':
|
||||
for geo_el in new_apertures[ap][k]:
|
||||
if 'solid' in geo_el:
|
||||
geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps))
|
||||
|
||||
# in case of 'R' or 'O' aperture type we need to update the aperture 'size' after
|
||||
# the 'width' and 'height' keys were updated
|
||||
for ap in new_apertures:
|
||||
type = new_apertures[ap]['type']
|
||||
for k in new_apertures[ap]:
|
||||
if type == 'R' or type == 'O':
|
||||
if k == 'size':
|
||||
new_apertures[ap][k] = math.sqrt(
|
||||
new_apertures[ap]['width'] ** 2 + new_apertures[ap]['height'] ** 2)
|
||||
|
||||
def init_func(new_obj, app_obj):
|
||||
"""
|
||||
Init a new object in FlatCAM Object collection
|
||||
|
||||
:param new_obj: New object
|
||||
:type new_obj: ObjectCollection
|
||||
:param app_obj: App
|
||||
:type app_obj: app_Main.App
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
new_obj.options.update(new_options)
|
||||
new_obj.options['name'] = outname
|
||||
new_obj.fill_color = deepcopy(grb_obj.fill_color)
|
||||
new_obj.outline_color = deepcopy(grb_obj.outline_color)
|
||||
|
||||
new_obj.apertures = deepcopy(new_apertures)
|
||||
|
||||
new_obj.solid_geometry = deepcopy(new_solid_geometry)
|
||||
new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
|
||||
local_use=new_obj, use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object('gerber', outname, init_func)
|
||||
|
||||
def reset_fields(self):
|
||||
self.ui.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
@staticmethod
|
||||
def poly2rings(poly):
|
||||
return [poly.exterior] + [interior for interior in poly.interiors]
|
||||
|
||||
|
||||
class EtchUI:
|
||||
|
||||
toolName = _("Etch Compensation Tool")
|
||||
|
||||
def __init__(self, layout, app):
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
self.layout = layout
|
||||
|
||||
self.tools_frame = QtWidgets.QFrame()
|
||||
self.tools_frame.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.tools_frame)
|
||||
|
@ -249,207 +467,24 @@ class ToolEtchCompensation(AppTool):
|
|||
""")
|
||||
self.tools_box.addWidget(self.reset_button)
|
||||
|
||||
self.compensate_btn.clicked.connect(self.on_compensate)
|
||||
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||
self.ratio_radio.activated_custom.connect(self.on_ratio_change)
|
||||
# #################################### FINSIHED GUI ###########################
|
||||
# #############################################################################
|
||||
|
||||
self.oz_entry.textChanged.connect(self.on_oz_conversion)
|
||||
self.mils_entry.textChanged.connect(self.on_mils_conversion)
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("ToolEtchCompensation()")
|
||||
log.debug("ToolEtchCompensation() is running ...")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
def confirmation_message(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
|
||||
self.decimals,
|
||||
minval,
|
||||
self.decimals,
|
||||
maxval), False)
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
||||
def confirmation_message_int(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
|
||||
(_("Edited value is out of range"), minval, maxval), False)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Etch Compensation Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.thick_entry.set_value(18.0)
|
||||
self.ratio_radio.set_value('factor')
|
||||
|
||||
def on_ratio_change(self, val):
|
||||
"""
|
||||
Called on activated_custom signal of the RadioSet GUI element self.radio_ratio
|
||||
|
||||
:param val: 'c' or 'p': 'c' means custom factor and 'p' means preselected etchants
|
||||
:type val: str
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
if val == 'factor':
|
||||
self.etchants_label.hide()
|
||||
self.etchants_combo.hide()
|
||||
self.factor_label.show()
|
||||
self.factor_entry.show()
|
||||
self.offset_label.hide()
|
||||
self.offset_entry.hide()
|
||||
elif val == 'etch_list':
|
||||
self.etchants_label.show()
|
||||
self.etchants_combo.show()
|
||||
self.factor_label.hide()
|
||||
self.factor_entry.hide()
|
||||
self.offset_label.hide()
|
||||
self.offset_entry.hide()
|
||||
else:
|
||||
self.etchants_label.hide()
|
||||
self.etchants_combo.hide()
|
||||
self.factor_label.hide()
|
||||
self.factor_entry.hide()
|
||||
self.offset_label.show()
|
||||
self.offset_entry.show()
|
||||
|
||||
def on_oz_conversion(self, txt):
|
||||
try:
|
||||
val = eval(txt)
|
||||
# oz thickness to mils by multiplying with 1.37
|
||||
# mils to microns by multiplying with 25.4
|
||||
val *= 34.798
|
||||
except Exception:
|
||||
self.oz_to_um_entry.set_value('')
|
||||
return
|
||||
self.oz_to_um_entry.set_value(val, self.decimals)
|
||||
|
||||
def on_mils_conversion(self, txt):
|
||||
try:
|
||||
val = eval(txt)
|
||||
val *= 25.4
|
||||
except Exception:
|
||||
self.mils_to_um_entry.set_value('')
|
||||
return
|
||||
self.mils_to_um_entry.set_value(val, self.decimals)
|
||||
|
||||
def on_compensate(self):
|
||||
log.debug("ToolEtchCompensation.on_compensate()")
|
||||
|
||||
ratio_type = self.ratio_radio.get_value()
|
||||
thickness = self.thick_entry.get_value() / 1000 # in microns
|
||||
|
||||
grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
|
||||
obj_name = self.gerber_combo.currentText()
|
||||
|
||||
outname = obj_name + "_comp"
|
||||
|
||||
# Get source object.
|
||||
try:
|
||||
grb_obj = self.app.collection.get_by_name(obj_name)
|
||||
except Exception as e:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
|
||||
return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
|
||||
|
||||
if grb_obj is None:
|
||||
if obj_name == '':
|
||||
obj_name = 'None'
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
|
||||
return
|
||||
|
||||
if ratio_type == 'factor':
|
||||
etch_factor = 1 / self.factor_entry.get_value()
|
||||
offset = thickness / etch_factor
|
||||
elif ratio_type == 'etch_list':
|
||||
etchant = self.etchants_combo.get_value()
|
||||
if etchant == "CuCl2":
|
||||
etch_factor = 0.33
|
||||
else:
|
||||
etch_factor = 0.25
|
||||
offset = thickness / etch_factor
|
||||
else:
|
||||
offset = self.offset_entry.get_value() / 1000 # in microns
|
||||
|
||||
try:
|
||||
__ = iter(grb_obj.solid_geometry)
|
||||
except TypeError:
|
||||
grb_obj.solid_geometry = list(grb_obj.solid_geometry)
|
||||
|
||||
new_solid_geometry = []
|
||||
|
||||
for poly in grb_obj.solid_geometry:
|
||||
new_solid_geometry.append(poly.buffer(offset, int(grb_circle_steps)))
|
||||
new_solid_geometry = unary_union(new_solid_geometry)
|
||||
|
||||
new_options = {}
|
||||
for opt in grb_obj.options:
|
||||
new_options[opt] = deepcopy(grb_obj.options[opt])
|
||||
|
||||
new_apertures = deepcopy(grb_obj.apertures)
|
||||
|
||||
# update the apertures attributes (keys in the apertures dict)
|
||||
for ap in new_apertures:
|
||||
type = new_apertures[ap]['type']
|
||||
for k in new_apertures[ap]:
|
||||
if type == 'R' or type == 'O':
|
||||
if k == 'width' or k == 'height':
|
||||
new_apertures[ap][k] += offset
|
||||
else:
|
||||
if k == 'size' or k == 'width' or k == 'height':
|
||||
new_apertures[ap][k] += offset
|
||||
|
||||
if k == 'geometry':
|
||||
for geo_el in new_apertures[ap][k]:
|
||||
if 'solid' in geo_el:
|
||||
geo_el['solid'] = geo_el['solid'].buffer(offset, int(grb_circle_steps))
|
||||
|
||||
# in case of 'R' or 'O' aperture type we need to update the aperture 'size' after
|
||||
# the 'width' and 'height' keys were updated
|
||||
for ap in new_apertures:
|
||||
type = new_apertures[ap]['type']
|
||||
for k in new_apertures[ap]:
|
||||
if type == 'R' or type == 'O':
|
||||
if k == 'size':
|
||||
new_apertures[ap][k] = math.sqrt(
|
||||
new_apertures[ap]['width'] ** 2 + new_apertures[ap]['height'] ** 2)
|
||||
|
||||
def init_func(new_obj, app_obj):
|
||||
"""
|
||||
Init a new object in FlatCAM Object collection
|
||||
|
||||
:param new_obj: New object
|
||||
:type new_obj: ObjectCollection
|
||||
:param app_obj: App
|
||||
:type app_obj: app_Main.App
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
new_obj.options.update(new_options)
|
||||
new_obj.options['name'] = outname
|
||||
new_obj.fill_color = deepcopy(grb_obj.fill_color)
|
||||
new_obj.outline_color = deepcopy(grb_obj.outline_color)
|
||||
|
||||
new_obj.apertures = deepcopy(new_apertures)
|
||||
|
||||
new_obj.solid_geometry = deepcopy(new_solid_geometry)
|
||||
new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
|
||||
local_use=new_obj, use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object('gerber', outname, init_func)
|
||||
|
||||
def reset_fields(self):
|
||||
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
@staticmethod
|
||||
def poly2rings(poly):
|
||||
return [poly.exterior] + [interior for interior in poly.interiors]
|
||||
# end of file
|
||||
|
|
|
@ -26,12 +26,404 @@ log = logging.getLogger('base')
|
|||
|
||||
class ToolExtractDrills(AppTool):
|
||||
|
||||
toolName = _("Extract Drills")
|
||||
|
||||
def __init__(self, app):
|
||||
AppTool.__init__(self, app)
|
||||
self.decimals = self.app.decimals
|
||||
|
||||
# #############################################################################
|
||||
# ######################### Tool GUI ##########################################
|
||||
# #############################################################################
|
||||
self.ui = ExtractDrillsUI(layout=self.layout, app=self.app)
|
||||
self.toolName = self.ui.toolName
|
||||
|
||||
# ## Signals
|
||||
self.ui.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
|
||||
self.ui.e_drills_button.clicked.connect(self.on_extract_drills_click)
|
||||
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
||||
|
||||
self.ui.circular_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.ui.circular_ring_entry.setDisabled(False) if state else self.ui.circular_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.ui.oblong_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.ui.oblong_ring_entry.setDisabled(False) if state else self.ui.oblong_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.ui.square_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.ui.square_ring_entry.setDisabled(False) if state else self.ui.square_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.ui.rectangular_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.ui.rectangular_ring_entry.setDisabled(False) if state else
|
||||
self.ui.rectangular_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.ui.other_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.ui.other_ring_entry.setDisabled(False) if state else self.ui.other_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("Extract Drills()")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Extract Drills Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.reset_fields()
|
||||
|
||||
self.ui.hole_size_radio.set_value(self.app.defaults["tools_edrills_hole_type"])
|
||||
|
||||
self.ui.dia_entry.set_value(float(self.app.defaults["tools_edrills_hole_fixed_dia"]))
|
||||
|
||||
self.ui.circular_ring_entry.set_value(float(self.app.defaults["tools_edrills_circular_ring"]))
|
||||
self.ui.oblong_ring_entry.set_value(float(self.app.defaults["tools_edrills_oblong_ring"]))
|
||||
self.ui.square_ring_entry.set_value(float(self.app.defaults["tools_edrills_square_ring"]))
|
||||
self.ui.rectangular_ring_entry.set_value(float(self.app.defaults["tools_edrills_rectangular_ring"]))
|
||||
self.ui.other_ring_entry.set_value(float(self.app.defaults["tools_edrills_others_ring"]))
|
||||
|
||||
self.ui.circular_cb.set_value(self.app.defaults["tools_edrills_circular"])
|
||||
self.ui.oblong_cb.set_value(self.app.defaults["tools_edrills_oblong"])
|
||||
self.ui.square_cb.set_value(self.app.defaults["tools_edrills_square"])
|
||||
self.ui.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
|
||||
self.ui.other_cb.set_value(self.app.defaults["tools_edrills_others"])
|
||||
|
||||
self.ui.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
|
||||
|
||||
def on_extract_drills_click(self):
|
||||
|
||||
drill_dia = self.ui.dia_entry.get_value()
|
||||
circ_r_val = self.ui.circular_ring_entry.get_value()
|
||||
oblong_r_val = self.ui.oblong_ring_entry.get_value()
|
||||
square_r_val = self.ui.square_ring_entry.get_value()
|
||||
rect_r_val = self.ui.rectangular_ring_entry.get_value()
|
||||
other_r_val = self.ui.other_ring_entry.get_value()
|
||||
|
||||
prop_factor = self.ui.factor_entry.get_value() / 100.0
|
||||
|
||||
drills = []
|
||||
tools = {}
|
||||
|
||||
selection_index = self.ui.gerber_object_combo.currentIndex()
|
||||
model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
|
||||
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||
return
|
||||
|
||||
outname = fcobj.options['name'].rpartition('.')[0]
|
||||
|
||||
mode = self.ui.hole_size_radio.get_value()
|
||||
|
||||
if mode == 'fixed':
|
||||
tools = {
|
||||
1: {
|
||||
"tooldia": drill_dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
}
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
if ap_type == 'C':
|
||||
if self.ui.circular_cb.get_value() is False:
|
||||
continue
|
||||
elif ap_type == 'O':
|
||||
if self.ui.oblong_cb.get_value() is False:
|
||||
continue
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if round(width, self.decimals) == round(height, self.decimals):
|
||||
if self.ui.square_cb.get_value() is False:
|
||||
continue
|
||||
else:
|
||||
if self.ui.rectangular_cb.get_value() is False:
|
||||
continue
|
||||
else:
|
||||
if self.ui.other_cb.get_value() is False:
|
||||
continue
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
tools[1]["drills"].append(geo_el['follow'])
|
||||
if 'solid_geometry' not in tools[1]:
|
||||
tools[1]['solid_geometry'] = []
|
||||
else:
|
||||
tools[1]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[1] or not tools[1]['solid_geometry']:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
elif mode == 'ring':
|
||||
drills_found = set()
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
dia = None
|
||||
if ap_type == 'C':
|
||||
if self.ui.circular_cb.get_value():
|
||||
dia = float(apid_value['size']) - (2 * circ_r_val)
|
||||
elif ap_type == 'O':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
if self.ui.oblong_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) - (2 * oblong_r_val)
|
||||
else:
|
||||
dia = float(apid_value['width']) - (2 * oblong_r_val)
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||
(10 ** -self.decimals):
|
||||
if self.ui.square_cb.get_value():
|
||||
dia = float(apid_value['height']) - (2 * square_r_val)
|
||||
else:
|
||||
if self.ui.rectangular_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) - (2 * rect_r_val)
|
||||
else:
|
||||
dia = float(apid_value['width']) - (2 * rect_r_val)
|
||||
else:
|
||||
if self.ui.other_cb.get_value():
|
||||
try:
|
||||
dia = float(apid_value['size']) - (2 * other_r_val)
|
||||
except KeyError:
|
||||
if ap_type == 'AM':
|
||||
pol = apid_value['geometry'][0]['solid']
|
||||
x0, y0, x1, y1 = pol.bounds
|
||||
dx = x1 - x0
|
||||
dy = y1 - y0
|
||||
if dx <= dy:
|
||||
dia = dx - (2 * other_r_val)
|
||||
else:
|
||||
dia = dy - (2 * other_r_val)
|
||||
|
||||
# if dia is None then none of the above applied so we skip the following
|
||||
if dia is None:
|
||||
continue
|
||||
|
||||
tool_in_drills = False
|
||||
for tool, tool_val in tools.items():
|
||||
if abs(float('%.*f' % (
|
||||
self.decimals,
|
||||
tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < (10 ** -self.decimals):
|
||||
tool_in_drills = tool
|
||||
|
||||
if tool_in_drills is False:
|
||||
if tools:
|
||||
new_tool = max([int(t) for t in tools]) + 1
|
||||
tool_in_drills = new_tool
|
||||
else:
|
||||
tool_in_drills = 1
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
if tool_in_drills not in tools:
|
||||
tools[tool_in_drills] = {
|
||||
"tooldia": dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
|
||||
tools[tool_in_drills]['drills'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||
tools[tool_in_drills]['solid_geometry'] = []
|
||||
else:
|
||||
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if tool_in_drills in tools:
|
||||
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||
drills_found.add(False)
|
||||
else:
|
||||
drills_found.add(True)
|
||||
|
||||
if True not in drills_found:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
else:
|
||||
drills_found = set()
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
dia = None
|
||||
if ap_type == 'C':
|
||||
if self.ui.circular_cb.get_value():
|
||||
dia = float(apid_value['size']) * prop_factor
|
||||
elif ap_type == 'O':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
if self.ui.oblong_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
dia = float(apid_value['width']) * prop_factor
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||
(10 ** -self.decimals):
|
||||
if self.ui.square_cb.get_value():
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
if self.ui.rectangular_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
dia = float(apid_value['width']) * prop_factor
|
||||
else:
|
||||
if self.ui.other_cb.get_value():
|
||||
try:
|
||||
dia = float(apid_value['size']) * prop_factor
|
||||
except KeyError:
|
||||
if ap_type == 'AM':
|
||||
pol = apid_value['geometry'][0]['solid']
|
||||
x0, y0, x1, y1 = pol.bounds
|
||||
dx = x1 - x0
|
||||
dy = y1 - y0
|
||||
if dx <= dy:
|
||||
dia = dx * prop_factor
|
||||
else:
|
||||
dia = dy * prop_factor
|
||||
|
||||
# if dia is None then none of the above applied so we skip the following
|
||||
if dia is None:
|
||||
continue
|
||||
|
||||
tool_in_drills = False
|
||||
for tool, tool_val in tools.items():
|
||||
if abs(float('%.*f' % (
|
||||
self.decimals,
|
||||
tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < (10 ** -self.decimals):
|
||||
tool_in_drills = tool
|
||||
|
||||
if tool_in_drills is False:
|
||||
if tools:
|
||||
new_tool = max([int(t) for t in tools]) + 1
|
||||
tool_in_drills = new_tool
|
||||
else:
|
||||
tool_in_drills = 1
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
if tool_in_drills not in tools:
|
||||
tools[tool_in_drills] = {
|
||||
"tooldia": dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
|
||||
tools[tool_in_drills]['drills'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||
tools[tool_in_drills]['solid_geometry'] = []
|
||||
else:
|
||||
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if tool_in_drills in tools:
|
||||
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||
drills_found.add(False)
|
||||
else:
|
||||
drills_found.add(True)
|
||||
|
||||
if True not in drills_found:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
|
||||
def obj_init(obj_inst, app_inst):
|
||||
obj_inst.tools = tools
|
||||
obj_inst.drills = drills
|
||||
obj_inst.create_geometry()
|
||||
obj_inst.source_file = self.app.export_excellon(obj_name=outname, local_use=obj_inst, filename=None,
|
||||
use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object("excellon", outname, obj_init)
|
||||
|
||||
def on_hole_size_toggle(self, val):
|
||||
if val == "fixed":
|
||||
self.ui.fixed_label.setDisabled(False)
|
||||
self.ui.dia_entry.setDisabled(False)
|
||||
self.ui.dia_label.setDisabled(False)
|
||||
|
||||
self.ui.ring_frame.setDisabled(True)
|
||||
|
||||
self.ui.prop_label.setDisabled(True)
|
||||
self.ui.factor_label.setDisabled(True)
|
||||
self.ui.factor_entry.setDisabled(True)
|
||||
elif val == "ring":
|
||||
self.ui.fixed_label.setDisabled(True)
|
||||
self.ui.dia_entry.setDisabled(True)
|
||||
self.ui.dia_label.setDisabled(True)
|
||||
|
||||
self.ui.ring_frame.setDisabled(False)
|
||||
|
||||
self.ui.prop_label.setDisabled(True)
|
||||
self.ui.factor_label.setDisabled(True)
|
||||
self.ui.factor_entry.setDisabled(True)
|
||||
elif val == "prop":
|
||||
self.ui.fixed_label.setDisabled(True)
|
||||
self.ui.dia_entry.setDisabled(True)
|
||||
self.ui.dia_label.setDisabled(True)
|
||||
|
||||
self.ui.ring_frame.setDisabled(True)
|
||||
|
||||
self.ui.prop_label.setDisabled(False)
|
||||
self.ui.factor_label.setDisabled(False)
|
||||
self.ui.factor_entry.setDisabled(False)
|
||||
|
||||
def reset_fields(self):
|
||||
self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.ui.gerber_object_combo.setCurrentIndex(0)
|
||||
|
||||
|
||||
class ExtractDrillsUI:
|
||||
|
||||
toolName = _("Extract Drills")
|
||||
|
||||
def __init__(self, layout, app):
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
self.layout = layout
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
|
@ -331,380 +723,22 @@ class ToolExtractDrills(AppTool):
|
|||
self.factor_entry.setDisabled(True)
|
||||
|
||||
self.ring_frame.setDisabled(True)
|
||||
# #################################### FINSIHED GUI ###########################
|
||||
# #############################################################################
|
||||
|
||||
# ## Signals
|
||||
self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
|
||||
self.e_drills_button.clicked.connect(self.on_extract_drills_click)
|
||||
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||
|
||||
self.circular_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.oblong_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.square_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.rectangular_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
self.other_cb.stateChanged.connect(
|
||||
lambda state:
|
||||
self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
|
||||
)
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("Extract Drills()")
|
||||
|
||||
if toggle:
|
||||
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Extract Drills Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.reset_fields()
|
||||
|
||||
self.hole_size_radio.set_value(self.app.defaults["tools_edrills_hole_type"])
|
||||
|
||||
self.dia_entry.set_value(float(self.app.defaults["tools_edrills_hole_fixed_dia"]))
|
||||
|
||||
self.circular_ring_entry.set_value(float(self.app.defaults["tools_edrills_circular_ring"]))
|
||||
self.oblong_ring_entry.set_value(float(self.app.defaults["tools_edrills_oblong_ring"]))
|
||||
self.square_ring_entry.set_value(float(self.app.defaults["tools_edrills_square_ring"]))
|
||||
self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_edrills_rectangular_ring"]))
|
||||
self.other_ring_entry.set_value(float(self.app.defaults["tools_edrills_others_ring"]))
|
||||
|
||||
self.circular_cb.set_value(self.app.defaults["tools_edrills_circular"])
|
||||
self.oblong_cb.set_value(self.app.defaults["tools_edrills_oblong"])
|
||||
self.square_cb.set_value(self.app.defaults["tools_edrills_square"])
|
||||
self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
|
||||
self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
|
||||
|
||||
self.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
|
||||
|
||||
def on_extract_drills_click(self):
|
||||
|
||||
drill_dia = self.dia_entry.get_value()
|
||||
circ_r_val = self.circular_ring_entry.get_value()
|
||||
oblong_r_val = self.oblong_ring_entry.get_value()
|
||||
square_r_val = self.square_ring_entry.get_value()
|
||||
rect_r_val = self.rectangular_ring_entry.get_value()
|
||||
other_r_val = self.other_ring_entry.get_value()
|
||||
|
||||
prop_factor = self.factor_entry.get_value() / 100.0
|
||||
|
||||
drills = []
|
||||
tools = {}
|
||||
|
||||
selection_index = self.gerber_object_combo.currentIndex()
|
||||
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
|
||||
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||
return
|
||||
|
||||
outname = fcobj.options['name'].rpartition('.')[0]
|
||||
|
||||
mode = self.hole_size_radio.get_value()
|
||||
|
||||
if mode == 'fixed':
|
||||
tools = {
|
||||
1: {
|
||||
"tooldia": drill_dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
}
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
if ap_type == 'C':
|
||||
if self.circular_cb.get_value() is False:
|
||||
continue
|
||||
elif ap_type == 'O':
|
||||
if self.oblong_cb.get_value() is False:
|
||||
continue
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if round(width, self.decimals) == round(height, self.decimals):
|
||||
if self.square_cb.get_value() is False:
|
||||
continue
|
||||
else:
|
||||
if self.rectangular_cb.get_value() is False:
|
||||
continue
|
||||
else:
|
||||
if self.other_cb.get_value() is False:
|
||||
continue
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
tools[1]["drills"].append(geo_el['follow'])
|
||||
if 'solid_geometry' not in tools[1]:
|
||||
tools[1]['solid_geometry'] = []
|
||||
else:
|
||||
tools[1]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[1] or not tools[1]['solid_geometry']:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
elif mode == 'ring':
|
||||
drills_found = set()
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
dia = None
|
||||
if ap_type == 'C':
|
||||
if self.circular_cb.get_value():
|
||||
dia = float(apid_value['size']) - (2 * circ_r_val)
|
||||
elif ap_type == 'O':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
if self.oblong_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) - (2 * oblong_r_val)
|
||||
else:
|
||||
dia = float(apid_value['width']) - (2 * oblong_r_val)
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||
(10 ** -self.decimals):
|
||||
if self.square_cb.get_value():
|
||||
dia = float(apid_value['height']) - (2 * square_r_val)
|
||||
else:
|
||||
if self.rectangular_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) - (2 * rect_r_val)
|
||||
else:
|
||||
dia = float(apid_value['width']) - (2 * rect_r_val)
|
||||
else:
|
||||
if self.other_cb.get_value():
|
||||
try:
|
||||
dia = float(apid_value['size']) - (2 * other_r_val)
|
||||
except KeyError:
|
||||
if ap_type == 'AM':
|
||||
pol = apid_value['geometry'][0]['solid']
|
||||
x0, y0, x1, y1 = pol.bounds
|
||||
dx = x1 - x0
|
||||
dy = y1 - y0
|
||||
if dx <= dy:
|
||||
dia = dx - (2 * other_r_val)
|
||||
else:
|
||||
dia = dy - (2 * other_r_val)
|
||||
|
||||
# if dia is None then none of the above applied so we skip the following
|
||||
if dia is None:
|
||||
continue
|
||||
|
||||
tool_in_drills = False
|
||||
for tool, tool_val in tools.items():
|
||||
if abs(float('%.*f' % (
|
||||
def confirmation_message(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
|
||||
self.decimals,
|
||||
tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < (10 ** -self.decimals):
|
||||
tool_in_drills = tool
|
||||
|
||||
if tool_in_drills is False:
|
||||
if tools:
|
||||
new_tool = max([int(t) for t in tools]) + 1
|
||||
tool_in_drills = new_tool
|
||||
else:
|
||||
tool_in_drills = 1
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
if tool_in_drills not in tools:
|
||||
tools[tool_in_drills] = {
|
||||
"tooldia": dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
|
||||
tools[tool_in_drills]['drills'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||
tools[tool_in_drills]['solid_geometry'] = []
|
||||
else:
|
||||
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if tool_in_drills in tools:
|
||||
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||
drills_found.add(False)
|
||||
else:
|
||||
drills_found.add(True)
|
||||
|
||||
if True not in drills_found:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
else:
|
||||
drills_found = set()
|
||||
for apid, apid_value in fcobj.apertures.items():
|
||||
ap_type = apid_value['type']
|
||||
|
||||
dia = None
|
||||
if ap_type == 'C':
|
||||
if self.circular_cb.get_value():
|
||||
dia = float(apid_value['size']) * prop_factor
|
||||
elif ap_type == 'O':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
if self.oblong_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
dia = float(apid_value['width']) * prop_factor
|
||||
elif ap_type == 'R':
|
||||
width = float(apid_value['width'])
|
||||
height = float(apid_value['height'])
|
||||
|
||||
# if the height == width (float numbers so the reason for the following)
|
||||
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||
(10 ** -self.decimals):
|
||||
if self.square_cb.get_value():
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
if self.rectangular_cb.get_value():
|
||||
if width > height:
|
||||
dia = float(apid_value['height']) * prop_factor
|
||||
else:
|
||||
dia = float(apid_value['width']) * prop_factor
|
||||
else:
|
||||
if self.other_cb.get_value():
|
||||
try:
|
||||
dia = float(apid_value['size']) * prop_factor
|
||||
except KeyError:
|
||||
if ap_type == 'AM':
|
||||
pol = apid_value['geometry'][0]['solid']
|
||||
x0, y0, x1, y1 = pol.bounds
|
||||
dx = x1 - x0
|
||||
dy = y1 - y0
|
||||
if dx <= dy:
|
||||
dia = dx * prop_factor
|
||||
else:
|
||||
dia = dy * prop_factor
|
||||
|
||||
# if dia is None then none of the above applied so we skip the following
|
||||
if dia is None:
|
||||
continue
|
||||
|
||||
tool_in_drills = False
|
||||
for tool, tool_val in tools.items():
|
||||
if abs(float('%.*f' % (
|
||||
minval,
|
||||
self.decimals,
|
||||
tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < (10 ** -self.decimals):
|
||||
tool_in_drills = tool
|
||||
|
||||
if tool_in_drills is False:
|
||||
if tools:
|
||||
new_tool = max([int(t) for t in tools]) + 1
|
||||
tool_in_drills = new_tool
|
||||
maxval), False)
|
||||
else:
|
||||
tool_in_drills = 1
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
||||
for geo_el in apid_value['geometry']:
|
||||
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||
if tool_in_drills not in tools:
|
||||
tools[tool_in_drills] = {
|
||||
"tooldia": dia,
|
||||
"drills": [],
|
||||
"slots": []
|
||||
}
|
||||
|
||||
tools[tool_in_drills]['drills'].append(geo_el['follow'])
|
||||
|
||||
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||
tools[tool_in_drills]['solid_geometry'] = []
|
||||
def confirmation_message_int(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
|
||||
(_("Edited value is out of range"), minval, maxval), False)
|
||||
else:
|
||||
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||
|
||||
if tool_in_drills in tools:
|
||||
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||
drills_found.add(False)
|
||||
else:
|
||||
drills_found.add(True)
|
||||
|
||||
if True not in drills_found:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||
return
|
||||
|
||||
def obj_init(obj_inst, app_inst):
|
||||
obj_inst.tools = tools
|
||||
obj_inst.drills = drills
|
||||
obj_inst.create_geometry()
|
||||
obj_inst.source_file = self.app.export_excellon(obj_name=outname, local_use=obj_inst, filename=None,
|
||||
use_thread=False)
|
||||
|
||||
self.app.app_obj.new_object("excellon", outname, obj_init)
|
||||
|
||||
def on_hole_size_toggle(self, val):
|
||||
if val == "fixed":
|
||||
self.fixed_label.setDisabled(False)
|
||||
self.dia_entry.setDisabled(False)
|
||||
self.dia_label.setDisabled(False)
|
||||
|
||||
self.ring_frame.setDisabled(True)
|
||||
|
||||
self.prop_label.setDisabled(True)
|
||||
self.factor_label.setDisabled(True)
|
||||
self.factor_entry.setDisabled(True)
|
||||
elif val == "ring":
|
||||
self.fixed_label.setDisabled(True)
|
||||
self.dia_entry.setDisabled(True)
|
||||
self.dia_label.setDisabled(True)
|
||||
|
||||
self.ring_frame.setDisabled(False)
|
||||
|
||||
self.prop_label.setDisabled(True)
|
||||
self.factor_label.setDisabled(True)
|
||||
self.factor_entry.setDisabled(True)
|
||||
elif val == "prop":
|
||||
self.fixed_label.setDisabled(True)
|
||||
self.dia_entry.setDisabled(True)
|
||||
self.dia_label.setDisabled(True)
|
||||
|
||||
self.ring_frame.setDisabled(True)
|
||||
|
||||
self.prop_label.setDisabled(False)
|
||||
self.factor_label.setDisabled(False)
|
||||
self.factor_entry.setDisabled(False)
|
||||
|
||||
def reset_fields(self):
|
||||
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.gerber_object_combo.setCurrentIndex(0)
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
|
|
@ -35,266 +35,22 @@ class Panelize(AppTool):
|
|||
toolName = _("Panelize PCB")
|
||||
|
||||
def __init__(self, app):
|
||||
self.decimals = app.decimals
|
||||
|
||||
AppTool.__init__(self, app)
|
||||
self.decimals = app.decimals
|
||||
self.app = app
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
|
||||
self.object_label.setToolTip(
|
||||
_("Specify the type of object to be panelized\n"
|
||||
"It can be of type: Gerber, Excellon or Geometry.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Object combobox.")
|
||||
)
|
||||
|
||||
self.layout.addWidget(self.object_label)
|
||||
|
||||
# Form Layout
|
||||
form_layout_0 = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout_0)
|
||||
|
||||
# Type of object to be panelized
|
||||
self.type_obj_combo = FCComboBox()
|
||||
self.type_obj_combo.addItem("Gerber")
|
||||
self.type_obj_combo.addItem("Excellon")
|
||||
self.type_obj_combo.addItem("Geometry")
|
||||
|
||||
self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||
self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
|
||||
self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
|
||||
|
||||
self.type_object_label = QtWidgets.QLabel('%s:' % _("Object Type"))
|
||||
|
||||
form_layout_0.addRow(self.type_object_label, self.type_obj_combo)
|
||||
|
||||
# Object to be panelized
|
||||
self.object_combo = FCComboBox()
|
||||
self.object_combo.setModel(self.app.collection)
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.object_combo.is_last = True
|
||||
|
||||
self.object_combo.setToolTip(
|
||||
_("Object to be panelized. This means that it will\n"
|
||||
"be duplicated in an array of rows and columns.")
|
||||
)
|
||||
form_layout_0.addRow(self.object_combo)
|
||||
|
||||
# Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
|
||||
# Type of box Panel object
|
||||
self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
|
||||
{'label': _('Bounding Box'), 'value': 'bbox'}])
|
||||
self.box_label = QtWidgets.QLabel("<b>%s:</b>" % _("Penelization Reference"))
|
||||
self.box_label.setToolTip(
|
||||
_("Choose the reference for panelization:\n"
|
||||
"- Object = the bounding box of a different object\n"
|
||||
"- Bounding Box = the bounding box of the object to be panelized\n"
|
||||
"\n"
|
||||
"The reference is useful when doing panelization for more than one\n"
|
||||
"object. The spacings (really offsets) will be applied in reference\n"
|
||||
"to this reference object therefore maintaining the panelized\n"
|
||||
"objects in sync.")
|
||||
)
|
||||
form_layout.addRow(self.box_label)
|
||||
form_layout.addRow(self.reference_radio)
|
||||
|
||||
# Type of Box Object to be used as an envelope for panelization
|
||||
self.type_box_combo = FCComboBox()
|
||||
self.type_box_combo.addItems([_("Gerber"), _("Geometry")])
|
||||
|
||||
# we get rid of item1 ("Excellon") as it is not suitable for use as a "box" for panelizing
|
||||
# self.type_box_combo.view().setRowHidden(1, True)
|
||||
self.type_box_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||
self.type_box_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
|
||||
|
||||
self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
|
||||
self.type_box_combo_label.setToolTip(
|
||||
_("Specify the type of object to be used as an container for\n"
|
||||
"panelization. It can be: Gerber or Geometry type.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Box Object combobox.")
|
||||
)
|
||||
form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
|
||||
|
||||
# Box
|
||||
self.box_combo = FCComboBox()
|
||||
self.box_combo.setModel(self.app.collection)
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.is_last = True
|
||||
|
||||
self.box_combo.setToolTip(
|
||||
_("The actual object that is used as container for the\n "
|
||||
"selected object that is to be panelized.")
|
||||
)
|
||||
form_layout.addRow(self.box_combo)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
|
||||
panel_data_label.setToolTip(
|
||||
_("This informations will shape the resulting panel.\n"
|
||||
"The number of rows and columns will set how many\n"
|
||||
"duplicates of the original geometry will be generated.\n"
|
||||
"\n"
|
||||
"The spacings will set the distance between any two\n"
|
||||
"elements of the panel array.")
|
||||
)
|
||||
form_layout.addRow(panel_data_label)
|
||||
|
||||
# Spacing Columns
|
||||
self.spacing_columns = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.spacing_columns.set_range(0, 9999)
|
||||
self.spacing_columns.set_precision(4)
|
||||
|
||||
self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
|
||||
self.spacing_columns_label.setToolTip(
|
||||
_("Spacing between columns of the desired panel.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
||||
|
||||
# Spacing Rows
|
||||
self.spacing_rows = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.spacing_rows.set_range(0, 9999)
|
||||
self.spacing_rows.set_precision(4)
|
||||
|
||||
self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
|
||||
self.spacing_rows_label.setToolTip(
|
||||
_("Spacing between rows of the desired panel.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
||||
|
||||
# Columns
|
||||
self.columns = FCSpinner(callback=self.confirmation_message_int)
|
||||
self.columns.set_range(0, 9999)
|
||||
|
||||
self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
|
||||
self.columns_label.setToolTip(
|
||||
_("Number of columns of the desired panel")
|
||||
)
|
||||
form_layout.addRow(self.columns_label, self.columns)
|
||||
|
||||
# Rows
|
||||
self.rows = FCSpinner(callback=self.confirmation_message_int)
|
||||
self.rows.set_range(0, 9999)
|
||||
|
||||
self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
|
||||
self.rows_label.setToolTip(
|
||||
_("Number of rows of the desired panel")
|
||||
)
|
||||
form_layout.addRow(self.rows_label, self.rows)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
# Type of resulting Panel object
|
||||
self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
|
||||
{'label': _('Geo'), 'value': 'geometry'}])
|
||||
self.panel_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Type"))
|
||||
self.panel_type_label.setToolTip(
|
||||
_("Choose the type of object for the panel object:\n"
|
||||
"- Geometry\n"
|
||||
"- Gerber")
|
||||
)
|
||||
form_layout.addRow(self.panel_type_label)
|
||||
form_layout.addRow(self.panel_type_radio)
|
||||
|
||||
# Constrains
|
||||
self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
|
||||
self.constrain_cb.setToolTip(
|
||||
_("Area define by DX and DY within to constrain the panel.\n"
|
||||
"DX and DY values are in current units.\n"
|
||||
"Regardless of how many columns and rows are desired,\n"
|
||||
"the final panel will have as many columns and rows as\n"
|
||||
"they fit completely within selected area.")
|
||||
)
|
||||
form_layout.addRow(self.constrain_cb)
|
||||
|
||||
self.x_width_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.x_width_entry.set_precision(4)
|
||||
self.x_width_entry.set_range(0, 9999)
|
||||
|
||||
self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
|
||||
self.x_width_lbl.setToolTip(
|
||||
_("The width (DX) within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
||||
|
||||
self.y_height_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.y_height_entry.set_range(0, 9999)
|
||||
self.y_height_entry.set_precision(4)
|
||||
|
||||
self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
|
||||
self.y_height_lbl.setToolTip(
|
||||
_("The height (DY)within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.y_height_lbl, self.y_height_entry)
|
||||
|
||||
self.constrain_sel = OptionalInputSection(
|
||||
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
# Buttons
|
||||
self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
|
||||
self.panelize_object_button.setToolTip(
|
||||
_("Panelize the specified object around the specified box.\n"
|
||||
"In other words it creates multiple copies of the source object,\n"
|
||||
"arranged in a 2D array of rows and columns.")
|
||||
)
|
||||
self.panelize_object_button.setStyleSheet("""
|
||||
QPushButton
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(self.panelize_object_button)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
# ## Reset Tool
|
||||
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
|
||||
self.reset_button.setToolTip(
|
||||
_("Will reset the tool parameters.")
|
||||
)
|
||||
self.reset_button.setStyleSheet("""
|
||||
QPushButton
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(self.reset_button)
|
||||
# #############################################################################
|
||||
# ######################### Tool GUI ##########################################
|
||||
# #############################################################################
|
||||
self.ui = PanelizeUI(layout=self.layout, app=self.app)
|
||||
self.toolName = self.ui.toolName
|
||||
|
||||
# Signals
|
||||
self.reference_radio.activated_custom.connect(self.on_reference_radio_changed)
|
||||
self.panelize_object_button.clicked.connect(self.on_panelize)
|
||||
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
self.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
||||
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||
self.ui.reference_radio.activated_custom.connect(self.on_reference_radio_changed)
|
||||
self.ui.panelize_object_button.clicked.connect(self.on_panelize)
|
||||
self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
self.ui.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
||||
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
||||
|
||||
# list to hold the temporary objects
|
||||
self.objs = []
|
||||
|
@ -342,35 +98,35 @@ class Panelize(AppTool):
|
|||
|
||||
sp_c = self.app.defaults["tools_panelize_spacing_columns"] if \
|
||||
self.app.defaults["tools_panelize_spacing_columns"] else 0.0
|
||||
self.spacing_columns.set_value(float(sp_c))
|
||||
self.ui.spacing_columns.set_value(float(sp_c))
|
||||
|
||||
sp_r = self.app.defaults["tools_panelize_spacing_rows"] if \
|
||||
self.app.defaults["tools_panelize_spacing_rows"] else 0.0
|
||||
self.spacing_rows.set_value(float(sp_r))
|
||||
self.ui.spacing_rows.set_value(float(sp_r))
|
||||
|
||||
rr = self.app.defaults["tools_panelize_rows"] if \
|
||||
self.app.defaults["tools_panelize_rows"] else 0.0
|
||||
self.rows.set_value(int(rr))
|
||||
self.ui.rows.set_value(int(rr))
|
||||
|
||||
cc = self.app.defaults["tools_panelize_columns"] if \
|
||||
self.app.defaults["tools_panelize_columns"] else 0.0
|
||||
self.columns.set_value(int(cc))
|
||||
self.ui.columns.set_value(int(cc))
|
||||
|
||||
c_cb = self.app.defaults["tools_panelize_constrain"] if \
|
||||
self.app.defaults["tools_panelize_constrain"] else False
|
||||
self.constrain_cb.set_value(c_cb)
|
||||
self.ui.constrain_cb.set_value(c_cb)
|
||||
|
||||
x_w = self.app.defaults["tools_panelize_constrainx"] if \
|
||||
self.app.defaults["tools_panelize_constrainx"] else 0.0
|
||||
self.x_width_entry.set_value(float(x_w))
|
||||
self.ui.x_width_entry.set_value(float(x_w))
|
||||
|
||||
y_w = self.app.defaults["tools_panelize_constrainy"] if \
|
||||
self.app.defaults["tools_panelize_constrainy"] else 0.0
|
||||
self.y_height_entry.set_value(float(y_w))
|
||||
self.ui.y_height_entry.set_value(float(y_w))
|
||||
|
||||
panel_type = self.app.defaults["tools_panelize_panel_type"] if \
|
||||
self.app.defaults["tools_panelize_panel_type"] else 'gerber'
|
||||
self.panel_type_radio.set_value(panel_type)
|
||||
self.ui.panel_type_radio.set_value(panel_type)
|
||||
|
||||
# run once the following so the obj_type attribute is updated in the FCComboBoxes
|
||||
# such that the last loaded object is populated in the combo boxes
|
||||
|
@ -378,43 +134,43 @@ class Panelize(AppTool):
|
|||
self.on_type_box_index_changed()
|
||||
|
||||
def on_type_obj_index_changed(self):
|
||||
obj_type = self.type_obj_combo.currentIndex()
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.object_combo.setCurrentIndex(0)
|
||||
self.object_combo.obj_type = {
|
||||
obj_type = self.ui.type_obj_combo.currentIndex()
|
||||
self.ui.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.ui.object_combo.setCurrentIndex(0)
|
||||
self.ui.object_combo.obj_type = {
|
||||
_("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
|
||||
}[self.type_obj_combo.get_value()]
|
||||
}[self.ui.type_obj_combo.get_value()]
|
||||
|
||||
# hide the panel type for Excellons, the panel can be only of type Geometry
|
||||
if self.type_obj_combo.currentText() != 'Excellon':
|
||||
self.panel_type_label.setDisabled(False)
|
||||
self.panel_type_radio.setDisabled(False)
|
||||
if self.ui.type_obj_combo.currentText() != 'Excellon':
|
||||
self.ui.panel_type_label.setDisabled(False)
|
||||
self.ui.panel_type_radio.setDisabled(False)
|
||||
else:
|
||||
self.panel_type_label.setDisabled(True)
|
||||
self.panel_type_radio.setDisabled(True)
|
||||
self.panel_type_radio.set_value('geometry')
|
||||
self.ui.panel_type_label.setDisabled(True)
|
||||
self.ui.panel_type_radio.setDisabled(True)
|
||||
self.ui.panel_type_radio.set_value('geometry')
|
||||
|
||||
def on_type_box_index_changed(self):
|
||||
obj_type = self.type_box_combo.currentIndex()
|
||||
obj_type = self.ui.type_box_combo.currentIndex()
|
||||
obj_type = 2 if obj_type == 1 else obj_type
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.setCurrentIndex(0)
|
||||
self.box_combo.obj_type = {
|
||||
self.ui.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.ui.box_combo.setCurrentIndex(0)
|
||||
self.ui.box_combo.obj_type = {
|
||||
_("Gerber"): "Gerber", _("Geometry"): "Geometry"
|
||||
}[self.type_box_combo.get_value()]
|
||||
}[self.ui.type_box_combo.get_value()]
|
||||
|
||||
def on_reference_radio_changed(self, current_val):
|
||||
if current_val == 'object':
|
||||
self.type_box_combo.setDisabled(False)
|
||||
self.type_box_combo_label.setDisabled(False)
|
||||
self.box_combo.setDisabled(False)
|
||||
self.ui.type_box_combo.setDisabled(False)
|
||||
self.ui.type_box_combo_label.setDisabled(False)
|
||||
self.ui.box_combo.setDisabled(False)
|
||||
else:
|
||||
self.type_box_combo.setDisabled(True)
|
||||
self.type_box_combo_label.setDisabled(True)
|
||||
self.box_combo.setDisabled(True)
|
||||
self.ui.type_box_combo.setDisabled(True)
|
||||
self.ui.type_box_combo_label.setDisabled(True)
|
||||
self.ui.box_combo.setDisabled(True)
|
||||
|
||||
def on_panelize(self):
|
||||
name = self.object_combo.currentText()
|
||||
name = self.ui.object_combo.currentText()
|
||||
|
||||
# Get source object to be panelized.
|
||||
try:
|
||||
|
@ -429,7 +185,7 @@ class Panelize(AppTool):
|
|||
(_("Object not found"), panel_source_obj))
|
||||
return
|
||||
|
||||
boxname = self.box_combo.currentText()
|
||||
boxname = self.ui.box_combo.currentText()
|
||||
|
||||
try:
|
||||
box = self.app.collection.get_by_name(boxname)
|
||||
|
@ -440,29 +196,29 @@ class Panelize(AppTool):
|
|||
|
||||
if box is None:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), panel_source_obj))
|
||||
self.reference_radio.set_value('bbox')
|
||||
self.ui.reference_radio.set_value('bbox')
|
||||
|
||||
if self.reference_radio.get_value() == 'bbox':
|
||||
if self.ui.reference_radio.get_value() == 'bbox':
|
||||
box = panel_source_obj
|
||||
|
||||
self.outname = name + '_panelized'
|
||||
|
||||
spacing_columns = float(self.spacing_columns.get_value())
|
||||
spacing_columns = float(self.ui.spacing_columns.get_value())
|
||||
spacing_columns = spacing_columns if spacing_columns is not None else 0
|
||||
|
||||
spacing_rows = float(self.spacing_rows.get_value())
|
||||
spacing_rows = float(self.ui.spacing_rows.get_value())
|
||||
spacing_rows = spacing_rows if spacing_rows is not None else 0
|
||||
|
||||
rows = int(self.rows.get_value())
|
||||
rows = int(self.ui.rows.get_value())
|
||||
rows = rows if rows is not None else 1
|
||||
|
||||
columns = int(self.columns.get_value())
|
||||
columns = int(self.ui.columns.get_value())
|
||||
columns = columns if columns is not None else 1
|
||||
|
||||
constrain_dx = float(self.x_width_entry.get_value())
|
||||
constrain_dy = float(self.y_height_entry.get_value())
|
||||
constrain_dx = float(self.ui.x_width_entry.get_value())
|
||||
constrain_dy = float(self.ui.y_height_entry.get_value())
|
||||
|
||||
panel_type = str(self.panel_type_radio.get_value())
|
||||
panel_type = str(self.ui.panel_type_radio.get_value())
|
||||
|
||||
if 0 in {columns, rows}:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
|
@ -474,7 +230,7 @@ class Panelize(AppTool):
|
|||
lenghty = ymax - ymin + spacing_rows
|
||||
|
||||
# check if constrain within an area is desired
|
||||
if self.constrain_cb.isChecked():
|
||||
if self.ui.constrain_cb.isChecked():
|
||||
panel_lengthx = ((xmax - xmin) * columns) + (spacing_columns * (columns - 1))
|
||||
panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
|
||||
|
||||
|
@ -820,3 +576,283 @@ class Panelize(AppTool):
|
|||
def reset_fields(self):
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
||||
|
||||
class PanelizeUI:
|
||||
|
||||
toolName = _("Panelize PCB")
|
||||
|
||||
def __init__(self, layout, app):
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
self.layout = layout
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
|
||||
self.object_label.setToolTip(
|
||||
_("Specify the type of object to be panelized\n"
|
||||
"It can be of type: Gerber, Excellon or Geometry.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Object combobox.")
|
||||
)
|
||||
|
||||
self.layout.addWidget(self.object_label)
|
||||
|
||||
# Form Layout
|
||||
form_layout_0 = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout_0)
|
||||
|
||||
# Type of object to be panelized
|
||||
self.type_obj_combo = FCComboBox()
|
||||
self.type_obj_combo.addItem("Gerber")
|
||||
self.type_obj_combo.addItem("Excellon")
|
||||
self.type_obj_combo.addItem("Geometry")
|
||||
|
||||
self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||
self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
|
||||
self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
|
||||
|
||||
self.type_object_label = QtWidgets.QLabel('%s:' % _("Object Type"))
|
||||
|
||||
form_layout_0.addRow(self.type_object_label, self.type_obj_combo)
|
||||
|
||||
# Object to be panelized
|
||||
self.object_combo = FCComboBox()
|
||||
self.object_combo.setModel(self.app.collection)
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.object_combo.is_last = True
|
||||
|
||||
self.object_combo.setToolTip(
|
||||
_("Object to be panelized. This means that it will\n"
|
||||
"be duplicated in an array of rows and columns.")
|
||||
)
|
||||
form_layout_0.addRow(self.object_combo)
|
||||
|
||||
# Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
|
||||
# Type of box Panel object
|
||||
self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
|
||||
{'label': _('Bounding Box'), 'value': 'bbox'}])
|
||||
self.box_label = QtWidgets.QLabel("<b>%s:</b>" % _("Penelization Reference"))
|
||||
self.box_label.setToolTip(
|
||||
_("Choose the reference for panelization:\n"
|
||||
"- Object = the bounding box of a different object\n"
|
||||
"- Bounding Box = the bounding box of the object to be panelized\n"
|
||||
"\n"
|
||||
"The reference is useful when doing panelization for more than one\n"
|
||||
"object. The spacings (really offsets) will be applied in reference\n"
|
||||
"to this reference object therefore maintaining the panelized\n"
|
||||
"objects in sync.")
|
||||
)
|
||||
form_layout.addRow(self.box_label)
|
||||
form_layout.addRow(self.reference_radio)
|
||||
|
||||
# Type of Box Object to be used as an envelope for panelization
|
||||
self.type_box_combo = FCComboBox()
|
||||
self.type_box_combo.addItems([_("Gerber"), _("Geometry")])
|
||||
|
||||
# we get rid of item1 ("Excellon") as it is not suitable for use as a "box" for panelizing
|
||||
# self.type_box_combo.view().setRowHidden(1, True)
|
||||
self.type_box_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||
self.type_box_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
|
||||
|
||||
self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
|
||||
self.type_box_combo_label.setToolTip(
|
||||
_("Specify the type of object to be used as an container for\n"
|
||||
"panelization. It can be: Gerber or Geometry type.\n"
|
||||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Box Object combobox.")
|
||||
)
|
||||
form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
|
||||
|
||||
# Box
|
||||
self.box_combo = FCComboBox()
|
||||
self.box_combo.setModel(self.app.collection)
|
||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.box_combo.is_last = True
|
||||
|
||||
self.box_combo.setToolTip(
|
||||
_("The actual object that is used as container for the\n "
|
||||
"selected object that is to be panelized.")
|
||||
)
|
||||
form_layout.addRow(self.box_combo)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
|
||||
panel_data_label.setToolTip(
|
||||
_("This informations will shape the resulting panel.\n"
|
||||
"The number of rows and columns will set how many\n"
|
||||
"duplicates of the original geometry will be generated.\n"
|
||||
"\n"
|
||||
"The spacings will set the distance between any two\n"
|
||||
"elements of the panel array.")
|
||||
)
|
||||
form_layout.addRow(panel_data_label)
|
||||
|
||||
# Spacing Columns
|
||||
self.spacing_columns = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.spacing_columns.set_range(0, 9999)
|
||||
self.spacing_columns.set_precision(4)
|
||||
|
||||
self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
|
||||
self.spacing_columns_label.setToolTip(
|
||||
_("Spacing between columns of the desired panel.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
||||
|
||||
# Spacing Rows
|
||||
self.spacing_rows = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.spacing_rows.set_range(0, 9999)
|
||||
self.spacing_rows.set_precision(4)
|
||||
|
||||
self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
|
||||
self.spacing_rows_label.setToolTip(
|
||||
_("Spacing between rows of the desired panel.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
||||
|
||||
# Columns
|
||||
self.columns = FCSpinner(callback=self.confirmation_message_int)
|
||||
self.columns.set_range(0, 9999)
|
||||
|
||||
self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
|
||||
self.columns_label.setToolTip(
|
||||
_("Number of columns of the desired panel")
|
||||
)
|
||||
form_layout.addRow(self.columns_label, self.columns)
|
||||
|
||||
# Rows
|
||||
self.rows = FCSpinner(callback=self.confirmation_message_int)
|
||||
self.rows.set_range(0, 9999)
|
||||
|
||||
self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
|
||||
self.rows_label.setToolTip(
|
||||
_("Number of rows of the desired panel")
|
||||
)
|
||||
form_layout.addRow(self.rows_label, self.rows)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
# Type of resulting Panel object
|
||||
self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
|
||||
{'label': _('Geo'), 'value': 'geometry'}])
|
||||
self.panel_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Type"))
|
||||
self.panel_type_label.setToolTip(
|
||||
_("Choose the type of object for the panel object:\n"
|
||||
"- Geometry\n"
|
||||
"- Gerber")
|
||||
)
|
||||
form_layout.addRow(self.panel_type_label)
|
||||
form_layout.addRow(self.panel_type_radio)
|
||||
|
||||
# Constrains
|
||||
self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
|
||||
self.constrain_cb.setToolTip(
|
||||
_("Area define by DX and DY within to constrain the panel.\n"
|
||||
"DX and DY values are in current units.\n"
|
||||
"Regardless of how many columns and rows are desired,\n"
|
||||
"the final panel will have as many columns and rows as\n"
|
||||
"they fit completely within selected area.")
|
||||
)
|
||||
form_layout.addRow(self.constrain_cb)
|
||||
|
||||
self.x_width_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.x_width_entry.set_precision(4)
|
||||
self.x_width_entry.set_range(0, 9999)
|
||||
|
||||
self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
|
||||
self.x_width_lbl.setToolTip(
|
||||
_("The width (DX) within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
||||
|
||||
self.y_height_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||
self.y_height_entry.set_range(0, 9999)
|
||||
self.y_height_entry.set_precision(4)
|
||||
|
||||
self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
|
||||
self.y_height_lbl.setToolTip(
|
||||
_("The height (DY)within which the panel must fit.\n"
|
||||
"In current units.")
|
||||
)
|
||||
form_layout.addRow(self.y_height_lbl, self.y_height_entry)
|
||||
|
||||
self.constrain_sel = OptionalInputSection(
|
||||
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
form_layout.addRow(separator_line)
|
||||
|
||||
# Buttons
|
||||
self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
|
||||
self.panelize_object_button.setToolTip(
|
||||
_("Panelize the specified object around the specified box.\n"
|
||||
"In other words it creates multiple copies of the source object,\n"
|
||||
"arranged in a 2D array of rows and columns.")
|
||||
)
|
||||
self.panelize_object_button.setStyleSheet("""
|
||||
QPushButton
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(self.panelize_object_button)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
# ## Reset Tool
|
||||
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
|
||||
self.reset_button.setToolTip(
|
||||
_("Will reset the tool parameters.")
|
||||
)
|
||||
self.reset_button.setStyleSheet("""
|
||||
QPushButton
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(self.reset_button)
|
||||
|
||||
# #################################### FINSIHED GUI ###########################
|
||||
# #############################################################################
|
||||
|
||||
def confirmation_message(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
|
||||
self.decimals,
|
||||
minval,
|
||||
self.decimals,
|
||||
maxval), False)
|
||||
else:
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
||||
def confirmation_message_int(self, accepted, minval, maxval):
|
||||
if accepted is False:
|
||||
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
|
||||
(_("Edited value is out of range"), minval, maxval), False)
|
||||
else:
|
||||
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue