- updated the Extract Tool - new functionality added: Extract Cutout Gerber from a given Gerber object; added parameters in Preferences
This commit is contained in:
@@ -15,6 +15,7 @@ CHANGELOG for FlatCAM beta
|
|||||||
- in Cutout Tool added the UI for a new feature: Cut by Drilling
|
- in Cutout Tool added the UI for a new feature: Cut by Drilling
|
||||||
- fixed a bug in Extract Tool, when extracting drills some of the drills were lost; added a new UI control to select/deselect all apertures
|
- fixed a bug in Extract Tool, when extracting drills some of the drills were lost; added a new UI control to select/deselect all apertures
|
||||||
- updated the Extract Tool - Extract Soldermask functionality, such that the selection of apertures will control the final SolderMask Gerber content
|
- updated the Extract Tool - Extract Soldermask functionality, such that the selection of apertures will control the final SolderMask Gerber content
|
||||||
|
- updated the Extract Tool - new functionality added: Extract Cutout Gerber from a given Gerber object; added parameters in Preferences
|
||||||
|
|
||||||
9.11.2020
|
9.11.2020
|
||||||
|
|
||||||
|
|||||||
@@ -646,6 +646,8 @@ class PreferencesUIManager:
|
|||||||
"tools_extract_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb,
|
"tools_extract_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb,
|
||||||
"tools_extract_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb,
|
"tools_extract_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb,
|
||||||
"tools_extract_sm_clearance": self.ui.tools2_defaults_form.tools2_edrills_group.clearance_entry,
|
"tools_extract_sm_clearance": self.ui.tools2_defaults_form.tools2_edrills_group.clearance_entry,
|
||||||
|
"tools_extract_cut_margin": self.ui.tools2_defaults_form.tools2_edrills_group.margin_cut_entry,
|
||||||
|
"tools_extract_cut_thickness": self.ui.tools2_defaults_form.tools2_edrills_group.thick_cut_entry,
|
||||||
|
|
||||||
# Punch Gerber Tool
|
# Punch Gerber Tool
|
||||||
"tools_punch_hole_type": self.ui.tools2_defaults_form.tools2_punch_group.hole_size_radio,
|
"tools_punch_hole_type": self.ui.tools2_defaults_form.tools2_punch_group.hole_size_radio,
|
||||||
|
|||||||
@@ -247,5 +247,39 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
|
|||||||
|
|
||||||
grid_lay.addWidget(self.clearance_label, 24, 0)
|
grid_lay.addWidget(self.clearance_label, 24, 0)
|
||||||
grid_lay.addWidget(self.clearance_entry, 24, 1)
|
grid_lay.addWidget(self.clearance_entry, 24, 1)
|
||||||
|
|
||||||
|
# EXTRACT CUTOUT
|
||||||
|
self.extract_cut_label = FCLabel('<b>%s</b>' % _("Extract Cutout"))
|
||||||
|
self.extract_cut_label.setToolTip(
|
||||||
|
_("Extract a cutout from a given Gerber file."))
|
||||||
|
grid_lay.addWidget(self.extract_cut_label, 26, 0, 1, 2)
|
||||||
|
|
||||||
|
# Margin Cutout
|
||||||
|
self.margin_cut_label = FCLabel('%s:' % _("Margin"))
|
||||||
|
self.margin_cut_label.setToolTip(
|
||||||
|
_("Margin over bounds. A positive value here\n"
|
||||||
|
"will make the cutout of the PCB further from\n"
|
||||||
|
"the actual PCB border")
|
||||||
|
)
|
||||||
|
self.margin_cut_entry = FCDoubleSpinner()
|
||||||
|
self.margin_cut_entry.set_range(-10000.0000, 10000.0000)
|
||||||
|
self.margin_cut_entry.set_precision(self.decimals)
|
||||||
|
self.margin_cut_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.margin_cut_label, 28, 0)
|
||||||
|
grid_lay.addWidget(self.margin_cut_entry, 28, 1)
|
||||||
|
|
||||||
|
# Thickness Cutout
|
||||||
|
self.thick_cut_label = FCLabel('%s:' % _("Thickness"))
|
||||||
|
self.thick_cut_label.setToolTip(
|
||||||
|
_("The thickness of the line that makes the cutout geometry.")
|
||||||
|
)
|
||||||
|
self.thick_cut_entry = FCDoubleSpinner()
|
||||||
|
self.thick_cut_entry.set_range(0.0000, 10000.0000)
|
||||||
|
self.thick_cut_entry.set_precision(self.decimals)
|
||||||
|
self.thick_cut_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.thick_cut_label, 30, 0)
|
||||||
|
grid_lay.addWidget(self.thick_cut_entry, 30, 1)
|
||||||
|
|
||||||
self.layout.addStretch()
|
self.layout.addStretch()
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from PyQt5 import QtWidgets, QtCore, QtGui
|
|||||||
from appTool import AppTool
|
from appTool import AppTool
|
||||||
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox, FCLabel
|
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox, FCLabel
|
||||||
|
|
||||||
from shapely.geometry import Point
|
from shapely.geometry import Point, MultiPolygon, Polygon, box
|
||||||
|
from shapely.ops import unary_union
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
@@ -40,9 +41,6 @@ class ToolExtract(AppTool):
|
|||||||
|
|
||||||
# ## Signals
|
# ## Signals
|
||||||
self.ui.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
|
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.e_sm_button.clicked.connect(self.on_extract_soldermask_click)
|
|
||||||
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
|
||||||
|
|
||||||
self.ui.circular_cb.stateChanged.connect(
|
self.ui.circular_cb.stateChanged.connect(
|
||||||
lambda state:
|
lambda state:
|
||||||
@@ -72,6 +70,11 @@ class ToolExtract(AppTool):
|
|||||||
|
|
||||||
self.ui.all_cb.stateChanged.connect(self.on_select_all)
|
self.ui.all_cb.stateChanged.connect(self.on_select_all)
|
||||||
|
|
||||||
|
self.ui.e_drills_button.clicked.connect(self.on_extract_drills_click)
|
||||||
|
self.ui.e_sm_button.clicked.connect(self.on_extract_soldermask_click)
|
||||||
|
self.ui.e_cut_button.clicked.connect(self.on_extract_cutout_click)
|
||||||
|
self.ui.reset_button.clicked.connect(self.set_tool_ui)
|
||||||
|
|
||||||
def install(self, icon=None, separator=None, **kwargs):
|
def install(self, icon=None, separator=None, **kwargs):
|
||||||
AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
|
AppTool.install(self, icon, separator, shortcut='Alt+I', **kwargs)
|
||||||
|
|
||||||
@@ -123,8 +126,13 @@ class ToolExtract(AppTool):
|
|||||||
|
|
||||||
self.ui.factor_entry.set_value(float(self.app.defaults["tools_extract_hole_prop_factor"]))
|
self.ui.factor_entry.set_value(float(self.app.defaults["tools_extract_hole_prop_factor"]))
|
||||||
|
|
||||||
|
# Extract Soldermask
|
||||||
self.ui.clearance_entry.set_value(float(self.app.defaults["tools_extract_sm_clearance"]))
|
self.ui.clearance_entry.set_value(float(self.app.defaults["tools_extract_sm_clearance"]))
|
||||||
|
|
||||||
|
# Extract Cutout
|
||||||
|
self.ui.margin_cut_entry.set_value(float(self.app.defaults["tools_extract_cut_margin"]))
|
||||||
|
self.ui.thick_cut_entry.set_value(float(self.app.defaults["tools_extract_cut_thickness"]))
|
||||||
|
|
||||||
def on_select_all(self, state):
|
def on_select_all(self, state):
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
@@ -435,7 +443,7 @@ class ToolExtract(AppTool):
|
|||||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||||
return
|
return
|
||||||
|
|
||||||
outname = '%s_sm' % obj.options['name'].rpartition('.')[0]
|
outname = '%s_esm' % obj.options['name'].rpartition('.')[0]
|
||||||
|
|
||||||
new_apertures = deepcopy(obj.apertures)
|
new_apertures = deepcopy(obj.apertures)
|
||||||
new_solid_geometry = []
|
new_solid_geometry = []
|
||||||
@@ -511,6 +519,81 @@ class ToolExtract(AppTool):
|
|||||||
log.error("Error on Extracted Soldermask Gerber object creation: %s" % str(e))
|
log.error("Error on Extracted Soldermask Gerber object creation: %s" % str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def on_extract_cutout_click(self):
|
||||||
|
margin = self.ui.margin_cut_entry.get_value()
|
||||||
|
thickness = self.ui.thick_cut_entry.get_value()
|
||||||
|
|
||||||
|
buff_radius = thickness / 2.0
|
||||||
|
|
||||||
|
selection_index = self.ui.gerber_object_combo.currentIndex()
|
||||||
|
model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = model_index.internalPointer().obj
|
||||||
|
except Exception:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||||
|
return
|
||||||
|
|
||||||
|
outname = '%s_ecut' % obj.options['name'].rpartition('.')[0]
|
||||||
|
|
||||||
|
cut_solid_geometry = obj.solid_geometry
|
||||||
|
if isinstance(obj.solid_geometry, list):
|
||||||
|
cut_solid_geometry = MultiPolygon(obj.solid_geometry)
|
||||||
|
|
||||||
|
if isinstance(cut_solid_geometry, (MultiPolygon, Polygon)):
|
||||||
|
x0, y0, x1, y1 = cut_solid_geometry.bounds
|
||||||
|
object_geo = box(x0, y0, x1, y1)
|
||||||
|
else:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Failed."), _("No cutout extracted.")))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
geo_buf = object_geo.buffer(margin)
|
||||||
|
new_geo_follow = geo_buf.exterior
|
||||||
|
new_geo_solid = new_geo_follow.buffer(buff_radius)
|
||||||
|
except Exception as e:
|
||||||
|
log.debug("ToolExtrct.on_extrct_cutout_click() -> %s" % str(e))
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Failed."), _("No cutout extracted.")))
|
||||||
|
return
|
||||||
|
|
||||||
|
if not new_geo_solid.is_valid or new_geo_solid.is_empty:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Failed."), _("No cutout extracted.")))
|
||||||
|
return
|
||||||
|
|
||||||
|
new_apertures = {
|
||||||
|
'10': {
|
||||||
|
'type': 'C',
|
||||||
|
'size': thickness,
|
||||||
|
'geometry': [
|
||||||
|
{
|
||||||
|
'solid': deepcopy(new_geo_solid),
|
||||||
|
'follow': deepcopy(new_geo_follow)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def obj_init(new_obj, app_obj):
|
||||||
|
new_obj.multitool = False
|
||||||
|
new_obj.multigeo = False
|
||||||
|
new_obj.follow = False
|
||||||
|
new_obj.apertures = deepcopy(new_apertures)
|
||||||
|
new_obj.solid_geometry = [deepcopy(new_geo_solid)]
|
||||||
|
new_obj.follow_geometry = [deepcopy(new_geo_follow)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_obj.source_file = app_obj.f_handlers.export_gerber(obj_name=outname, filename=None,
|
||||||
|
local_use=new_obj, use_thread=False)
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.app.proc_container.new(_("Working ...")):
|
||||||
|
try:
|
||||||
|
self.app.app_obj.new_object("gerber", outname, obj_init)
|
||||||
|
except Exception as e:
|
||||||
|
log.error("Error on Extracted Cutout Gerber object creation: %s" % str(e))
|
||||||
|
return
|
||||||
|
|
||||||
def on_hole_size_toggle(self, val):
|
def on_hole_size_toggle(self, val):
|
||||||
if val == "fixed":
|
if val == "fixed":
|
||||||
self.ui.fixed_label.setVisible(True)
|
self.ui.fixed_label.setVisible(True)
|
||||||
@@ -663,14 +746,14 @@ class ExtractUI:
|
|||||||
grid1.setColumnStretch(0, 0)
|
grid1.setColumnStretch(0, 0)
|
||||||
grid1.setColumnStretch(1, 1)
|
grid1.setColumnStretch(1, 1)
|
||||||
|
|
||||||
grid1.addWidget(FCLabel(""), 0, 0, 1, 2)
|
# grid1.addWidget(FCLabel(""), 0, 0, 1, 2)
|
||||||
|
|
||||||
self.extract_drills_label = FCLabel('<b>%s</b>' % _("Extract Drills").upper())
|
self.extract_drills_label = FCLabel('<b>%s</b>' % _("Extract Drills").upper())
|
||||||
self.extract_drills_label.setToolTip(
|
self.extract_drills_label.setToolTip(
|
||||||
_("Extract an Excellon object from the Gerber pads."))
|
_("Extract an Excellon object from the Gerber pads."))
|
||||||
grid1.addWidget(self.extract_drills_label, 1, 0, 1, 2)
|
grid1.addWidget(self.extract_drills_label, 1, 0, 1, 2)
|
||||||
|
|
||||||
self.method_label = FCLabel('<b>%s</b>' % _("Method"))
|
self.method_label = FCLabel('<b>%s:</b>' % _("Method"))
|
||||||
self.method_label.setToolTip(
|
self.method_label.setToolTip(
|
||||||
_("The method for processing pads. Can be:\n"
|
_("The method for processing pads. Can be:\n"
|
||||||
"- Fixed Diameter -> all holes will have a set size\n"
|
"- Fixed Diameter -> all holes will have a set size\n"
|
||||||
@@ -850,7 +933,7 @@ class ExtractUI:
|
|||||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
grid3.addWidget(separator_line, 14, 0, 1, 2)
|
grid3.addWidget(separator_line, 14, 0, 1, 2)
|
||||||
|
|
||||||
grid3.addWidget(FCLabel(""), 16, 0, 1, 2)
|
# grid3.addWidget(FCLabel(""), 16, 0, 1, 2)
|
||||||
|
|
||||||
# EXTRACT SOLDERMASK
|
# EXTRACT SOLDERMASK
|
||||||
self.extract_sm_label = FCLabel('<b>%s</b>' % _("Extract Soldermask").upper())
|
self.extract_sm_label = FCLabel('<b>%s</b>' % _("Extract Soldermask").upper())
|
||||||
@@ -891,6 +974,59 @@ class ExtractUI:
|
|||||||
""")
|
""")
|
||||||
grid3.addWidget(self.e_sm_button, 24, 0, 1, 2)
|
grid3.addWidget(self.e_sm_button, 24, 0, 1, 2)
|
||||||
|
|
||||||
|
# EXTRACT CUTOUT
|
||||||
|
self.extract_sm_label = FCLabel('<b>%s</b>' % _("Extract Cutout").upper())
|
||||||
|
self.extract_sm_label.setToolTip(
|
||||||
|
_("Extract a cutout from a given Gerber file."))
|
||||||
|
grid3.addWidget(self.extract_sm_label, 26, 0, 1, 2)
|
||||||
|
|
||||||
|
# Margin
|
||||||
|
self.margin_cut_label = FCLabel('%s:' % _("Margin"))
|
||||||
|
self.margin_cut_label.setToolTip(
|
||||||
|
_("Margin over bounds. A positive value here\n"
|
||||||
|
"will make the cutout of the PCB further from\n"
|
||||||
|
"the actual PCB border")
|
||||||
|
)
|
||||||
|
self.margin_cut_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||||
|
self.margin_cut_entry.set_range(-10000.0000, 10000.0000)
|
||||||
|
self.margin_cut_entry.set_precision(self.decimals)
|
||||||
|
self.margin_cut_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
grid3.addWidget(self.margin_cut_label, 28, 0)
|
||||||
|
grid3.addWidget(self.margin_cut_entry, 28, 1)
|
||||||
|
|
||||||
|
# Thickness
|
||||||
|
self.thick_cut_label = FCLabel('%s:' % _("Thickness"))
|
||||||
|
self.thick_cut_label.setToolTip(
|
||||||
|
_("The thickness of the line that makes the cutout geometry.")
|
||||||
|
)
|
||||||
|
self.thick_cut_entry = FCDoubleSpinner(callback=self.confirmation_message)
|
||||||
|
self.thick_cut_entry.set_range(0.0000, 10000.0000)
|
||||||
|
self.thick_cut_entry.set_precision(self.decimals)
|
||||||
|
self.thick_cut_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
grid3.addWidget(self.thick_cut_label, 30, 0)
|
||||||
|
grid3.addWidget(self.thick_cut_entry, 30, 1)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid3.addWidget(separator_line, 32, 0, 1, 2)
|
||||||
|
|
||||||
|
# Extract cutout from Gerber apertures flashes (pads)
|
||||||
|
self.e_cut_button = QtWidgets.QPushButton(_("Extract Cutout"))
|
||||||
|
self.e_cut_button.setIcon(QtGui.QIcon(self.app.resource_location + '/extract32.png'))
|
||||||
|
self.e_cut_button.setToolTip(
|
||||||
|
_("Extract soldermask from a given Gerber file.")
|
||||||
|
)
|
||||||
|
self.e_cut_button.setStyleSheet("""
|
||||||
|
QPushButton
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
grid3.addWidget(self.e_cut_button, 34, 0, 1, 2)
|
||||||
|
|
||||||
self.layout.addStretch()
|
self.layout.addStretch()
|
||||||
|
|
||||||
# ## Reset Tool
|
# ## Reset Tool
|
||||||
|
|||||||
@@ -713,6 +713,8 @@ class FlatCAMDefaults:
|
|||||||
"tools_extract_rectangular": False,
|
"tools_extract_rectangular": False,
|
||||||
"tools_extract_others": False,
|
"tools_extract_others": False,
|
||||||
"tools_extract_sm_clearance": 0.1,
|
"tools_extract_sm_clearance": 0.1,
|
||||||
|
"tools_extract_cut_margin": 0.1,
|
||||||
|
"tools_extract_cut_thickness": 0.1,
|
||||||
|
|
||||||
# Punch Gerber Tool
|
# Punch Gerber Tool
|
||||||
"tools_punch_hole_type": 'exc',
|
"tools_punch_hole_type": 'exc',
|
||||||
|
|||||||
Reference in New Issue
Block a user