- updated the Film Tool to allow exporting PDF and PNG file (besides the SVG file)

This commit is contained in:
Marius Stanciu 2019-11-26 16:37:21 +02:00
parent c025d6ad79
commit f1af9d7999
6 changed files with 490 additions and 343 deletions

View File

@ -557,7 +557,7 @@ class App(QtCore.QObject):
"gerber_editor_newdim": "0.5, 0.5",
"gerber_editor_array_size": 5,
"gerber_editor_lin_axis": 'X',
"gerber_editor_lin_pitch": 1,
"gerber_editor_lin_pitch": 0.1,
"gerber_editor_lin_angle": 0.0,
"gerber_editor_circ_dir": 'CW',
"gerber_editor_circ_angle": 0.0,
@ -765,6 +765,7 @@ class App(QtCore.QObject):
"tools_film_skew_ref_radio": 'bottomleft',
"tools_film_mirror_cb": False,
"tools_film_mirror_axis_radio": 'none',
"tools_film_file_type_radio": 'svg',
# Panel Tool
"tools_panelize_spacing_columns": 0,
@ -1322,6 +1323,7 @@ class App(QtCore.QObject):
"tools_film_skew_ref_radio": self.ui.tools_defaults_form.tools_film_group.film_skew_reference,
"tools_film_mirror_cb": self.ui.tools_defaults_form.tools_film_group.film_mirror_cb,
"tools_film_mirror_axis_radio": self.ui.tools_defaults_form.tools_film_group.film_mirror_axis,
"tools_film_file_type_radio": self.ui.tools_defaults_form.tools_film_group.file_type_radio,
# Panelize Tool
"tools_panelize_spacing_columns": self.ui.tools_defaults_form.tools_panelize_group.pspacing_columns,
@ -5560,7 +5562,7 @@ class App(QtCore.QObject):
if self.toggle_units_ignore:
return
new_units = self.defaults['units'].upper()
new_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
# If option is the same, then ignore
if new_units == self.defaults["units"].upper():
@ -10059,291 +10061,6 @@ class App(QtCore.QObject):
self.inform.emit('[success] %s: %s' %
(_("SVG file exported to"), filename))
def export_svg_negative(self, obj_name, box_name, filename, boundary,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None,
use_thread=True):
"""
Exports a Geometry Object to an SVG file in negative.
:param obj_name: the name of the FlatCAM object to be saved as SVG
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
:param filename: Path to the SVG file to save to.
:param boundary: thickness of a black border to surround all the features
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
:param scale_factor_x: factor to scale the svg geometry on the X axis
:param scale_factor_y: factor to scale the svg geometry on the Y axis
:param skew_factor_x: factor to skew the svg geometry on the X axis
:param skew_factor_y: factor to skew the svg geometry on the Y axis
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
those are the 4 points of the bounding box of the geometry to be skewed.
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
:param use_thread: if to be run in a separate thread; boolean
:return:
"""
self.report_usage("export_negative()")
if filename is None:
filename = self.defaults["global_last_save_folder"]
self.log.debug("export_svg() negative")
try:
obj = self.collection.get_by_name(str(obj_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % obj_name
try:
box = self.collection.get_by_name(str(box_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % box_name
if box is None:
self.inform.emit('[WARNING_NOTCL] %s: %s' %
(_("No object Box. Using instead"), obj))
box = obj
def make_negative_film():
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
mirror=mirror
)
# Determine bounding area for svg export
bounds = box.bounds()
size = box.size()
uom = obj.units.lower()
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0] + (2 * boundary))
svgheight = str(size[1] + (2 * boundary))
minx = str(bounds[0] - boundary)
miny = str(bounds[1] + boundary + size[1])
miny_rect = str(bounds[1] - boundary)
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders
# properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
svg_header += '>'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
# Change the attributes of the exported SVG
# We don't need stroke-width - wrong, we do when we have lines with certain width
# We set opacity to maximum
# We set the color to WHITE
root = ET.fromstring(exported_svg)
for child in root:
child.set('fill', '#FFFFFF')
child.set('opacity', '1.0')
child.set('stroke', '#FFFFFF')
# first_svg_elem = 'rect x="' + minx + '" ' + 'y="' + miny_rect + '" '
# first_svg_elem += 'width="' + svgwidth + '" ' + 'height="' + svgheight + '" '
# first_svg_elem += 'fill="#000000" opacity="1.0" stroke-width="0.0"'
first_svg_elem_tag = 'rect'
first_svg_elem_attribs = {
'x': minx,
'y': miny_rect,
'width': svgwidth,
'height': svgheight,
'id': 'neg_rect',
'style': 'fill:#000000;opacity:1.0;stroke-width:0.0'
}
root.insert(0, ET.Element(first_svg_elem_tag, first_svg_elem_attribs))
exported_svg = ET.tostring(root)
svg_elem = svg_header + str(exported_svg) + svg_footer
# Parse the xml through a xml parser just to add line feeds
# and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
try:
with open(filename, 'w') as fp:
fp.write(doc.toprettyxml())
except PermissionError:
self.inform.emit('[WARNING] %s' %
_("Permission denied, saving not possible.\n"
"Most likely another app is holding the file open and not accessible."))
return 'fail'
if self.defaults["global_open_style"] is False:
self.file_opened.emit("SVG", filename)
self.file_saved.emit("SVG", filename)
self.inform.emit('[success] %s: %s' %
(_("SVG file exported to"), filename))
if use_thread is True:
proc = self.proc_container.new(_("Generating Film ... Please wait."))
def job_thread_film(app_obj):
try:
make_negative_film()
except Exception:
proc.done()
return
proc.done()
self.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else:
make_negative_film()
def export_svg_positive(self, obj_name, box_name, filename,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None,
use_thread=True):
"""
Exports a Geometry Object to an SVG file in positive black.
:param obj_name: the name of the FlatCAM object to be saved as SVG
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
:param filename: Path to the SVG file to save to.
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
:param scale_factor_x: factor to scale the svg geometry on the X axis
:param scale_factor_y: factor to scale the svg geometry on the Y axis
:param skew_factor_x: factor to skew the svg geometry on the X axis
:param skew_factor_y: factor to skew the svg geometry on the Y axis
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
those are the 4 points of the bounding box of the geometry to be skewed.
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
:param use_thread: if to be run in a separate thread; boolean
:return:
"""
self.report_usage("export_svg_positive()")
if filename is None:
filename = self.defaults["global_last_save_folder"]
self.log.debug("export_svg() black")
try:
obj = self.collection.get_by_name(str(obj_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % obj_name
try:
box = self.collection.get_by_name(str(box_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % box_name
if box is None:
self.inform.emit('[WARNING_NOTCL] %s: %s' %
(_("No object Box. Using instead"), obj))
box = obj
def make_positive_film():
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
mirror=mirror
)
self.progress.emit(40)
# Change the attributes of the exported SVG
# We don't need stroke-width
# We set opacity to maximum
# We set the colour to WHITE
root = ET.fromstring(exported_svg)
for child in root:
child.set('fill', str(self.defaults['tools_film_color']))
child.set('opacity', '1.0')
child.set('stroke', str(self.defaults['tools_film_color']))
exported_svg = ET.tostring(root)
# Determine bounding area for svg export
bounds = box.bounds()
size = box.size()
# This contain the measure units
uom = obj.units.lower()
# Define a boundary around SVG of about 1.0mm (~39mils)
if uom in "mm":
boundary = 1.0
else:
boundary = 0.0393701
self.progress.emit(80)
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0] + (2 * boundary))
svgheight = str(size[1] + (2 * boundary))
minx = str(bounds[0] - boundary)
miny = str(bounds[1] + boundary + size[1])
self.log.debug(minx)
self.log.debug(miny)
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders
# properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
svg_header += '>'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
svg_elem = str(svg_header) + str(exported_svg) + str(svg_footer)
self.progress.emit(90)
# Parse the xml through a xml parser just to add line feeds
# and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
try:
with open(filename, 'w') as fp:
fp.write(doc.toprettyxml())
except PermissionError:
self.inform.emit('[WARNING] %s' %
_("Permission denied, saving not possible.\n"
"Most likely another app is holding the file open and not accessible."))
return 'fail'
self.progress.emit(100)
if self.defaults["global_open_style"] is False:
self.file_opened.emit("SVG", filename)
self.file_saved.emit("SVG", filename)
self.inform.emit('[success] %s: %s' %
(_("SVG file exported to"), filename))
if use_thread is True:
proc = self.proc_container.new(_("Generating Film ... Please wait."))
def job_thread_film(app_obj):
try:
make_positive_film()
except Exception:
proc.done()
return
proc.done()
self.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else:
make_positive_film()
def save_source_file(self, obj_name, filename, use_thread=True):
"""
Exports a FlatCAM Object to an Gerber/Excellon file.

View File

@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
26.11.2019
- updated the Film Tool to allow exporting PDF and PNG file (besides the SVG file)
25.11.2019
- In Gerber isolation changed the UI

View File

@ -4634,6 +4634,27 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_mirror_axis_label, 13, 0)
grid0.addWidget(self.film_mirror_axis, 13, 1)
separator_line3 = QtWidgets.QFrame()
separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line3, 14, 0, 1, 2)
self.file_type_radio = RadioSet([{'label': _('SVG'), 'value': 'svg'},
{'label': _('PNG'), 'value': 'png'},
{'label': _('PDF'), 'value': 'pdf'}
], stretch=False)
self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
self.file_type_label.setToolTip(
_("The file type of the saved film. Can be:\n"
"- 'SVG' -> open-source vectorial format\n"
"- 'PNG' -> raster image\n"
"- 'PDF' -> portable document format")
)
grid0.addWidget(self.file_type_label, 15, 0)
grid0.addWidget(self.file_type_radio, 15, 1)
self.layout.addStretch()

View File

@ -15,6 +15,15 @@ from copy import deepcopy
import logging
from shapely.geometry import Polygon, MultiPolygon, Point
from reportlab.graphics import renderPDF, renderPM
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A0, A1, A2, A3, A4, A5
from svglib.svglib import svg2rlg
from xml.dom.minidom import parseString as parse_xml_string
from lxml import etree as ET
from io import StringIO
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
@ -168,6 +177,12 @@ class Film(FlatCAMTool):
self.ois_scale = OptionalInputSection(self.film_scale_cb, [self.film_scalex_label, self.film_scalex_entry,
self.film_scaley_label, self.film_scaley_entry])
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 9, 0, 1, 2)
# Skew Geometry
self.film_skew_cb = FCCheckBox('%s' % _("Skew Film geometry"))
self.film_skew_cb.setToolTip(
@ -179,7 +194,7 @@ class Film(FlatCAMTool):
QCheckBox {font-weight: bold; color: black}
"""
)
grid0.addWidget(self.film_skew_cb, 9, 0, 1, 2)
grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
self.film_skewx_entry = FCDoubleSpinner()
@ -187,8 +202,8 @@ class Film(FlatCAMTool):
self.film_skewx_entry.set_precision(self.decimals)
self.film_skewx_entry.setSingleStep(0.01)
grid0.addWidget(self.film_skewx_label, 10, 0)
grid0.addWidget(self.film_skewx_entry, 10, 1)
grid0.addWidget(self.film_skewx_label, 11, 0)
grid0.addWidget(self.film_skewx_entry, 11, 1)
self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
self.film_skewy_entry = FCDoubleSpinner()
@ -196,8 +211,8 @@ class Film(FlatCAMTool):
self.film_skewy_entry.set_precision(self.decimals)
self.film_skewy_entry.setSingleStep(0.01)
grid0.addWidget(self.film_skewy_label, 11, 0)
grid0.addWidget(self.film_skewy_entry, 11, 1)
grid0.addWidget(self.film_skewy_label, 12, 0)
grid0.addWidget(self.film_skewy_entry, 12, 1)
self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
self.film_skew_ref_label.setToolTip(
@ -211,12 +226,18 @@ class Film(FlatCAMTool):
orientation='vertical',
stretch=False)
grid0.addWidget(self.film_skew_ref_label, 12, 0)
grid0.addWidget(self.film_skew_reference, 12, 1)
grid0.addWidget(self.film_skew_ref_label, 13, 0)
grid0.addWidget(self.film_skew_reference, 13, 1)
self.ois_skew = OptionalInputSection(self.film_skew_cb, [self.film_skewx_label, self.film_skewx_entry,
self.film_skewy_label, self.film_skewy_entry,
self.film_skew_reference])
separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line1, 14, 0, 1, 2)
# Mirror Geometry
self.film_mirror_cb = FCCheckBox('%s' % _("Mirror Film geometry"))
self.film_mirror_cb.setToolTip(
@ -227,7 +248,7 @@ class Film(FlatCAMTool):
QCheckBox {font-weight: bold; color: black}
"""
)
grid0.addWidget(self.film_mirror_cb, 13, 0, 1, 2)
grid0.addWidget(self.film_mirror_cb, 15, 0, 1, 2)
self.film_mirror_axis = RadioSet([{'label': _('None'), 'value': 'none'},
{'label': _('X'), 'value': 'x'},
@ -236,13 +257,16 @@ class Film(FlatCAMTool):
stretch=False)
self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
grid0.addWidget(self.film_mirror_axis_label, 14, 0)
grid0.addWidget(self.film_mirror_axis, 14, 1)
grid0.addWidget(self.film_mirror_axis_label, 16, 0)
grid0.addWidget(self.film_mirror_axis, 16, 1)
self.ois_mirror = OptionalInputSection(self.film_mirror_cb,
[self.film_mirror_axis_label, self.film_mirror_axis])
grid0.addWidget(QtWidgets.QLabel(''), 15, 0)
separator_line2 = QtWidgets.QFrame()
separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line2, 17, 0, 1, 2)
# Scale Stroke size
self.film_scale_stroke_entry = FCDoubleSpinner()
@ -256,10 +280,10 @@ class Film(FlatCAMTool):
"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_stroke_label, 16, 0)
grid0.addWidget(self.film_scale_stroke_entry, 16, 1)
grid0.addWidget(self.film_scale_stroke_label, 18, 0)
grid0.addWidget(self.film_scale_stroke_entry, 18, 1)
grid0.addWidget(QtWidgets.QLabel(''), 17, 0)
grid0.addWidget(QtWidgets.QLabel(''), 19, 0)
# Film Type
self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
@ -274,8 +298,8 @@ class Film(FlatCAMTool):
"with white on a black canvas.\n"
"The Film format is SVG.")
)
grid0.addWidget(self.film_type_label, 18, 0)
grid0.addWidget(self.film_type, 18, 1)
grid0.addWidget(self.film_type_label, 20, 0)
grid0.addWidget(self.film_type, 20, 1)
# Boundary for negative film generation
self.boundary_entry = FCDoubleSpinner()
@ -294,8 +318,8 @@ class Film(FlatCAMTool):
"white color like the rest and which may confound with the\n"
"surroundings if not for this border.")
)
grid0.addWidget(self.boundary_label, 19, 0)
grid0.addWidget(self.boundary_entry, 19, 1)
grid0.addWidget(self.boundary_label, 21, 0)
grid0.addWidget(self.boundary_entry, 21, 1)
self.boundary_label.hide()
self.boundary_entry.hide()
@ -305,7 +329,7 @@ class Film(FlatCAMTool):
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, 20, 0, 1, 2)
grid0.addWidget(self.punch_cb, 22, 0, 1, 2)
# this way I can hide/show the frame
self.punch_frame = QtWidgets.QFrame()
@ -359,10 +383,32 @@ class Film(FlatCAMTool):
self.punch_size_label.hide()
self.punch_size_spinner.hide()
# Buttons
hlay = QtWidgets.QHBoxLayout()
self.layout.addLayout(hlay)
grid1 = QtWidgets.QGridLayout()
self.layout.addLayout(grid1)
grid1.setColumnStretch(0, 0)
grid1.setColumnStretch(1, 1)
separator_line3 = QtWidgets.QFrame()
separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line3, 0, 0, 1, 2)
self.file_type_radio = RadioSet([{'label': _('SVG'), 'value': 'svg'},
{'label': _('PNG'), 'value': 'png'},
{'label': _('PDF'), 'value': 'pdf'}
], stretch=False)
self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
self.file_type_label.setToolTip(
_("The file type of the saved film. Can be:\n"
"- 'SVG' -> open-source vectorial format\n"
"- 'PNG' -> raster image\n"
"- 'PDF' -> portable document format")
)
grid1.addWidget(self.file_type_label, 1, 0)
grid1.addWidget(self.file_type_radio, 1, 1)
# Buttons
self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
self.film_object_button.setToolTip(
_("Create a Film for the selected object, within\n"
@ -370,10 +416,12 @@ class Film(FlatCAMTool):
"FlatCAM object, but directly save it in SVG format\n"
"which can be opened with Inkscape.")
)
hlay.addWidget(self.film_object_button)
grid1.addWidget(self.film_object_button, 2, 0, 1, 2)
self.layout.addStretch()
self.units = self.app.defaults['units']
# ## Signals
self.film_object_button.clicked.connect(self.on_film_creation)
self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
@ -449,6 +497,7 @@ class Film(FlatCAMTool):
self.film_skew_reference.set_value(self.app.defaults["tools_film_skew_ref_radio"])
self.film_mirror_cb.set_value(self.app.defaults["tools_film_mirror_cb"])
self.film_mirror_axis.set_value(self.app.defaults["tools_film_mirror_axis_radio"])
self.file_type_radio.set_value(self.app.defaults["tools_film_file_type_radio"])
def on_film_type(self, val):
type_of_film = val
@ -485,21 +534,21 @@ class Film(FlatCAMTool):
try:
name = self.tf_object_combo.currentText()
except Exception as e:
except Exception:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("No FlatCAM object selected. Load an object for Film and retry."))
return
try:
boxname = self.tf_box_combo.currentText()
except Exception as e:
except Exception:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("No FlatCAM object selected. Load an object for Box and retry."))
return
scale_stroke_width = float(self.film_scale_stroke_entry.get_value())
source = self.source_punch.get_value()
file_type = self.file_type_radio.get_value()
# #################################################################
# ################ STARTING THE JOB ###############################
@ -510,13 +559,13 @@ class Film(FlatCAMTool):
if self.film_type.get_value() == "pos":
if self.punch_cb.get_value() is False:
self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width)
self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
else:
self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width)
self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width, ftype=file_type)
else:
self.generate_negative_film(name, boxname, factor=scale_stroke_width)
self.generate_negative_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
def generate_positive_normal_film(self, name, boxname, factor):
def generate_positive_normal_film(self, name, boxname, factor, ftype='svg'):
log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
scale_factor_x = None
@ -541,29 +590,40 @@ class Film(FlatCAMTool):
if self.film_mirror_cb.get_value():
if self.film_mirror_axis.get_value() != 'none':
mirror = self.film_mirror_axis.get_value()
if ftype == 'svg':
filter_ext = "SVG Files (*.SVG);;"\
"All Files (*.*)"
elif ftype == 'png':
filter_ext = "PNG Files (*.PNG);;" \
"All Files (*.*)"
else:
filter_ext = "PDF Files (*.PDF);;" \
"All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG positive"),
caption=_("Export positive film"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
filter=filter_ext)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export positive film"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export positive film cancelled."))
return
else:
self.app.export_svg_positive(name, boxname, filename,
scale_stroke_factor=factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
skew_reference=skew_reference,
mirror=mirror
)
self.export_positive(name, boxname, filename,
scale_stroke_factor=factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
skew_reference=skew_reference,
mirror=mirror, ftype=ftype
)
def generate_positive_punched_film(self, name, boxname, source, factor):
def generate_positive_punched_film(self, name, boxname, source, factor, ftype='svg'):
film_obj = self.app.collection.get_by_name(name)
@ -572,7 +632,7 @@ class Film(FlatCAMTool):
try:
exc_name = self.exc_combo.currentText()
except Exception as e:
except Exception:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("No Excellon object selected. Load an object for punching reference and retry."))
return
@ -640,7 +700,7 @@ class Film(FlatCAMTool):
self.generate_positive_normal_film(outname, boxname, factor=factor)
def generate_negative_film(self, name, boxname, factor):
def generate_negative_film(self, name, boxname, factor, ftype='svg'):
log.debug("ToolFilm.Film.generate_negative_film() started ...")
scale_factor_x = None
@ -671,27 +731,370 @@ class Film(FlatCAMTool):
if border is None:
border = 0
if ftype == 'svg':
filter_ext = "SVG Files (*.SVG);;"\
"All Files (*.*)"
elif ftype == 'png':
filter_ext = "PNG Files (*.PNG);;" \
"All Files (*.*)"
else:
filter_ext = "PDF Files (*.PDF);;" \
"All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
caption=_("Export SVG negative"),
caption=_("Export negative film"),
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
filter=filter_ext)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export negative film"))
filename = str(filename)
if str(filename) == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export negative film cancelled."))
return
else:
self.app.export_svg_negative(name, boxname, filename, border,
scale_stroke_factor=factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
skew_reference=skew_reference,
mirror=mirror
)
self.export_negative(name, boxname, filename, border,
scale_stroke_factor=factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
skew_reference=skew_reference,
mirror=mirror, ftype=ftype
)
def export_negative(self, obj_name, box_name, filename, boundary,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None,
use_thread=True, ftype='svg'):
"""
Exports a Geometry Object to an SVG file in negative.
:param obj_name: the name of the FlatCAM object to be saved as SVG
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
:param filename: Path to the SVG file to save to.
:param boundary: thickness of a black border to surround all the features
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
:param scale_factor_x: factor to scale the svg geometry on the X axis
:param scale_factor_y: factor to scale the svg geometry on the Y axis
:param skew_factor_x: factor to skew the svg geometry on the X axis
:param skew_factor_y: factor to skew the svg geometry on the Y axis
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
those are the 4 points of the bounding box of the geometry to be skewed.
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
:param use_thread: if to be run in a separate thread; boolean
:return:
"""
self.app.report_usage("export_negative()")
if filename is None:
filename = self.app.defaults["global_last_save_folder"]
self.app.log.debug("export_svg() negative")
try:
obj = self.app.collection.get_by_name(str(obj_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % obj_name
try:
box = self.app.collection.get_by_name(str(box_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % box_name
if box is None:
self.app.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
box = obj
def make_negative_film():
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
mirror=mirror
)
# Determine bounding area for svg export
bounds = box.bounds()
size = box.size()
uom = obj.units.lower()
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0] + (2 * boundary))
svgheight = str(size[1] + (2 * boundary))
minx = str(bounds[0] - boundary)
miny = str(bounds[1] + boundary + size[1])
miny_rect = str(bounds[1] - boundary)
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders
# properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
svg_header += '>'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
# Change the attributes of the exported SVG
# We don't need stroke-width - wrong, we do when we have lines with certain width
# We set opacity to maximum
# We set the color to WHITE
root = ET.fromstring(exported_svg)
for child in root:
child.set('fill', '#FFFFFF')
child.set('opacity', '1.0')
child.set('stroke', '#FFFFFF')
# first_svg_elem = 'rect x="' + minx + '" ' + 'y="' + miny_rect + '" '
# first_svg_elem += 'width="' + svgwidth + '" ' + 'height="' + svgheight + '" '
# first_svg_elem += 'fill="#000000" opacity="1.0" stroke-width="0.0"'
first_svg_elem_tag = 'rect'
first_svg_elem_attribs = {
'x': minx,
'y': miny_rect,
'width': svgwidth,
'height': svgheight,
'id': 'neg_rect',
'style': 'fill:#000000;opacity:1.0;stroke-width:0.0'
}
root.insert(0, ET.Element(first_svg_elem_tag, first_svg_elem_attribs))
exported_svg = ET.tostring(root)
svg_elem = svg_header + str(exported_svg) + svg_footer
# Parse the xml through a xml parser just to add line feeds
# and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
doc_final = doc.toprettyxml()
if ftype == 'svg':
try:
with open(filename, 'w') as fp:
fp.write(doc_final)
except PermissionError:
self.app.inform.emit('[WARNING] %s' %
_("Permission denied, saving not possible.\n"
"Most likely another app is holding the file open and not accessible."))
return 'fail'
elif ftype == 'png':
try:
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
renderPM.drawToFile(drawing, filename, 'PNG')
except Exception as e:
log.debug("FilmTool.export_negative() --> PNG output --> %s" % str(e))
return 'fail'
else:
try:
if self.units == 'INCH':
from reportlab.lib.units import inch
unit = inch
else:
from reportlab.lib.units import mm
unit = mm
doc_final = StringIO(doc_final)
my_canvas = canvas.Canvas(filename, pagesize=A4)
drawing = svg2rlg(doc_final)
my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
renderPDF.draw(drawing, my_canvas, 0, 0)
my_canvas.save()
except Exception as e:
log.debug("FilmTool.export_negative() --> PDF output --> %s" % str(e))
return 'fail'
if self.app.defaults["global_open_style"] is False:
self.app.file_opened.emit("SVG", filename)
self.app.file_saved.emit("SVG", filename)
self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
if use_thread is True:
proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
def job_thread_film(app_obj):
try:
make_negative_film()
except Exception:
proc.done()
return
proc.done()
self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else:
make_negative_film()
def export_positive(self, obj_name, box_name, filename,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None,
use_thread=True, ftype='svg'):
"""
Exports a Geometry Object to an SVG file in positive black.
:param obj_name: the name of the FlatCAM object to be saved as SVG
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
:param filename: Path to the SVG file to save to.
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
:param scale_factor_x: factor to scale the svg geometry on the X axis
:param scale_factor_y: factor to scale the svg geometry on the Y axis
:param skew_factor_x: factor to skew the svg geometry on the X axis
:param skew_factor_y: factor to skew the svg geometry on the Y axis
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
those are the 4 points of the bounding box of the geometry to be skewed.
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
:param use_thread: if to be run in a separate thread; boolean
:return:
"""
self.app.report_usage("export_positive()")
if filename is None:
filename = self.app.defaults["global_last_save_folder"]
self.app.log.debug("export_svg() black")
try:
obj = self.app.collection.get_by_name(str(obj_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % obj_name
try:
box = self.app.collection.get_by_name(str(box_name))
except Exception:
# TODO: The return behavior has not been established... should raise exception?
return "Could not retrieve object: %s" % box_name
if box is None:
self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
box = obj
def make_positive_film():
log.debug("FilmTool.export_positive().make_positive_film()")
exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
mirror=mirror
)
# Change the attributes of the exported SVG
# We don't need stroke-width
# We set opacity to maximum
# We set the colour to WHITE
root = ET.fromstring(exported_svg)
for child in root:
child.set('fill', str(self.app.defaults['tools_film_color']))
child.set('opacity', '1.0')
child.set('stroke', str(self.app.defaults['tools_film_color']))
exported_svg = ET.tostring(root)
# Determine bounding area for svg export
bounds = box.bounds()
size = box.size()
# This contain the measure units
uom = obj.units.lower()
# Define a boundary around SVG of about 1.0mm (~39mils)
if uom in "mm":
boundary = 1.0
else:
boundary = 0.0393701
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0] + (2 * boundary))
svgheight = str(size[1] + (2 * boundary))
minx = str(bounds[0] - boundary)
miny = str(bounds[1] + boundary + size[1])
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders
# properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
svg_header += '>'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
svg_elem = str(svg_header) + str(exported_svg) + str(svg_footer)
# Parse the xml through a xml parser just to add line feeds
# and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
doc_final = doc.toprettyxml()
if ftype == 'svg':
try:
with open(filename, 'w') as fp:
fp.write(doc_final)
except PermissionError:
self.app.inform.emit('[WARNING] %s' %
_("Permission denied, saving not possible.\n"
"Most likely another app is holding the file open and not accessible."))
return 'fail'
elif ftype == 'png':
try:
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
renderPM.drawToFile(drawing, filename, 'PNG')
except Exception as e:
log.debug("FilmTool.export_positive() --> PNG output --> %s" % str(e))
return 'fail'
else:
try:
if self.units == 'INCH':
from reportlab.lib.units import inch
unit = inch
else:
from reportlab.lib.units import mm
unit = mm
doc_final = StringIO(doc_final)
my_canvas = canvas.Canvas(filename, pagesize=A4)
drawing = svg2rlg(doc_final)
my_canvas.translate(bounds[0]*unit, bounds[1]*unit)
renderPDF.draw(drawing, my_canvas, 0, 0)
my_canvas.save()
except Exception as e:
log.debug("FilmTool.export_positive() --> PDF output --> %s" % str(e))
return 'fail'
if self.app.defaults["global_open_style"] is False:
self.app.file_opened.emit("SVG", filename)
self.app.file_saved.emit("SVG", filename)
self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
if use_thread is True:
proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
def job_thread_film(app_obj):
try:
make_positive_film()
except Exception:
proc.done()
return
proc.done()
self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
else:
make_positive_film()
def reset_fields(self):
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -22,3 +22,5 @@ rasterio
lxml
ezdxf
qrcode>=6.0
reportlab>=3.0
svglib

View File

@ -5,4 +5,4 @@ sudo apt install --reinstall python3-pip python3-tk python3-imaging
sudo python3 -m pip install --upgrade pip numpy scipy shapely rtree tk lxml cycler python-dateutil kiwisolver dill
sudo python3 -m pip install --upgrade vispy pyopengl setuptools svg.path ortools freetype-py fontTools rasterio ezdxf
sudo python3 -m pip install --upgrade matplotlib qrcode
sudo python3 -m pip install --upgrade matplotlib qrcode reportlab svglib