- 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:
Marius Stanciu 2020-06-16 23:25:02 +03:00 committed by Marius
parent 651b3137e9
commit 0a64b02397
8 changed files with 3412 additions and 3181 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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)
@ -47,12 +265,12 @@ class ToolEtchCompensation(AppTool):
# Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.tools_box.addWidget(title_label)
# Grid Layout
@ -227,11 +445,11 @@ class ToolEtchCompensation(AppTool):
_("Will increase the copper features thickness to compensate the lateral etch.")
)
self.compensate_btn.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.compensate_btn, 24, 0, 1, 2)
self.tools_box.addStretch()
@ -242,214 +460,31 @@ class ToolEtchCompensation(AppTool):
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
QPushButton
{
font-weight: bold;
}
""")
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])
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
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:
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()
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.etchants_label.hide()
self.etchants_combo.hide()
self.factor_label.hide()
self.factor_entry.hide()
self.offset_label.show()
self.offset_entry.show()
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
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

View File

@ -26,21 +26,413 @@ 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("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel(""))
@ -297,11 +689,11 @@ class ToolExtractDrills(AppTool):
_("Extract drills from a given Gerber file.")
)
self.e_drills_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
QPushButton
{
font-weight: bold;
}
""")
self.layout.addWidget(self.e_drills_button)
self.layout.addStretch()
@ -312,11 +704,11 @@ class ToolExtractDrills(AppTool):
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
QPushButton
{
font-weight: bold;
}
""")
self.layout.addWidget(self.reset_button)
self.circular_ring_entry.setEnabled(False)
@ -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
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:
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, _("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' % (
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 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:
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' % (
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.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)

View File

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