- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center

This commit is contained in:
Marius Stanciu 2019-10-04 02:59:11 +03:00 committed by Marius
parent fb0edc6c18
commit 67b0a81f17
2 changed files with 257 additions and 62 deletions

View File

@ -10,6 +10,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
4.10.2019
- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center
3.10.2019
- previously I've added the initial layout for the FlatCAMDocument object

View File

@ -7,10 +7,14 @@
# ########################################################## ##
from FlatCAMTool import FlatCAMTool
from FlatCAMObj import *
from flatcamGUI.GUIElements import RadioSet, FCEntry
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
OptionalHideInputSection, OptionalInputSection
from PyQt5 import QtGui, QtCore, QtWidgets
from copy import deepcopy
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
@ -39,8 +43,11 @@ class Film(FlatCAMTool):
self.layout.addWidget(title_label)
# Form Layout
tf_form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(tf_form_layout)
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
# Type of object for which to create the film
self.tf_type_obj_combo = QtWidgets.QComboBox()
@ -60,7 +67,8 @@ class Film(FlatCAMTool):
"The selection here decide the type of objects that will be\n"
"in the Film Object combobox.")
)
tf_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
grid0.addWidget(self.tf_type_obj_combo_label, 0, 0)
grid0.addWidget(self.tf_type_obj_combo, 0, 1)
# List of objects for which we can create the film
self.tf_object_combo = QtWidgets.QComboBox()
@ -72,7 +80,8 @@ class Film(FlatCAMTool):
self.tf_object_label.setToolTip(
_("Object for which to create the film.")
)
tf_form_layout.addRow(self.tf_object_label, self.tf_object_combo)
grid0.addWidget(self.tf_object_label, 1, 0)
grid0.addWidget(self.tf_object_combo, 1, 1)
# Type of Box Object to be used as an envelope for film creation
# Within this we can create negative
@ -93,7 +102,8 @@ class Film(FlatCAMTool):
"The selection here decide the type of objects that will be\n"
"in the Box Object combobox.")
)
tf_form_layout.addRow(self.tf_type_box_combo_label, self.tf_type_box_combo)
grid0.addWidget(self.tf_type_box_combo_label, 2, 0)
grid0.addWidget(self.tf_type_box_combo, 2, 1)
# Box
self.tf_box_combo = QtWidgets.QComboBox()
@ -108,11 +118,28 @@ class Film(FlatCAMTool):
"Usually it is the PCB outline but it can be also the\n"
"same object for which the film is created.")
)
tf_form_layout.addRow(self.tf_box_combo_label, self.tf_box_combo)
grid0.addWidget(self.tf_box_combo_label, 3, 0)
grid0.addWidget(self.tf_box_combo, 3, 1)
# Scale Stroke size
self.film_scale_entry = FCDoubleSpinner()
self.film_scale_entry.set_range(-999.9999, 999.9999)
self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
self.film_scale_label.setToolTip(
_("Scale the line stroke thickness of each feature in the SVG file.\n"
"It means that the line that envelope each SVG feature will be thicker or thinner,\n"
"therefore the fine features may be more affected by this parameter.")
)
grid0.addWidget(self.film_scale_label, 4, 0)
grid0.addWidget(self.film_scale_entry, 4, 1)
grid0.addWidget(QtWidgets.QLabel(''), 5, 0)
# Film Type
self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
{'label': _('Negative'), 'value': 'neg'}])
{'label': _('Negative'), 'value': 'neg'}],
stretch=False)
self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
self.film_type_label.setToolTip(
_("Generate a Positive black film or a Negative film.\n"
@ -122,11 +149,12 @@ class Film(FlatCAMTool):
"with white on a black canvas.\n"
"The Film format is SVG.")
)
tf_form_layout.addRow(self.film_type_label, self.film_type)
grid0.addWidget(self.film_type_label, 6, 0)
grid0.addWidget(self.film_type, 6, 1)
# Boundary for negative film generation
self.boundary_entry = FCEntry()
self.boundary_entry = FCDoubleSpinner()
self.boundary_entry.set_range(-999.9999, 999.9999)
self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
self.boundary_label.setToolTip(
_("Specify a border around the object.\n"
@ -138,21 +166,72 @@ class Film(FlatCAMTool):
"white color like the rest and which may confound with the\n"
"surroundings if not for this border.")
)
tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
grid0.addWidget(self.boundary_label, 7, 0)
grid0.addWidget(self.boundary_entry, 7, 1)
self.film_scale_entry = FCEntry()
self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
self.film_scale_label.setToolTip(
_("Scale the line stroke thickness of each feature in the SVG file.\n"
"It means that the line that envelope each SVG feature will be thicker or thinner,\n"
"therefore the fine features may be more affected by this parameter.")
self.boundary_label.hide()
self.boundary_entry.hide()
# Punch Drill holes
self.punch_cb = FCCheckBox(_("Punch drill holes"))
self.punch_cb.setToolTip(_("When checked the generated film will have holes in pads when\n"
"the generated film is positive. This is done to help drilling,\n"
"when done manually."))
grid0.addWidget(self.punch_cb, 8, 0, 1, 2)
# this way I can hide/show the frame
self.punch_frame = QtWidgets.QFrame()
self.punch_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.punch_frame)
punch_grid = QtWidgets.QGridLayout()
punch_grid.setContentsMargins(0, 0, 0, 0)
self.punch_frame.setLayout(punch_grid)
punch_grid.setColumnStretch(0, 0)
punch_grid.setColumnStretch(1, 1)
self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
self.source_label = QtWidgets.QLabel('%s:' % _("Source"))
self.source_label.setToolTip(
_("The punch hole source can be:\n"
"- Excellon -> an Excellon holes center will serve as reference.\n"
"- Pad Center -> will try to use the pads center as reference.")
)
tf_form_layout.addRow(self.film_scale_label, self.film_scale_entry)
self.source_punch = RadioSet([{'label': _('Excellon'), 'value': 'exc'},
{'label': _('Pad center'), 'value': 'pad'}],
stretch=False)
punch_grid.addWidget(self.source_label, 0, 0)
punch_grid.addWidget(self.source_punch, 0, 1)
self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
self.exc_label.setToolTip(
_("Remove the geometry of Excellon from the Film to create tge holes in pads.")
)
self.exc_combo = QtWidgets.QComboBox()
self.exc_combo.setModel(self.app.collection)
self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
self.exc_combo.setCurrentIndex(1)
punch_grid.addWidget(self.exc_label, 1, 0)
punch_grid.addWidget(self.exc_combo, 1, 1)
self.exc_label.hide()
self.exc_combo.hide()
self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size"))
self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
self.punch_size_spinner = FCDoubleSpinner()
self.punch_size_spinner.set_range(0, 999.9999)
punch_grid.addWidget(self.punch_size_label, 2, 0)
punch_grid.addWidget(self.punch_size_spinner, 2, 1)
self.punch_size_label.hide()
self.punch_size_spinner.hide()
# Buttons
hlay = QtWidgets.QHBoxLayout()
self.layout.addLayout(hlay)
hlay.addStretch()
self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
self.film_object_button.setToolTip(
@ -170,6 +249,9 @@ class Film(FlatCAMTool):
self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
self.film_type.activated_custom.connect(self.on_film_type)
self.source_punch.activated_custom.connect(self.on_punch_source)
def on_type_obj_index_changed(self, index):
obj_type = self.tf_type_obj_combo.currentIndex()
self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
@ -216,6 +298,7 @@ class Film(FlatCAMTool):
f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
self.film_type.set_value(str(f_type))
self.on_film_type(val=f_type)
b_entry = self.app.defaults["tools_film_boundary"] if self.app.defaults["tools_film_boundary"] else 0.0
self.boundary_entry.set_value(float(b_entry))
@ -223,7 +306,41 @@ class Film(FlatCAMTool):
scale_stroke_width = self.app.defaults["tools_film_scale"] if self.app.defaults["tools_film_scale"] else 0.0
self.film_scale_entry.set_value(int(scale_stroke_width))
self.punch_cb.set_value(False)
self.source_punch.set_value('exc')
def on_film_type(self, val):
type_of_film = val
if type_of_film == 'neg':
self.boundary_label.show()
self.boundary_entry.show()
self.punch_cb.hide()
else:
self.boundary_label.hide()
self.boundary_entry.hide()
self.punch_cb.show()
def on_punch_source(self, val):
if val == 'pad' and self.punch_cb.get_value():
self.punch_size_label.show()
self.punch_size_spinner.show()
self.exc_label.hide()
self.exc_combo.hide()
else:
self.punch_size_label.hide()
self.punch_size_spinner.hide()
self.exc_label.show()
self.exc_combo.show()
if val == 'pad' and self.tf_type_obj_combo.currentText() == 'Geometry':
self.source_punch.set_value('exc')
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Using the Pad center does not work on Geometry objects. "
"Only a Gerber object has pads."))
def on_film_creation(self):
log.debug("ToolFilm.Film.on_film_creation() started ...")
try:
name = self.tf_object_combo.currentText()
except Exception as e:
@ -238,59 +355,133 @@ class Film(FlatCAMTool):
_("No FlatCAM object selected. Load an object for Box and retry."))
return
try:
border = float(self.boundary_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
border = float(self.boundary_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
return
scale_stroke_width = float(self.film_scale_entry.get_value())
try:
scale_stroke_width = int(self.film_scale_entry.get_value())
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
return
source = self.source_punch.get_value()
if border is None:
border = 0
# #################################################################
# ################ STARTING THE JOB ###############################
# #################################################################
self.app.inform.emit(_("Generating Film ..."))
if self.film_type.get_value() == "pos":
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG positive"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
return
if self.punch_cb.get_value() is False:
self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width)
else:
self.app.export_svg_positive(name, boxname, filename, scale_factor=scale_stroke_width)
self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width)
else:
self.generate_negative_film(name, boxname, factor=scale_stroke_width)
def generate_positive_normal_film(self, name, boxname, factor):
log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG positive"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
return
else:
self.app.export_svg_positive(name, boxname, filename, scale_factor=factor)
def generate_positive_punched_film(self, name, boxname, source, factor):
film_obj = self.app.collection.get_by_name(name)
if source == 'exc':
log.debug("ToolFilm.Film.generate_positive_punched_film() with Excellon source started ...")
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG negative"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
exc_name = self.exc_combo.currentText()
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("No Excellon object selected. Load an object for punching reference and retry."))
return
else:
self.app.export_svg_negative(name, boxname, filename, border, scale_factor=scale_stroke_width)
exc_obj = self.app.collection.get_by_name(exc_name)
exc_solid_geometry = MultiPolygon(exc_obj.solid_geometry)
punched_solid_geometry = MultiPolygon(film_obj.solid_geometry).difference(exc_solid_geometry)
def init_func(new_obj, app_obj):
new_obj.solid_geometry = deepcopy(punched_solid_geometry)
outname = name + "_punched"
self.app.new_object('gerber', outname, init_func)
self.generate_positive_normal_film(outname, boxname, factor=factor)
else:
log.debug("ToolFilm.Film.generate_positive_punched_film() with Pad center source started ...")
punch_size = float(self.punch_size_spinner.get_value())
punching_geo = list()
for apid in film_obj.apertures:
if film_obj.apertures[apid]['type'] == 'C':
if punch_size >= float(film_obj.apertures[apid]['size']):
self.app.inform.emit('[ERROR_NOTCL] %s' %
_(" Could not generate punched hole film because the punch hole size"
"is bigger than some of the apertures in the Gerber object."))
return 'fail'
else:
for elem in film_obj.apertures[apid]['geometry']:
if 'follow' in elem:
if isinstance(elem['follow'], Point):
punching_geo.append(elem['follow'].buffer(punch_size / 2))
else:
if punch_size >= float(film_obj.apertures[apid]['width']) or \
punch_size >= float(film_obj.apertures[apid]['height']):
self.app.inform.emit('[ERROR_NOTCL] %s' %
_(" Could not generate punched hole film because the punch hole size"
"is bigger than some of the apertures in the Gerber object."))
return 'fail'
else:
for elem in film_obj.apertures[apid]['geometry']:
if 'follow' in elem:
if isinstance(elem['follow'], Point):
punching_geo.append(elem['follow'].buffer(punch_size / 2))
punching_geo = MultiPolygon(punching_geo)
punched_solid_geometry = MultiPolygon(film_obj.solid_geometry).difference(punching_geo)
def init_func(new_obj, app_obj):
new_obj.solid_geometry = deepcopy(punched_solid_geometry)
outname = name + "_punched"
self.app.new_object('gerber', outname, init_func)
self.generate_positive_normal_film(outname, boxname, factor=factor)
def generate_negative_film(self, name, boxname, factor):
log.debug("ToolFilm.Film.generate_negative_film() started ...")
border = float(self.boundary_entry.get_value())
if border is None:
border = 0
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG negative"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
return
else:
self.app.export_svg_negative(name, boxname, filename, border, scale_factor=factor)
def reset_fields(self):
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))