Merged Beta_8.994 into AutolevellingFeature

This commit is contained in:
Marius Stanciu 2020-10-21 13:58:22 +00:00
commit 2bbab19e0a
64 changed files with 23581 additions and 21398 deletions

View File

@ -7,6 +7,56 @@ CHANGELOG for FlatCAM beta
=================================================
21.10.2020
- in Geometry Object fixed the issue with not using the End X-Y value and also made some other updates here
- in NCC and Paint Tool fixed some issues with missing keys in the tool data dictionary
- In Excellon Object UI fixed the enable/disable for the Milling section according to the Tools Table row that is selected
- In Excellon Object UI fixed the milling geometry generation
- updated the translations strings to the changes in the source code
- some strings changed
- fixed crash on using shortcut for creating a new Document Object
- fixed Cutout Tool to work with the endxy parameter
- added the exclusion parameters for Drilling Tool to the Preferences area
- cascaded_union() method will be deprecated in Shapely 1.8 in favor of unary_union; replaced the usage of cascaded_union with unary_union in all the app
- added some strings to the translatable strings and updated the translation strings
20.10.2020
- finished to add the Properties data to the Object Properties (former Selected Tab)
19.10.2020
- added a check (and added to Preferences too) for the verification of tools validity in the Isolation Tool
- fixed QrCode Tool
- updated the Turkish translation (by Mehmet Kaya)
18.10.2020
- fixed issue with calling the inform signal in the FlatCAMDefaults.load method
- fixed macro parsing in Gerber files generated by KiCAD 4.99 (KiCAD 5.0)
17.10.2020
- updated Turkish translation (by Mehmet Kaya)
8.10.2020
- small change in the NCC Tool UI
- some strings are changed and therefore the translation strings source are updated
- Isolation Tool - added a check for having a complete isolation
7.10.2020
- working on adding DPI setting for PNG export in Film Tool - update
- finished updating DPI setting feature for PNG export in Film Tool
5.10.2020
- working on adding DPI setting for PNG export in the Film Tool
- finished working in adding DPI settings for PNG export in Film Tool although there are some limitations due of Reportlab
- small change in TclCommandExportSVG
26.09.2020
- the Selected Tab is now Properties Tab for FlatCAM objects

View File

@ -16,12 +16,12 @@ from PyQt5.QtCore import Qt, QSettings
from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
from appTool import AppTool
from appGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
from appGUI.GUIElements import OptionalInputSection, FCCheckBox, FCLabel, FCComboBox, FCTextAreaRich, \
FCDoubleSpinner, FCButton, FCInputDialog, FCTree, NumericalEvalTupleEntry
from appParsers.ParseFont import *
from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
from shapely.ops import cascaded_union, unary_union, linemerge
from shapely.ops import unary_union, linemerge
import shapely.affinity as affinity
from shapely.geometry.polygon import orient
@ -46,7 +46,7 @@ class BufferSelectionTool(AppTool):
Simple input for buffer distance.
"""
toolName = "Buffer Selection"
toolName = _("Buffer Selection")
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
@ -55,7 +55,7 @@ class BufferSelectionTool(AppTool):
self.decimals = app.decimals
# Title
title_label = QtWidgets.QLabel("%s" % ('Editor ' + self.toolName))
title_label = FCLabel("%s" % ('Editor ' + self.toolName))
title_label.setStyleSheet("""
QLabel
{
@ -82,7 +82,7 @@ class BufferSelectionTool(AppTool):
self.buffer_distance_entry.set_precision(self.decimals)
self.buffer_distance_entry.set_range(0.0000, 999999.9999)
form_layout.addRow(_("Buffer distance:"), self.buffer_distance_entry)
self.buffer_corner_lbl = QtWidgets.QLabel(_("Buffer corner:"))
self.buffer_corner_lbl = FCLabel(_("Buffer corner:"))
self.buffer_corner_lbl.setToolTip(
_("There are 3 types of corners:\n"
" - 'Round': the corner is rounded for exterior buffer.\n"
@ -99,15 +99,15 @@ class BufferSelectionTool(AppTool):
hlay = QtWidgets.QHBoxLayout()
self.buffer_tools_box.addLayout(hlay)
self.buffer_int_button = QtWidgets.QPushButton(_("Buffer Interior"))
self.buffer_int_button = FCButton(_("Buffer Interior"))
hlay.addWidget(self.buffer_int_button)
self.buffer_ext_button = QtWidgets.QPushButton(_("Buffer Exterior"))
self.buffer_ext_button = FCButton(_("Buffer Exterior"))
hlay.addWidget(self.buffer_ext_button)
hlay1 = QtWidgets.QHBoxLayout()
self.buffer_tools_box.addLayout(hlay1)
self.buffer_button = QtWidgets.QPushButton(_("Full Buffer"))
self.buffer_button = FCButton(_("Full Buffer"))
hlay1.addWidget(self.buffer_button)
self.layout.addStretch()
@ -191,7 +191,7 @@ class TextInputTool(AppTool):
Simple input for buffer distance.
"""
toolName = "Text Input Tool"
toolName = _("Text Input Tool")
def __init__(self, app):
AppTool.__init__(self, app)
@ -212,7 +212,7 @@ class TextInputTool(AppTool):
self.text_tool_frame.setLayout(self.text_tools_box)
# Title
title_label = QtWidgets.QLabel("%s" % ('Editor ' + self.toolName))
title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
@ -238,7 +238,7 @@ class TextInputTool(AppTool):
self.font_type_cb = QtWidgets.QFontComboBox(self)
self.font_type_cb.setCurrentFont(f_current)
self.form_layout.addRow(QtWidgets.QLabel('%s:' % _("Font")), self.font_type_cb)
self.form_layout.addRow(FCLabel('%s:' % _("Font")), self.font_type_cb)
# Flag variables to show if font is bold, italic, both or none (regular)
self.font_bold = False
@ -310,7 +310,7 @@ class TextInputTool(AppTool):
self.font_italic_tb.setIcon(QtGui.QIcon(self.app.resource_location + '/italic32.png'))
hlay.addWidget(self.font_italic_tb)
self.form_layout.addRow(QtWidgets.QLabel('%s:' % "Size"), hlay)
self.form_layout.addRow(FCLabel('%s:' % "Size"), hlay)
# Text input
self.text_input_entry = FCTextAreaRich()
@ -319,13 +319,13 @@ class TextInputTool(AppTool):
# self.text_input_entry.setMaximumHeight(150)
self.text_input_entry.setCurrentFont(f_current)
self.text_input_entry.setFontPointSize(10)
self.form_layout.addRow(QtWidgets.QLabel('%s:' % _("Text")), self.text_input_entry)
self.form_layout.addRow(FCLabel('%s:' % _("Text")), self.text_input_entry)
# Buttons
hlay1 = QtWidgets.QHBoxLayout()
self.form_layout.addRow("", hlay1)
hlay1.addStretch()
self.apply_button = QtWidgets.QPushButton("Apply")
self.apply_button = FCButton(_("Apply"))
hlay1.addWidget(self.apply_button)
# self.layout.addStretch()
@ -409,7 +409,7 @@ class PaintOptionsTool(AppTool):
Inputs to specify how to paint the selected polygons.
"""
toolName = "Paint Tool"
toolName = _("Paint Tool")
def __init__(self, app, fcdraw):
AppTool.__init__(self, app)
@ -419,7 +419,7 @@ class PaintOptionsTool(AppTool):
self.decimals = self.app.decimals
# Title
title_label = QtWidgets.QLabel("%s" % ('Editor ' + self.toolName))
title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
@ -435,7 +435,7 @@ class PaintOptionsTool(AppTool):
grid.setColumnStretch(1, 1)
# Tool dia
ptdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
ptdlabel = FCLabel('%s:' % _('Tool dia'))
ptdlabel.setToolTip(
_("Diameter of the tool to be used in the operation.")
)
@ -447,7 +447,7 @@ class PaintOptionsTool(AppTool):
grid.addWidget(self.painttooldia_entry, 0, 1)
# Overlap
ovlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
ovlabel = FCLabel('%s:' % _('Overlap'))
ovlabel.setToolTip(
_("How much (percentage) of the tool width to overlap each tool pass.\n"
"Adjust the value starting with lower values\n"
@ -467,7 +467,7 @@ class PaintOptionsTool(AppTool):
grid.addWidget(self.paintoverlap_entry, 1, 1)
# Margin
marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
marginlabel = FCLabel('%s:' % _('Margin'))
marginlabel.setToolTip(
_("Distance by which to avoid\n"
"the edges of the polygon to\n"
@ -481,7 +481,7 @@ class PaintOptionsTool(AppTool):
grid.addWidget(self.paintmargin_entry, 2, 1)
# Method
methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
methodlabel = FCLabel('%s:' % _('Method'))
methodlabel.setToolTip(
_("Algorithm to paint the polygons:\n"
"- Standard: Fixed step inwards.\n"
@ -502,7 +502,7 @@ class PaintOptionsTool(AppTool):
grid.addWidget(self.paintmethod_combo, 3, 1)
# Connect lines
pathconnectlabel = QtWidgets.QLabel(_("Connect:"))
pathconnectlabel = FCLabel('%s:' % _("Connect"))
pathconnectlabel.setToolTip(
_("Draw lines between resulting\n"
"segments to minimize tool lifts.")
@ -512,7 +512,7 @@ class PaintOptionsTool(AppTool):
grid.addWidget(pathconnectlabel, 4, 0)
grid.addWidget(self.pathconnect_cb, 4, 1)
contourlabel = QtWidgets.QLabel(_("Contour:"))
contourlabel = FCLabel('%s:' % _("Contour"))
contourlabel.setToolTip(
_("Cut around the perimeter of the polygon\n"
"to trim rough edges.")
@ -525,7 +525,7 @@ class PaintOptionsTool(AppTool):
# Buttons
hlay = QtWidgets.QHBoxLayout()
self.layout.addLayout(hlay)
self.paint_button = QtWidgets.QPushButton(_("Paint"))
self.paint_button = FCButton(_("Paint"))
hlay.addWidget(self.paint_button)
self.layout.addStretch()
@ -619,7 +619,7 @@ class TransformEditorTool(AppTool):
self.decimals = self.app.decimals
# ## Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
@ -628,7 +628,7 @@ class TransformEditorTool(AppTool):
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel(''))
self.layout.addWidget(FCLabel(''))
# ## Layout
grid0 = QtWidgets.QGridLayout()
@ -637,10 +637,10 @@ class TransformEditorTool(AppTool):
grid0.setColumnStretch(1, 1)
grid0.setColumnStretch(2, 0)
grid0.addWidget(QtWidgets.QLabel(''))
grid0.addWidget(FCLabel(''))
# Reference
ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
ref_label = FCLabel('%s:' % _("Reference"))
ref_label.setToolTip(
_("The reference point for Rotate, Skew, Scale, Mirror.\n"
"Can be:\n"
@ -656,7 +656,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(ref_label, 0, 0)
grid0.addWidget(self.ref_combo, 0, 1, 1, 2)
self.point_label = QtWidgets.QLabel('%s:' % _("Value"))
self.point_label = FCLabel('%s:' % _("Value"))
self.point_label.setToolTip(
_("A point of reference in format X,Y.")
)
@ -677,10 +677,10 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 5, 0, 1, 3)
# ## Rotate Title
rotate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
rotate_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.rotateName)
grid0.addWidget(rotate_title_label, 6, 0, 1, 3)
self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle"))
self.rotate_label = FCLabel('%s:' % _("Angle"))
self.rotate_label.setToolTip(
_("Angle for Rotation action, in degrees.\n"
"Float number between -360 and 359.\n"
@ -714,7 +714,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 8, 0, 1, 3)
# ## Skew Title
skew_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
skew_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.skewName)
grid0.addWidget(skew_title_label, 9, 0, 1, 2)
self.skew_link_cb = FCCheckBox()
@ -725,7 +725,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.skew_link_cb, 9, 2)
self.skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
self.skewx_label = FCLabel('%s:' % _("X angle"))
self.skewx_label.setToolTip(
_("Angle for Skew action, in degrees.\n"
"Float number between -360 and 360.")
@ -746,7 +746,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.skewx_entry, 10, 1)
grid0.addWidget(self.skewx_button, 10, 2)
self.skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
self.skewy_label = FCLabel('%s:' % _("Y angle"))
self.skewy_label.setToolTip(
_("Angle for Skew action, in degrees.\n"
"Float number between -360 and 360.")
@ -776,7 +776,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 14, 0, 1, 3)
# ## Scale Title
scale_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.scaleName)
scale_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.scaleName)
grid0.addWidget(scale_title_label, 15, 0, 1, 2)
self.scale_link_cb = FCCheckBox()
@ -787,7 +787,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.scale_link_cb, 15, 2)
self.scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
self.scalex_label = FCLabel('%s:' % _("X factor"))
self.scalex_label.setToolTip(
_("Factor for scaling on X axis.")
)
@ -807,7 +807,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.scalex_entry, 17, 1)
grid0.addWidget(self.scalex_button, 17, 2)
self.scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
self.scaley_label = FCLabel('%s:' % _("Y factor"))
self.scaley_label.setToolTip(
_("Factor for scaling on Y axis.")
)
@ -840,7 +840,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 21, 0, 1, 3)
# ## Flip Title
flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
flip_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.flipName)
grid0.addWidget(flip_title_label, 23, 0, 1, 3)
self.flipx_button = FCButton(_("Flip on X"))
@ -865,10 +865,10 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 27, 0, 1, 3)
# ## Offset Title
offset_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.offsetName)
offset_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.offsetName)
grid0.addWidget(offset_title_label, 29, 0, 1, 3)
self.offx_label = QtWidgets.QLabel('%s:' % _("X val"))
self.offx_label = FCLabel('%s:' % _("X val"))
self.offx_label.setToolTip(
_("Distance to offset on X axis. In current units.")
)
@ -888,7 +888,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.offx_entry, 31, 1)
grid0.addWidget(self.offx_button, 31, 2)
self.offy_label = QtWidgets.QLabel('%s:' % _("Y val"))
self.offy_label = FCLabel('%s:' % _("Y val"))
self.offy_label.setToolTip(
_("Distance to offset on Y axis. In current units.")
)
@ -914,7 +914,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(separator_line, 34, 0, 1, 3)
# ## Buffer Title
buffer_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.bufferName)
buffer_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.bufferName)
grid0.addWidget(buffer_title_label, 35, 0, 1, 2)
self.buffer_rounded_cb = FCCheckBox('%s' % _("Rounded"))
@ -927,7 +927,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.buffer_rounded_cb, 35, 2)
self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
self.buffer_label = FCLabel('%s:' % _("Distance"))
self.buffer_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
@ -952,7 +952,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.buffer_entry, 37, 1)
grid0.addWidget(self.buffer_button, 37, 2)
self.buffer_factor_label = QtWidgets.QLabel('%s:' % _("Value"))
self.buffer_factor_label = FCLabel('%s:' % _("Value"))
self.buffer_factor_label.setToolTip(
_("A positive value will create the effect of dilation,\n"
"while a negative value will create the effect of erosion.\n"
@ -978,7 +978,7 @@ class TransformEditorTool(AppTool):
grid0.addWidget(self.buffer_factor_entry, 38, 1)
grid0.addWidget(self.buffer_factor_button, 38, 2)
grid0.addWidget(QtWidgets.QLabel(''), 42, 0, 1, 3)
grid0.addWidget(FCLabel(''), 42, 0, 1, 3)
self.layout.addStretch()
@ -3147,7 +3147,7 @@ class FCEraser(FCShapeTool):
temp_shape = eraser_shape.buffer(0.0000001)
temp_shape = Polygon(temp_shape.exterior)
eraser_sel_shapes.append(temp_shape)
eraser_sel_shapes = cascaded_union(eraser_sel_shapes)
eraser_sel_shapes = unary_union(eraser_sel_shapes)
for obj_shape in self.storage.get_objects():
try:
@ -3273,15 +3273,15 @@ class AppGeoEditor(QtCore.QObject):
# ## Page Title icon
pixmap = QtGui.QPixmap(self.app.resource_location + '/flatcam_icon32.png')
self.icon = QtWidgets.QLabel()
self.icon = FCLabel()
self.icon.setPixmap(pixmap)
self.title_box.addWidget(self.icon, stretch=0)
# ## Title label
self.title_label = QtWidgets.QLabel("<font size=5><b>%s</b></font>" % _('Geometry Editor'))
self.title_label = FCLabel("<font size=5><b>%s</b></font>" % _('Geometry Editor'))
self.title_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.title_box.addWidget(self.title_label, stretch=1)
self.title_box.addWidget(QtWidgets.QLabel(''))
self.title_box.addWidget(FCLabel(''))
self.tw = FCTree(columns=3, header_hidden=False, protected_column=[0, 1], extended_sel=True)
self.tw.setHeaderLabels(["ID", _("Type"), _("Name")])
@ -3298,7 +3298,7 @@ class AppGeoEditor(QtCore.QObject):
layout.addStretch()
# Editor
self.exit_editor_button = QtWidgets.QPushButton(_('Exit Editor'))
self.exit_editor_button = FCButton(_('Exit Editor'))
self.exit_editor_button.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png'))
self.exit_editor_button.setToolTip(
_("Exit from Editor.")
@ -5134,7 +5134,7 @@ class AppGeoEditor(QtCore.QObject):
return
# add the result to the results list
results.append(cascaded_union(local_results))
results.append(unary_union(local_results))
# This is a dirty patch:
for r in results:

View File

@ -9,7 +9,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QSettings
from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Polygon, MultiPolygon, box
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import shapely.affinity as affinity
from vispy.geometry import Rect
@ -2235,7 +2235,7 @@ class FCEraser(FCShapeTool):
temp_shape = eraser_shape['solid'].buffer(0.0000001)
temp_shape = Polygon(temp_shape.exterior)
eraser_sel_shapes.append(temp_shape)
eraser_sel_shapes = cascaded_union(eraser_sel_shapes)
eraser_sel_shapes = unary_union(eraser_sel_shapes)
for storage in self.draw_app.storage_dict:
try:
@ -4968,7 +4968,7 @@ class AppGerberEditor(QtCore.QObject):
if 'solid' in actual_geo:
edit_geo.append(actual_geo['solid'])
all_geo = cascaded_union(edit_geo)
all_geo = unary_union(edit_geo)
# calculate the bounds values for the edited Gerber object
xmin, ymin, xmax, ymax = all_geo.bounds

View File

@ -2684,9 +2684,9 @@ class MainGUI(QtWidgets.QMainWindow):
if key == QtCore.Qt.Key_B:
self.app.app_obj.new_gerber_object()
# New Geometry
# New Document Object
if key == QtCore.Qt.Key_D:
self.app.new_document_object()
self.app.app_obj.new_document_object()
# Copy Object Name
if key == QtCore.Qt.Key_E:

View File

@ -251,10 +251,31 @@ class GerberObjectUI(ObjectUI):
""")
grid0.addWidget(self.editor_button, 4, 0, 1, 3)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 6, 0, 1, 3)
# PROPERTIES CB
self.properties_button = FCButton('%s' % _("PROPERTIES"), checkable=True)
self.properties_button.setIcon(QtGui.QIcon(self.app.resource_location + '/properties32.png'))
self.properties_button.setToolTip(_("Show the Properties."))
self.properties_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.properties_button, 6, 0, 1, 3)
# PROPERTIES Frame
self.properties_frame = QtWidgets.QFrame()
self.properties_frame.setContentsMargins(0, 0, 0, 0)
grid0.addWidget(self.properties_frame, 7, 0, 1, 3)
self.properties_box = QtWidgets.QVBoxLayout()
self.properties_box.setContentsMargins(0, 0, 0, 0)
self.properties_frame.setLayout(self.properties_box)
self.properties_frame.hide()
self.treeWidget = FCTree(columns=2)
self.properties_box.addWidget(self.treeWidget)
self.properties_box.setStretch(0, 0)
# ### Gerber Apertures ####
self.apertures_table_label = QtWidgets.QLabel('%s:' % _('Apertures'))
@ -324,6 +345,11 @@ class GerberObjectUI(ObjectUI):
)
grid0.addWidget(self.create_buffer_button, 12, 0, 1, 3)
separator_line1 = QtWidgets.QFrame()
separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line1, 13, 0, 1, 3)
self.tool_lbl = QtWidgets.QLabel('<b>%s</b>' % _("TOOLS"))
grid0.addWidget(self.tool_lbl, 14, 0, 1, 3)
@ -538,6 +564,32 @@ class ExcellonObjectUI(ObjectUI):
""")
grid0.addWidget(self.editor_button, 4, 0, 1, 3)
# PROPERTIES CB
self.properties_button = FCButton('%s' % _("PROPERTIES"), checkable=True)
self.properties_button.setIcon(QtGui.QIcon(self.app.resource_location + '/properties32.png'))
self.properties_button.setToolTip(_("Show the Properties."))
self.properties_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.properties_button, 6, 0, 1, 3)
# PROPERTIES Frame
self.properties_frame = QtWidgets.QFrame()
self.properties_frame.setContentsMargins(0, 0, 0, 0)
grid0.addWidget(self.properties_frame, 7, 0, 1, 3)
self.properties_box = QtWidgets.QVBoxLayout()
self.properties_box.setContentsMargins(0, 0, 0, 0)
self.properties_frame.setLayout(self.properties_box)
self.properties_frame.hide()
self.treeWidget = FCTree(columns=2)
self.properties_box.addWidget(self.treeWidget)
self.properties_box.setStretch(0, 0)
# ### Tools Drills ####
self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
self.tools_table_label.setToolTip(
@ -561,9 +613,9 @@ class ExcellonObjectUI(ObjectUI):
hlay_plot.addStretch()
hlay_plot.addWidget(self.plot_cb)
grid0.addWidget(self.tools_table_label, 6, 0)
grid0.addWidget(self.table_visibility_cb, 6, 1)
grid0.addLayout(hlay_plot, 6, 2)
grid0.addWidget(self.tools_table_label, 8, 0)
grid0.addWidget(self.table_visibility_cb, 8, 1)
grid0.addLayout(hlay_plot, 8, 2)
# #############################################################################################################
# #############################################################################################################
@ -657,7 +709,7 @@ class ExcellonObjectUI(ObjectUI):
self.milling_button = QtWidgets.QPushButton(_('Milling Tool'))
self.milling_button.setIcon(QtGui.QIcon(self.app.resource_location + '/milling_tool32.png'))
self.milling_button.setToolTip(
_("Generate GCode out of slot holes in an Excellon object.")
_("Generate a Geometry for milling drills or slots in an Excellon object.")
)
self.milling_button.setStyleSheet("""
QPushButton
@ -666,6 +718,8 @@ class ExcellonObjectUI(ObjectUI):
}
""")
grid2.addWidget(self.milling_button, 6, 0, 1, 2)
# TODO until the Milling Tool is finished this stays disabled
self.milling_button.setDisabled(True)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@ -689,7 +743,7 @@ class ExcellonObjectUI(ObjectUI):
)
self.grid6.addWidget(self.mill_hole_label, 5, 0, 1, 3)
self.tdlabel = QtWidgets.QLabel('%s:' % _('Tool Dia'))
self.tdlabel = QtWidgets.QLabel('%s:' % _('Milling Diameter'))
self.tdlabel.setToolTip(
_("Diameter of the cutting tool.")
)
@ -704,7 +758,7 @@ class ExcellonObjectUI(ObjectUI):
self.generate_milling_button = QtWidgets.QPushButton(_('Mill Drills'))
self.generate_milling_button.setToolTip(
_("Create the Geometry Object\n"
"for milling DRILLS toolpaths.")
"for milling drills.")
)
self.generate_milling_button.setStyleSheet("""
QPushButton
@ -724,7 +778,7 @@ class ExcellonObjectUI(ObjectUI):
self.generate_milling_slots_button = QtWidgets.QPushButton(_('Mill Slots'))
self.generate_milling_slots_button.setToolTip(
_("Create the Geometry Object\n"
"for milling SLOTS toolpaths.")
"for milling slots.")
)
self.generate_milling_slots_button.setStyleSheet("""
QPushButton
@ -814,6 +868,32 @@ class GeometryObjectUI(ObjectUI):
""")
grid_header.addWidget(self.editor_button, 4, 0, 1, 3)
# PROPERTIES CB
self.properties_button = FCButton('%s' % _("PROPERTIES"), checkable=True)
self.properties_button.setIcon(QtGui.QIcon(self.app.resource_location + '/properties32.png'))
self.properties_button.setToolTip(_("Show the Properties."))
self.properties_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid_header.addWidget(self.properties_button, 6, 0, 1, 3)
# PROPERTIES Frame
self.properties_frame = QtWidgets.QFrame()
self.properties_frame.setContentsMargins(0, 0, 0, 0)
grid_header.addWidget(self.properties_frame, 7, 0, 1, 3)
self.properties_box = QtWidgets.QVBoxLayout()
self.properties_box.setContentsMargins(0, 0, 0, 0)
self.properties_frame.setLayout(self.properties_box)
self.properties_frame.hide()
self.treeWidget = FCTree(columns=2)
self.properties_box.addWidget(self.treeWidget)
self.properties_box.setStretch(0, 0)
# add a frame and inside add a vertical box layout. Inside this vbox layout I add all the Tools widgets
# this way I can hide/show the frame
self.geo_tools_frame = QtWidgets.QFrame()
@ -980,8 +1060,9 @@ class GeometryObjectUI(ObjectUI):
self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
self.addtool_from_db_btn.setToolTip(
_("Add a new tool to the Tool Table\n"
"from the Tool Database.\n"
"Tool database administration in Menu: Options -> Tools Database")
"from the Tools Database.\n"
"Tools database administration in in:\n"
"Menu: Options -> Tools Database")
)
bhlay.addWidget(self.addtool_btn)
@ -1757,6 +1838,32 @@ class CNCObjectUI(ObjectUI):
""")
f_lay.addWidget(self.editor_button, 4, 0, 1, 3)
# PROPERTIES CB
self.properties_button = FCButton('%s' % _("PROPERTIES"), checkable=True)
self.properties_button.setIcon(QtGui.QIcon(self.app.resource_location + '/properties32.png'))
self.properties_button.setToolTip(_("Show the Properties."))
self.properties_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
f_lay.addWidget(self.properties_button, 6, 0, 1, 3)
# PROPERTIES Frame
self.properties_frame = QtWidgets.QFrame()
self.properties_frame.setContentsMargins(0, 0, 0, 0)
f_lay.addWidget(self.properties_frame, 7, 0, 1, 3)
self.properties_box = QtWidgets.QVBoxLayout()
self.properties_box.setContentsMargins(0, 0, 0, 0)
self.properties_frame.setLayout(self.properties_box)
self.properties_frame.hide()
self.treeWidget = FCTree(columns=2)
self.properties_box.addWidget(self.treeWidget)
self.properties_box.setStretch(0, 0)
# Annotation
self.annotation_cb = FCCheckBox(_("Display Annotation"))
self.annotation_cb.setToolTip(
@ -1764,12 +1871,12 @@ class CNCObjectUI(ObjectUI):
"When checked it will display numbers in order for each end\n"
"of a travel line.")
)
f_lay.addWidget(self.annotation_cb, 6, 0, 1, 3)
f_lay.addWidget(self.annotation_cb, 8, 0, 1, 3)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
f_lay.addWidget(separator_line, 8, 0, 1, 3)
f_lay.addWidget(separator_line, 10, 0, 1, 3)
# Travelled Distance
self.t_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _("Travelled distance"))
@ -1780,9 +1887,9 @@ class CNCObjectUI(ObjectUI):
self.t_distance_entry = FCEntry()
self.units_label = QtWidgets.QLabel()
f_lay.addWidget(self.t_distance_label, 10, 0)
f_lay.addWidget(self.t_distance_entry, 10, 1)
f_lay.addWidget(self.units_label, 10, 2)
f_lay.addWidget(self.t_distance_label, 12, 0)
f_lay.addWidget(self.t_distance_entry, 12, 1)
f_lay.addWidget(self.units_label, 12, 2)
# Estimated Time
self.t_time_label = QtWidgets.QLabel("<b>%s:</b>" % _("Estimated time"))
@ -1793,9 +1900,9 @@ class CNCObjectUI(ObjectUI):
self.t_time_entry = FCEntry()
self.units_time_label = QtWidgets.QLabel()
f_lay.addWidget(self.t_time_label, 12, 0)
f_lay.addWidget(self.t_time_entry, 12, 1)
f_lay.addWidget(self.units_time_label, 12, 2)
f_lay.addWidget(self.t_time_label, 14, 0)
f_lay.addWidget(self.t_time_entry, 14, 1)
f_lay.addWidget(self.units_time_label, 14, 2)
self.t_distance_label.hide()
self.t_distance_entry.setVisible(False)
@ -1805,7 +1912,7 @@ class CNCObjectUI(ObjectUI):
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
f_lay.addWidget(separator_line, 14, 0, 1, 3)
f_lay.addWidget(separator_line, 16, 0, 1, 3)
hlay = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(hlay)

View File

@ -342,6 +342,7 @@ class PreferencesUIManager:
"tools_iso_rest": self.ui.tools_defaults_form.tools_iso_group.rest_cb,
"tools_iso_combine_passes": self.ui.tools_defaults_form.tools_iso_group.combine_passes_cb,
"tools_iso_check_valid": self.ui.tools_defaults_form.tools_iso_group.valid_cb,
"tools_iso_isoexcept": self.ui.tools_defaults_form.tools_iso_group.except_cb,
"tools_iso_selection": self.ui.tools_defaults_form.tools_iso_group.select_combo,
"tools_iso_poly_ints": self.ui.tools_defaults_form.tools_iso_group.poly_int_cb,
@ -381,6 +382,12 @@ class PreferencesUIManager:
"tools_drill_f_plunge": self.ui.tools_defaults_form.tools_drill_group.fplunge_cb,
"tools_drill_f_retract": self.ui.tools_defaults_form.tools_drill_group.fretract_cb,
# Area Exclusion
"tools_drill_area_exclusion": self.ui.tools_defaults_form.tools_drill_group.exclusion_cb,
"tools_drill_area_shape": self.ui.tools_defaults_form.tools_drill_group.area_shape_radio,
"tools_drill_area_strategy": self.ui.tools_defaults_form.tools_drill_group.strategy_radio,
"tools_drill_area_overz": self.ui.tools_defaults_form.tools_drill_group.over_z_entry,
# NCC Tool
"tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
"tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
@ -461,6 +468,7 @@ class PreferencesUIManager:
"tools_film_file_type_radio": self.ui.tools_defaults_form.tools_film_group.file_type_radio,
"tools_film_orientation": self.ui.tools_defaults_form.tools_film_group.orientation_radio,
"tools_film_pagesize": self.ui.tools_defaults_form.tools_film_group.pagesize_combo,
"tools_film_png_dpi": self.ui.tools_defaults_form.tools_film_group.png_dpi_spinner,
# Panelize Tool
"tools_panelize_spacing_columns": self.ui.tools_defaults_form.tools_panelize_group.pspacing_columns,
@ -949,7 +957,7 @@ class PreferencesUIManager:
self.save_defaults(silent=False)
# load the defaults so they are updated into the app
self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig'))
self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig'), inform=self.inform)
settgs = QSettings("Open Source", "FlatCAM")

View File

@ -197,13 +197,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
# -----------------------------
# --- Area Exclusion ----------
# -----------------------------
self.adv_label = QtWidgets.QLabel('<b>%s:</b>' % _('Area Exclusion'))
self.adv_label.setToolTip(
_("Area exclusion parameters.\n"
"Those parameters are available only for\n"
"Advanced App. Level.")
self.area_exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Area Exclusion'))
self.area_exc_label.setToolTip(
_("Area exclusion parameters.")
)
grid1.addWidget(self.adv_label, 13, 0, 1, 2)
grid1.addWidget(self.area_exc_label, 13, 0, 1, 2)
# Exclusion Area CB
self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))

View File

@ -64,9 +64,9 @@ class Tools2InvertPrefGroupUI(OptionsGroupUI):
"- bevel -> the lines are joined by a third line")
)
self.join_radio = RadioSet([
{'label': 'Rounded', 'value': 'r'},
{'label': 'Square', 'value': 's'},
{'label': 'Bevel', 'value': 'b'}
{'label': _('Rounded'), 'value': 'r'},
{'label': _('Square'), 'value': 's'},
{'label': _('Bevel'), 'value': 'b'}
], orientation='vertical', stretch=False)
grid0.addWidget(self.join_label, 5, 0, 1, 2)

View File

@ -2,7 +2,7 @@ from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings, Qt
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry, \
OptionalInputSection, NumericalEvalEntry
OptionalInputSection, NumericalEvalEntry, FCLabel
from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -28,7 +28,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
self.decimals = decimals
# ## Clear non-copper regions
self.drill_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
self.drill_label = FCLabel("<b>%s:</b>" % _("Parameters"))
self.drill_label.setToolTip(
_("Create CNCJob with toolpaths for drilling or milling holes.")
)
@ -38,7 +38,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
self.layout.addLayout(grid0)
# Tool order Radio Button
self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
self.order_label = FCLabel('%s:' % _('Tool order'))
self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
"'No' --> means that the used order is the one in the tool table\n"
"'Forward' --> means that the tools will be ordered from small to big\n"
@ -54,7 +54,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.order_radio, 1, 1, 1, 2)
# Cut Z
cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
cutzlabel = FCLabel('%s:' % _('Cut Z'))
cutzlabel.setToolTip(
_("Drill depth (negative)\n"
"below the copper surface.")
@ -95,7 +95,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.maxdepth_entry, 4, 1, 1, 2)
# Travel Z
travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
travelzlabel = FCLabel('%s:' % _('Travel Z'))
travelzlabel.setToolTip(
_("Tool height when travelling\n"
"across the XY plane.")
@ -121,7 +121,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.toolchange_cb, 6, 0, 1, 3)
# Tool Change Z
toolchangezlabel = QtWidgets.QLabel('%s:' % _('Toolchange Z'))
toolchangezlabel = FCLabel('%s:' % _('Toolchange Z'))
toolchangezlabel.setToolTip(
_("Z-axis position (height) for\n"
"tool change.")
@ -139,7 +139,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.toolchangez_entry, 7, 1, 1, 2)
# End Move Z
endz_label = QtWidgets.QLabel('%s:' % _('End move Z'))
endz_label = FCLabel('%s:' % _('End move Z'))
endz_label.setToolTip(
_("Height of the tool after\n"
"the last move at the end of the job.")
@ -156,7 +156,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.endz_entry, 8, 1, 1, 2)
# End Move X,Y
endmove_xy_label = QtWidgets.QLabel('%s:' % _('End move X,Y'))
endmove_xy_label = FCLabel('%s:' % _('End move X,Y'))
endmove_xy_label.setToolTip(
_("End move X,Y position. In format (x,y).\n"
"If no value is entered then there is no move\n"
@ -168,7 +168,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.endxy_entry, 9, 1, 1, 2)
# Feedrate Z
frlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
frlabel = FCLabel('%s:' % _('Feedrate Z'))
frlabel.setToolTip(
_("Tool speed while drilling\n"
"(in units per minute).\n"
@ -183,7 +183,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.feedrate_z_entry, 10, 1, 1, 2)
# Spindle speed
spdlabel = QtWidgets.QLabel('%s:' % _('Spindle Speed'))
spdlabel = FCLabel('%s:' % _('Spindle Speed'))
spdlabel.setToolTip(
_("Speed of the spindle\n"
"in RPM (optional)")
@ -206,7 +206,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.dwell_cb, 12, 0, 1, 3)
# Dwell Time
dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
dwelltime = FCLabel('%s:' % _('Duration'))
dwelltime.setToolTip(_("Number of time units for spindle to dwell."))
self.dwelltime_entry = FCDoubleSpinner()
self.dwelltime_entry.set_precision(self.decimals)
@ -218,7 +218,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
# preprocessor selection
pp_excellon_label = QtWidgets.QLabel('%s:' % _("Preprocessor"))
pp_excellon_label = FCLabel('%s:' % _("Preprocessor"))
pp_excellon_label.setToolTip(
_("The preprocessor JSON file that dictates\n"
"Gcode output.")
@ -236,7 +236,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(separator_line, 16, 0, 1, 3)
# DRILL SLOTS LABEL
self.dslots_label = QtWidgets.QLabel('<b>%s:</b>' % _('Drilling Slots'))
self.dslots_label = FCLabel('<b>%s:</b>' % _('Drilling Slots'))
grid0.addWidget(self.dslots_label, 18, 0, 1, 3)
# Drill slots
@ -247,7 +247,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.drill_slots_cb, 20, 0, 1, 3)
# Drill Overlap
self.drill_overlap_label = QtWidgets.QLabel('%s:' % _('Overlap'))
self.drill_overlap_label = FCLabel('%s:' % _('Overlap'))
self.drill_overlap_label.setToolTip(
_("How much (percentage) of the tool diameter to overlap previous drill hole.")
)
@ -273,14 +273,14 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 26, 0, 1, 3)
self.exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
self.exc_label = FCLabel('<b>%s:</b>' % _('Advanced Options'))
self.exc_label.setToolTip(
_("A list of advanced parameters.")
)
grid0.addWidget(self.exc_label, 28, 0, 1, 3)
# Offset Z
offsetlabel = QtWidgets.QLabel('%s:' % _('Offset Z'))
offsetlabel = FCLabel('%s:' % _('Offset Z'))
offsetlabel.setToolTip(
_("Some drill bits (the larger ones) need to drill deeper\n"
"to create the desired exit hole diameter due of the tip shape.\n"
@ -293,7 +293,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.offset_entry, 29, 1, 1, 2)
# ToolChange X,Y
toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X,Y'))
toolchange_xy_label = FCLabel('%s:' % _('Toolchange X,Y'))
toolchange_xy_label.setToolTip(
_("Toolchange X,Y position.")
)
@ -303,7 +303,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.toolchangexy_entry, 31, 1, 1, 2)
# Start Z
startzlabel = QtWidgets.QLabel('%s:' % _('Start Z'))
startzlabel = FCLabel('%s:' % _('Start Z'))
startzlabel.setToolTip(
_("Height of the tool just after start.\n"
"Delete the value if you don't need this feature.")
@ -314,7 +314,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.estartz_entry, 33, 1, 1, 2)
# Feedrate Rapids
fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
fr_rapid_label = FCLabel('%s:' % _('Feedrate Rapids'))
fr_rapid_label.setToolTip(
_("Tool speed while drilling\n"
"(in units per minute).\n"
@ -330,7 +330,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.feedrate_rapid_entry, 35, 1, 1, 2)
# Probe depth
self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
self.pdepth_label = FCLabel('%s:' % _("Probe Z depth"))
self.pdepth_label.setToolTip(
_("The maximum depth that the probe is allowed\n"
"to probe. Negative value, in current units.")
@ -343,7 +343,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.pdepth_entry, 37, 1, 1, 2)
# Probe feedrate
self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
self.feedrate_probe_label = FCLabel('%s:' % _("Feedrate Probe"))
self.feedrate_probe_label.setToolTip(
_("The feedrate used while the probe is probing.")
)
@ -355,7 +355,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.feedrate_probe_entry, 38, 1, 1, 2)
# Spindle direction
spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle direction'))
spindle_dir_label = FCLabel('%s:' % _('Spindle direction'))
spindle_dir_label.setToolTip(
_("This sets the direction that the spindle is rotating.\n"
"It can be either:\n"
@ -389,4 +389,64 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.fretract_cb, 45, 0, 1, 3)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 46, 0, 1, 3)
# -----------------------------
# --- Area Exclusion ----------
# -----------------------------
self.area_exc_label = FCLabel('<b>%s:</b>' % _('Area Exclusion'))
self.area_exc_label.setToolTip(
_("Area exclusion parameters.")
)
grid0.addWidget(self.area_exc_label, 47, 0, 1, 2)
# Exclusion Area CB
self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
self.exclusion_cb.setToolTip(
_(
"Include exclusion areas.\n"
"In those areas the travel of the tools\n"
"is forbidden."
)
)
grid0.addWidget(self.exclusion_cb, 49, 0, 1, 2)
# Area Selection shape
self.area_shape_label = FCLabel('%s:' % _("Shape"))
self.area_shape_label.setToolTip(
_("The kind of selection shape used for area selection.")
)
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
grid0.addWidget(self.area_shape_label, 51, 0)
grid0.addWidget(self.area_shape_radio, 51, 1)
# Chose Strategy
self.strategy_label = FCLabel('%s:' % _("Strategy"))
self.strategy_label.setToolTip(_("The strategy followed when encountering an exclusion area.\n"
"Can be:\n"
"- Over -> when encountering the area, the tool will go to a set height\n"
"- Around -> will avoid the exclusion area by going around the area"))
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
{'label': _('Around'), 'value': 'around'}])
grid0.addWidget(self.strategy_label, 53, 0)
grid0.addWidget(self.strategy_radio, 53, 1)
# Over Z
self.over_z_label = FCLabel('%s:' % _("Over Z"))
self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
"an interdiction area."))
self.over_z_entry = FCDoubleSpinner()
self.over_z_entry.set_range(0.000, 9999.9999)
self.over_z_entry.set_precision(self.decimals)
grid0.addWidget(self.over_z_label, 55, 0)
grid0.addWidget(self.over_z_entry, 55, 1)
self.layout.addStretch()

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt, QSettings
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from appGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox, FCColorEntry
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox, FCColorEntry, FCLabel, FCSpinner
from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -28,7 +28,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
self.decimals = decimals
# ## Parameters
self.film_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
self.film_label = FCLabel("<b>%s:</b>" % _("Parameters"))
self.film_label.setToolTip(
_("Create a PCB film from a Gerber or Geometry object.\n"
"The file is saved in SVG format.")
@ -40,7 +40,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
self.film_type_radio = RadioSet([{'label': 'Pos', 'value': 'pos'},
{'label': 'Neg', 'value': 'neg'}])
ftypelbl = QtWidgets.QLabel('%s:' % _('Film Type'))
ftypelbl = FCLabel('%s:' % _('Film Type'))
ftypelbl.setToolTip(
_("Generate a Positive black film or a Negative film.\n"
"Positive means that it will print the features\n"
@ -53,7 +53,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_type_radio, 0, 1)
# Film Color
self.film_color_label = QtWidgets.QLabel('%s:' % _('Film Color'))
self.film_color_label = FCLabel('%s:' % _('Film Color'))
self.film_color_label.setToolTip(
_("Set the film color when positive film is selected.")
)
@ -68,7 +68,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
self.film_boundary_entry.set_range(0, 9999.9999)
self.film_boundary_entry.setSingleStep(0.1)
self.film_boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
self.film_boundary_label = FCLabel('%s:' % _("Border"))
self.film_boundary_label.setToolTip(
_("Specify a border around the object.\n"
"Only for negative film.\n"
@ -87,7 +87,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
self.film_scale_stroke_entry.set_range(0, 9999.9999)
self.film_scale_stroke_entry.setSingleStep(0.1)
self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
self.film_scale_stroke_label = FCLabel('%s:' % _("Scale Stroke"))
self.film_scale_stroke_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"
@ -96,7 +96,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_scale_stroke_label, 3, 0)
grid0.addWidget(self.film_scale_stroke_entry, 3, 1)
self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments"))
self.film_adj_label = FCLabel('<b>%s</b>' % _("Film Adjustments"))
self.film_adj_label.setToolTip(
_("Sometime the printers will distort the print shape, especially the Laser types.\n"
"This section provide the tools to compensate for the print distortions.")
@ -117,7 +117,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.film_scale_cb, 5, 0, 1, 2)
self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
self.film_scalex_label = FCLabel('%s:' % _("X factor"))
self.film_scalex_entry = FCDoubleSpinner()
self.film_scalex_entry.set_range(-999.9999, 999.9999)
self.film_scalex_entry.set_precision(self.decimals)
@ -126,7 +126,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_scalex_label, 6, 0)
grid0.addWidget(self.film_scalex_entry, 6, 1)
self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
self.film_scaley_label = FCLabel('%s:' % _("Y factor"))
self.film_scaley_entry = FCDoubleSpinner()
self.film_scaley_entry.set_range(-999.9999, 999.9999)
self.film_scaley_entry.set_precision(self.decimals)
@ -148,7 +148,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.film_skew_cb, 8, 0, 1, 2)
self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
self.film_skewx_label = FCLabel('%s:' % _("X angle"))
self.film_skewx_entry = FCDoubleSpinner()
self.film_skewx_entry.set_range(-999.9999, 999.9999)
self.film_skewx_entry.set_precision(self.decimals)
@ -157,7 +157,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_skewx_label, 9, 0)
grid0.addWidget(self.film_skewx_entry, 9, 1)
self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
self.film_skewy_label = FCLabel('%s:' % _("Y angle"))
self.film_skewy_entry = FCDoubleSpinner()
self.film_skewy_entry.set_range(-999.9999, 999.9999)
self.film_skewy_entry.set_precision(self.decimals)
@ -166,7 +166,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.film_skewy_label, 10, 0)
grid0.addWidget(self.film_skewy_entry, 10, 1)
self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
self.film_skew_ref_label = FCLabel('%s:' % _("Reference"))
self.film_skew_ref_label.setToolTip(
_("The reference point to be used as origin for the skew.\n"
"It can be one of the four points of the geometry bounding box.")
@ -198,7 +198,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
{'label': _('Y'), 'value': 'y'},
{'label': _('Both'), 'value': 'both'}],
stretch=False)
self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror axis"))
grid0.addWidget(self.film_mirror_axis_label, 13, 0)
grid0.addWidget(self.film_mirror_axis, 13, 1)
@ -213,7 +213,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
{'label': _('PDF'), 'value': 'pdf'}
], stretch=False)
self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
self.file_type_label = FCLabel(_("Film Type:"))
self.file_type_label.setToolTip(
_("The file type of the saved film. Can be:\n"
"- 'SVG' -> open-source vectorial format\n"
@ -224,7 +224,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.file_type_radio, 15, 1)
# Page orientation
self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
self.orientation_label = FCLabel('%s:' % _("Page Orientation"))
self.orientation_label.setToolTip(_("Can be:\n"
"- Portrait\n"
"- Landscape"))
@ -237,7 +237,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.orientation_radio, 16, 1)
# Page Size
self.pagesize_label = QtWidgets.QLabel('%s:' % _("Page Size"))
self.pagesize_label = FCLabel('%s:' % _("Page Size"))
self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
self.pagesize_combo = FCComboBox()
@ -302,6 +302,17 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.pagesize_label, 17, 0)
grid0.addWidget(self.pagesize_combo, 17, 1)
# PNG DPI
self.png_dpi_label = FCLabel('%s:' % "PNG DPI")
self.png_dpi_label.setToolTip(
_("Default value is 96 DPI. Change this value to scale the PNG file.")
)
self.png_dpi_spinner = FCSpinner()
self.png_dpi_spinner.set_range(0, 100000)
grid0.addWidget(self.png_dpi_label, 19, 0)
grid0.addWidget(self.png_dpi_spinner, 19, 1)
self.layout.addStretch()
# Film Tool

View File

@ -271,6 +271,16 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.except_cb.setObjectName("i_except")
grid0.addWidget(self.except_cb, 17, 2)
# Check Tool validity
self.valid_cb = FCCheckBox(label=_('Check validity'))
self.valid_cb.setToolTip(
_("If checked then the tools diameters are verified\n"
"if they will provide a complete isolation.")
)
self.valid_cb.setObjectName("i_check")
grid0.addWidget(self.valid_cb, 18, 0, 1, 3)
# Isolation Scope
self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
self.select_label.setToolTip(

View File

@ -574,8 +574,14 @@ class CNCJobObject(FlatCAMObj, CNCjob):
self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
self.ui.review_gcode_button.clicked.connect(self.on_edit_code_click)
# Editor Signal
self.ui.editor_button.clicked.connect(lambda: self.app.object2editor())
# Properties
self.ui.properties_button.toggled.connect(self.on_properties)
self.calculations_finished.connect(self.update_area_chull)
# autolevelling signals
self.ui.sal_cb.stateChanged.connect(self.on_toggle_autolevelling)
self.ui.al_mode_radio.activated_custom.connect(self.on_mode_radio)
@ -699,6 +705,20 @@ class CNCJobObject(FlatCAMObj, CNCjob):
except (TypeError, AttributeError):
pass
def on_properties(self, state):
if state:
self.ui.properties_frame.show()
else:
self.ui.properties_frame.hide()
return
self.ui.treeWidget.clear()
self.add_properties_items(obj=self, treeWidget=self.ui.treeWidget)
self.ui.treeWidget.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.MinimumExpanding)
# make sure that the FCTree widget columns are resized to content
self.ui.treeWidget.resize_sig.emit()
def on_add_al_probepoints(self):
# create the solid_geo
@ -1260,7 +1280,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
pass
answer = self.on_grbl_wake()
answer = ['ok'] # hack for development without a GRBL controller connected
answer = ['ok'] # FIXME: hack for development without a GRBL controller connected
for line in answer:
if 'ok' in line.lower():
self.ui.com_connect_button.setStyleSheet("QPushButton {background-color: seagreen;}")
@ -2548,7 +2568,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
# g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0))
#
# tool_dia_copy['gcode_parsed'] = deepcopy(dia_value)
# tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value])
# tool_dia_copy['solid_geometry'] = unary_union([geo['geom'] for geo in dia_value])
temp_tools_dict.update({
tooluid_key: deepcopy(tool_dia_copy)

View File

@ -176,7 +176,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
# Editor
self.ui.editor_button.clicked.connect(lambda: self.app.object2editor())
# Properties
self.ui.properties_button.toggled.connect(self.on_properties)
self.calculations_finished.connect(self.update_area_chull)
self.ui.drill_button.clicked.connect(lambda: self.app.drilling_tool.run(toggle=True))
# self.ui.milling_button.clicked.connect(lambda: self.app.milling_tool.run(toggle=True))
@ -438,6 +442,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui.slot_tooldia_entry.setDisabled(False)
self.ui.generate_milling_slots_button.setDisabled(False)
# update the milling section
self.on_row_selection_change()
self.ui_connect()
def ui_connect(self):
@ -510,12 +517,22 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui.slot_tooldia_entry.setDisabled(False)
self.ui.generate_milling_slots_button.setDisabled(False)
# find if we have drills:
has_drills = None
for tt in self.tools:
if 'drills' in self.tools[tt] and self.tools[tt]['drills']:
has_drills = True
break
has_drills = True
has_slots = True
for row in sel_rows:
row_dia = self.app.dec_format(float(self.ui.tools_table.item(row, 1).text()), self.decimals)
for tt in self.tools:
tool_dia = self.app.dec_format(float(self.tools[tt]['tooldia']), self.decimals)
if tool_dia == row_dia:
# find if we have drills:
if 'drills' not in self.tools[tt] or not self.tools[tt]['drills']:
has_drills = None
# find if we have slots
if 'slots' not in self.tools[tt] or not self.tools[tt]['slots']:
has_slots = None
if has_drills is None:
self.ui.tooldia_entry.setDisabled(True)
self.ui.generate_milling_button.setDisabled(True)
@ -523,12 +540,6 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui.tooldia_entry.setDisabled(False)
self.ui.generate_milling_button.setDisabled(False)
# find if we have slots
has_slots = None
for tt in self.tools:
if 'slots' in self.tools[tt] and self.tools[tt]['slots']:
has_slots = True
break
if has_slots is None:
self.ui.slot_tooldia_entry.setDisabled(True)
self.ui.generate_milling_slots_button.setDisabled(True)
@ -603,6 +614,19 @@ class ExcellonObject(FlatCAMObj, Excellon):
def on_table_visibility_toggle(self, state):
self.ui.tools_table.show() if state else self.ui.tools_table.hide()
def on_properties(self, state):
if state:
self.ui.properties_frame.show()
else:
self.ui.properties_frame.hide()
return
self.ui.treeWidget.clear()
self.add_properties_items(obj=self, treeWidget=self.ui.treeWidget)
# make sure that the FCTree widget columns are resized to content
self.ui.treeWidget.resize_sig.emit()
def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
"""
Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
@ -878,7 +902,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.options["multidepth"] = self.app.defaults["geometry_multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
@ -978,7 +1002,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.options["multidepth"] = self.app.defaults["geometry_multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution

View File

@ -438,6 +438,11 @@ class GeometryObject(FlatCAMObj, Geometry):
"area_shape": self.ui.area_shape_radio,
"area_strategy": self.ui.strategy_radio,
"area_overz": self.ui.over_z_entry,
"polish": self.ui.polish_cb,
"polish_dia": self.ui.polish_dia_entry,
"polish_pressure": self.ui.polish_pressure_entry,
"polish_overlap": self.ui.polish_over_entry,
"polish_method": self.ui.polish_method_combo,
})
self.param_fields.update({
@ -589,8 +594,13 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
# Editor Signal
self.ui.editor_button.clicked.connect(self.app.object2editor)
# Properties
self.ui.properties_button.toggled.connect(self.on_properties)
self.calculations_finished.connect(self.update_area_chull)
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
@ -614,6 +624,20 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.geo_tools_table.drag_drop_sig.connect(self.rebuild_ui)
def on_properties(self, state):
if state:
self.ui.properties_frame.show()
else:
self.ui.properties_frame.hide()
return
self.ui.treeWidget.clear()
self.add_properties_items(obj=self, treeWidget=self.ui.treeWidget)
self.ui.treeWidget.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.MinimumExpanding)
# make sure that the FCTree widget columns are resized to content
self.ui.treeWidget.resize_sig.emit()
def rebuild_ui(self):
# read the table tools uid
current_uid_list = []
@ -722,7 +746,13 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
# common parameters update
self.ui.toolchangeg_cb.stateChanged.connect(self.update_common_param_in_storage)
self.ui.toolchangez_entry.editingFinished.connect(self.update_common_param_in_storage)
self.ui.endz_entry.editingFinished.connect(self.update_common_param_in_storage)
self.ui.endxy_entry.editingFinished.connect(self.update_common_param_in_storage)
self.ui.pp_geometry_name_cb.currentIndexChanged.connect(self.update_common_param_in_storage)
self.ui.exclusion_cb.stateChanged.connect(self.update_common_param_in_storage)
self.ui.polish_cb.stateChanged.connect(self.update_common_param_in_storage)
def ui_disconnect(self):
@ -806,6 +836,36 @@ class GeometryObject(FlatCAMObj, Geometry):
except (TypeError, AttributeError):
pass
# common parameters update
try:
self.ui.toolchangeg_cb.stateChanged.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.toolchangez_entry.editingFinished.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.endz_entry.editingFinished.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.endxy_entry.editingFinished.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.pp_geometry_name_cb.currentIndexChanged.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.exclusion_cb.stateChanged.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
try:
self.ui.polish_cb.stateChanged.disconnect(self.update_common_param_in_storage)
except (TypeError, AttributeError):
pass
def on_toggle_all_rows(self):
"""
will toggle the selection of all rows in Tools table
@ -1555,7 +1615,13 @@ class GeometryObject(FlatCAMObj, Geometry):
def update_common_param_in_storage(self):
for tooluid_value in self.tools.values():
tooluid_value['data']['toolchange'] = self.ui.toolchangeg_cb.get_value()
tooluid_value['data']['toolchangez'] = self.ui.toolchangez_entry.get_value()
tooluid_value['data']['endz'] = self.ui.endz_entry.get_value()
tooluid_value['data']['endxy'] = self.ui.endxy_entry.get_value()
tooluid_value['data']['ppname_g'] = self.ui.pp_geometry_name_cb.get_value()
tooluid_value['data']['area_exclusion'] = self.ui.exclusion_cb.get_value()
tooluid_value['data']['polish'] = self.ui.polish_cb.get_value()
def select_tools_table_row(self, row, clearsel=None):
if clearsel:
@ -1854,6 +1920,7 @@ class GeometryObject(FlatCAMObj, Geometry):
return
self.multigeo = True
# Object initialization function for app.app_obj.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init_single_geometry(job_obj, app_obj):
@ -1985,7 +2052,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# TODO this serve for bounding box creation only; should be optimized
# commented this; there is no need for the actual GCode geometry - the original one will serve as well
# for bounding box values
# dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']])
# dia_cnc_dict['solid_geometry'] = unary_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']])
try:
dia_cnc_dict['solid_geometry'] = tool_solid_geometry
self.app.inform.emit('[success] %s...' % _("Finished G-Code processing"))
@ -2115,9 +2182,9 @@ class GeometryObject(FlatCAMObj, Geometry):
is_first = True if tooluid_key == tool_lst[0] else False
is_last = True if tooluid_key == tool_lst[-1] else False
res, start_gcode = job_obj.geometry_tool_gcode_gen(tooluid_key, tools_dict, first_pt=(0, 0),
tolerance = tol,
tolerance=tol,
is_first=is_first, is_last=is_last,
toolchange = True)
toolchange=True)
if res == 'fail':
log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
return 'fail'
@ -2135,7 +2202,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# TODO this serve for bounding box creation only; should be optimized
# commented this; there is no need for the actual GCode geometry - the original one will serve as well
# for bounding box values
# geo_for_bound_values = cascaded_union([
# geo_for_bound_values = unary_union([
# geo['geom'] for geo in dia_cnc_dict['gcode_parsed'] if geo['geom'].is_valid is True
# ])
try:
@ -2303,8 +2370,8 @@ class GeometryObject(FlatCAMObj, Geometry):
toolchangexy=toolchangexy,
extracut=extracut, extracut_length=extracut_length,
startz=startz, endz=endz, endxy=endxy,
pp_geometry_name=ppname_g
)
pp_geometry_name=ppname_g)
job_obj.source_file = res
# tell gcode_parse from which point to start drawing the lines depending on what kind of object is the
# source of gcode
@ -2872,13 +2939,13 @@ class GeometryObject(FlatCAMObj, Geometry):
self.plot()
@staticmethod
def merge(geo_list, geo_final, multigeo=None, fuse_tools=None):
def merge(geo_list, geo_final, multi_geo=None, fuse_tools=None):
"""
Merges the geometry of objects in grb_list into the geometry of geo_final.
:param geo_list: List of GerberObject Objects to join.
:param geo_final: Destination GerberObject object.
:param multigeo: if the merged geometry objects are of type MultiGeo
:param multi_geo: if the merged geometry objects are of type MultiGeo
:param fuse_tools: If True will try to fuse tools of the same type for the Geometry objects
:return: None
"""
@ -2908,7 +2975,7 @@ class GeometryObject(FlatCAMObj, Geometry):
GeometryObject.merge(geo_list=geo_obj, geo_final=geo_final)
# If not list, just append
else:
if multigeo is None or multigeo is False:
if multi_geo is None or multi_geo is False:
geo_final.multigeo = False
else:
geo_final.multigeo = True
@ -2966,19 +3033,23 @@ class GeometryObject(FlatCAMObj, Geometry):
new_tool_nr = 1
for i_lst in intersect_list:
new_solid_geo = []
last_tool = None
for old_tool in i_lst:
new_solid_geo += new_tools[old_tool]['solid_geometry']
last_tool = old_tool
if new_solid_geo:
if new_solid_geo and last_tool:
final_tools[new_tool_nr] = \
{
k: deepcopy(new_tools[old_tool][k]) for k in new_tools[old_tool] if k != 'solid_geometry'
k: deepcopy(new_tools[last_tool][k]) for k in new_tools[last_tool] if k != 'solid_geometry'
}
final_tools[new_tool_nr]['solid_geometry'] = deepcopy(new_solid_geo)
new_tool_nr += 1
else:
final_tools = new_tools
# if not final_tools:
# return 'fail'
geo_final.tools = final_tools
@staticmethod

View File

@ -12,7 +12,7 @@
from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
from shapely.ops import cascaded_union
from shapely.ops import unary_union
from appParsers.ParseGerber import Gerber
from appObjects.FlatCAMObj import *
@ -150,6 +150,10 @@ class GerberObject(FlatCAMObj, Gerber):
# Editor
self.ui.editor_button.clicked.connect(lambda: self.app.object2editor())
# Properties
self.ui.properties_button.toggled.connect(self.on_properties)
self.calculations_finished.connect(self.update_area_chull)
# Tools
self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
@ -343,6 +347,19 @@ class GerberObject(FlatCAMObj, Gerber):
return new_geo
def on_properties(self, state):
if state:
self.ui.properties_frame.show()
else:
self.ui.properties_frame.hide()
return
self.ui.treeWidget.clear()
self.add_properties_items(obj=self, treeWidget=self.ui.treeWidget)
# make sure that the FCTree widget columns are resized to content
self.ui.treeWidget.resize_sig.emit()
def on_generate_buffer(self):
self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
@ -369,7 +386,7 @@ class GerberObject(FlatCAMObj, Gerber):
try:
self.solid_geometry = MultiPolygon(self.solid_geometry)
except Exception:
self.solid_geometry = cascaded_union(self.solid_geometry)
self.solid_geometry = unary_union(self.solid_geometry)
bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
if not self.options["noncopperrounded"]:
@ -395,7 +412,7 @@ class GerberObject(FlatCAMObj, Gerber):
try:
self.solid_geometry = MultiPolygon(self.solid_geometry)
except Exception:
self.solid_geometry = cascaded_union(self.solid_geometry)
self.solid_geometry = unary_union(self.solid_geometry)
# Bounding box with rounded corners
bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))

View File

@ -18,7 +18,12 @@ from appCommon.Common import LoudDict
from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy
from appGUI.VisPyVisuals import ShapeCollection
from shapely.ops import unary_union
from shapely.geometry import Polygon, MultiPolygon
from copy import deepcopy
import sys
import math
import gettext
import appTranslation as fcTranslate
@ -55,6 +60,9 @@ class FlatCAMObj(QtCore.QObject):
# signal to plot a single object
plot_single_object = QtCore.pyqtSignal()
# signal for Properties
calculations_finished = QtCore.pyqtSignal(float, float, float, float, float, object)
def __init__(self, name):
"""
Constructor.
@ -112,6 +120,9 @@ class FlatCAMObj(QtCore.QObject):
# self.units = 'IN'
self.units = self.app.defaults['units']
# this is the treeWidget from the UI; it is updated when the add_properties_items() method is called
self.treeWidget = None
self.plot_single_object.connect(self.single_object_plot)
def __del__(self):
@ -456,6 +467,444 @@ class FlatCAMObj(QtCore.QObject):
self.app.defaults[filter_string] = ';;'.join(filter_list)
return
def add_properties_items(self, obj, treeWidget):
self.treeWidget = treeWidget
parent = self.treeWidget.invisibleRootItem()
apertures = ''
tools = ''
drills = ''
slots = ''
others = ''
font = QtGui.QFont()
font.setBold(True)
p_color = QtGui.QColor("#000000") if self.app.defaults['global_gray_icons'] is False \
else QtGui.QColor("#FFFFFF")
# main Items categories
dims = self.treeWidget.addParent(
parent, _('Dimensions'), expanded=True, color=p_color, font=font)
options = self.treeWidget.addParent(parent, _('Options'), color=p_color, font=font)
if obj.kind.lower() == 'gerber':
apertures = self.treeWidget.addParent(
parent, _('Apertures'), expanded=True, color=p_color, font=font)
else:
tools = self.treeWidget.addParent(
parent, _('Tools'), expanded=True, color=p_color, font=font)
if obj.kind.lower() == 'excellon':
drills = self.treeWidget.addParent(
parent, _('Drills'), expanded=True, color=p_color, font=font)
slots = self.treeWidget.addParent(
parent, _('Slots'), expanded=True, color=p_color, font=font)
if obj.kind.lower() == 'cncjob':
others = self.treeWidget.addParent(
parent, _('Others'), expanded=True, color=p_color, font=font)
# separator = self.treeWidget.addParent(parent, '')
def job_thread(obj_prop):
self.app.proc_container.new(_("Calculating dimensions ... Please wait."))
length = 0.0
width = 0.0
area = 0.0
copper_area = 0.0
geo = obj_prop.solid_geometry
if geo:
# calculate physical dimensions
try:
xmin, ymin, xmax, ymax = obj_prop.bounds()
length = abs(xmax - xmin)
width = abs(ymax - ymin)
except Exception as ee:
log.debug("FlatCAMObj.addItems() -> calculate dimensions --> %s" % str(ee))
# calculate box area
if self.app.defaults['units'].lower() == 'mm':
area = (length * width) / 100
else:
area = length * width
if obj_prop.kind.lower() == 'gerber':
# calculate copper area
try:
for geo_el in geo:
copper_area += geo_el.area
except TypeError:
copper_area += geo.area
copper_area /= 100
else:
xmin = []
ymin = []
xmax = []
ymax = []
if obj_prop.kind.lower() == 'cncjob':
try:
for tool_k in obj_prop.exc_cnc_tools:
x0, y0, x1, y1 = unary_union(obj_prop.exc_cnc_tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
ymax.append(y1)
except Exception as ee:
log.debug("FlatCAMObj.addItems() --> %s" % str(ee))
try:
for tool_k in obj_prop.cnc_tools:
x0, y0, x1, y1 = unary_union(obj_prop.cnc_tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
ymax.append(y1)
except Exception as ee:
log.debug("FlatCAMObj.addItems() --> %s" % str(ee))
else:
try:
for tool_k in obj_prop.tools:
x0, y0, x1, y1 = unary_union(obj_prop.tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
ymax.append(y1)
except Exception as ee:
log.debug("FlatCAMObj.addItems() --> %s" % str(ee))
try:
xmin = min(xmin)
ymin = min(ymin)
xmax = max(xmax)
ymax = max(ymax)
length = abs(xmax - xmin)
width = abs(ymax - ymin)
# calculate box area
if self.app.defaults['units'].lower() == 'mm':
area = (length * width) / 100
else:
area = length * width
if obj_prop.kind.lower() == 'gerber':
# calculate copper area
# create a complete solid_geometry from the tools
geo_tools = []
for tool_k in obj_prop.tools:
if 'solid_geometry' in obj_prop.tools[tool_k]:
for geo_el in obj_prop.tools[tool_k]['solid_geometry']:
geo_tools.append(geo_el)
try:
for geo_el in geo_tools:
copper_area += geo_el.area
except TypeError:
copper_area += geo_tools.area
copper_area /= 100
except Exception as err:
log.debug("FlatCAMObj.addItems() --> %s" % str(err))
area_chull = 0.0
if obj_prop.kind.lower() != 'cncjob':
# calculate and add convex hull area
if geo:
if isinstance(geo, list) and geo[0] is not None:
if isinstance(geo, MultiPolygon):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = unary_union(geo)
env_obj = env_obj.convex_hull
else:
env_obj = unary_union(geo)
env_obj = env_obj.convex_hull
area_chull = env_obj.area
else:
area_chull = 0
else:
try:
area_chull = []
for tool_k in obj_prop.tools:
area_el = unary_union(obj_prop.tools[tool_k]['solid_geometry']).convex_hull
area_chull.append(area_el.area)
area_chull = max(area_chull)
except Exception as er:
area_chull = None
log.debug("FlatCAMObj.addItems() --> %s" % str(er))
if self.app.defaults['units'].lower() == 'mm' and area_chull:
area_chull = area_chull / 100
if area_chull is None:
area_chull = 0
self.calculations_finished.emit(area, length, width, area_chull, copper_area, dims)
self.app.worker_task.emit({'fcn': job_thread, 'params': [obj]})
# Options items
for option in obj.options:
if option == 'name':
continue
self.treeWidget.addChild(options, [str(option), str(obj.options[option])], True)
# Items that depend on the object type
if obj.kind.lower() == 'gerber':
temp_ap = {}
for ap in obj.apertures:
temp_ap.clear()
temp_ap = deepcopy(obj.apertures[ap])
temp_ap.pop('geometry', None)
solid_nr = 0
follow_nr = 0
clear_nr = 0
if 'geometry' in obj.apertures[ap]:
if obj.apertures[ap]['geometry']:
font.setBold(True)
for el in obj.apertures[ap]['geometry']:
if 'solid' in el:
solid_nr += 1
if 'follow' in el:
follow_nr += 1
if 'clear' in el:
clear_nr += 1
else:
font.setBold(False)
temp_ap['Solid_Geo'] = '%s Polygons' % str(solid_nr)
temp_ap['Follow_Geo'] = '%s LineStrings' % str(follow_nr)
temp_ap['Clear_Geo'] = '%s Polygons' % str(clear_nr)
apid = self.treeWidget.addParent(
apertures, str(ap), expanded=False, color=p_color, font=font)
for key in temp_ap:
self.treeWidget.addChild(apid, [str(key), str(temp_ap[key])], True)
elif obj.kind.lower() == 'excellon':
tot_drill_cnt = 0
tot_slot_cnt = 0
for tool, value in obj.tools.items():
toolid = self.treeWidget.addParent(
tools, str(tool), expanded=False, color=p_color, font=font)
drill_cnt = 0 # variable to store the nr of drills per tool
slot_cnt = 0 # variable to store the nr of slots per tool
# Find no of drills for the current tool
if 'drills' in value and value['drills']:
drill_cnt = len(value['drills'])
tot_drill_cnt += drill_cnt
# Find no of slots for the current tool
if 'slots' in value and value['slots']:
slot_cnt = len(value['slots'])
tot_slot_cnt += slot_cnt
self.treeWidget.addChild(
toolid,
[
_('Diameter'),
'%.*f %s' % (self.decimals, value['tooldia'], self.app.defaults['units'].lower())
],
True
)
self.treeWidget.addChild(toolid, [_('Drills number'), str(drill_cnt)], True)
self.treeWidget.addChild(toolid, [_('Slots number'), str(slot_cnt)], True)
self.treeWidget.addChild(drills, [_('Drills total number:'), str(tot_drill_cnt)], True)
self.treeWidget.addChild(slots, [_('Slots total number:'), str(tot_slot_cnt)], True)
elif obj.kind.lower() == 'geometry':
for tool, value in obj.tools.items():
geo_tool = self.treeWidget.addParent(
tools, str(tool), expanded=False, color=p_color, font=font)
for k, v in value.items():
if k == 'solid_geometry':
# printed_value = _('Present') if v else _('None')
try:
printed_value = str(len(v))
except (TypeError, AttributeError):
printed_value = '1'
self.treeWidget.addChild(geo_tool, [str(k), printed_value], True)
elif k == 'data':
tool_data = self.treeWidget.addParent(
geo_tool, str(k).capitalize(), color=p_color, font=font)
for data_k, data_v in v.items():
self.treeWidget.addChild(tool_data, [str(data_k), str(data_v)], True)
else:
self.treeWidget.addChild(geo_tool, [str(k), str(v)], True)
elif obj.kind.lower() == 'cncjob':
# for cncjob objects made from gerber or geometry
for tool, value in obj.cnc_tools.items():
geo_tool = self.treeWidget.addParent(
tools, str(tool), expanded=False, color=p_color, font=font)
for k, v in value.items():
if k == 'solid_geometry':
printed_value = _('Present') if v else _('None')
self.treeWidget.addChild(geo_tool, [_("Solid Geometry"), printed_value], True)
elif k == 'gcode':
printed_value = _('Present') if v != '' else _('None')
self.treeWidget.addChild(geo_tool, [_("GCode Text"), printed_value], True)
elif k == 'gcode_parsed':
printed_value = _('Present') if v else _('None')
self.treeWidget.addChild(geo_tool, [_("GCode Geometry"), printed_value], True)
elif k == 'data':
pass
else:
self.treeWidget.addChild(geo_tool, [str(k), str(v)], True)
v = value['data']
tool_data = self.treeWidget.addParent(
geo_tool, _("Tool Data"), color=p_color, font=font)
for data_k, data_v in v.items():
self.treeWidget.addChild(tool_data, [str(data_k).capitalize(), str(data_v)], True)
# for cncjob objects made from excellon
for tool_dia, value in obj.exc_cnc_tools.items():
exc_tool = self.treeWidget.addParent(
tools, str(value['tool']), expanded=False, color=p_color, font=font
)
self.treeWidget.addChild(
exc_tool,
[
_('Diameter'),
'%.*f %s' % (self.decimals, tool_dia, self.app.defaults['units'].lower())
],
True
)
for k, v in value.items():
if k == 'solid_geometry':
printed_value = _('Present') if v else _('None')
self.treeWidget.addChild(exc_tool, [_("Solid Geometry"), printed_value], True)
elif k == 'nr_drills':
self.treeWidget.addChild(exc_tool, [_("Drills number"), str(v)], True)
elif k == 'nr_slots':
self.treeWidget.addChild(exc_tool, [_("Slots number"), str(v)], True)
elif k == 'gcode':
printed_value = _('Present') if v != '' else _('None')
self.treeWidget.addChild(exc_tool, [_("GCode Text"), printed_value], True)
elif k == 'gcode_parsed':
printed_value = _('Present') if v else _('None')
self.treeWidget.addChild(exc_tool, [_("GCode Geometry"), printed_value], True)
else:
pass
self.treeWidget.addChild(
exc_tool,
[
_("Depth of Cut"),
'%.*f %s' % (
self.decimals,
(obj.z_cut - abs(value['data']['tools_drill_offset'])),
self.app.defaults['units'].lower()
)
],
True
)
self.treeWidget.addChild(
exc_tool,
[
_("Clearance Height"),
'%.*f %s' % (
self.decimals,
obj.z_move,
self.app.defaults['units'].lower()
)
],
True
)
self.treeWidget.addChild(
exc_tool,
[
_("Feedrate"),
'%.*f %s/min' % (
self.decimals,
obj.feedrate,
self.app.defaults['units'].lower()
)
],
True
)
v = value['data']
tool_data = self.treeWidget.addParent(
exc_tool, _("Tool Data"), color=p_color, font=font)
for data_k, data_v in v.items():
self.treeWidget.addChild(tool_data, [str(data_k).capitalize(), str(data_v)], True)
r_time = obj.routing_time
if r_time > 1:
units_lbl = 'min'
else:
r_time *= 60
units_lbl = 'sec'
r_time = math.ceil(float(r_time))
self.treeWidget.addChild(
others,
[
'%s:' % _('Routing time'),
'%.*f %s' % (self.decimals, r_time, units_lbl)],
True
)
self.treeWidget.addChild(
others,
[
'%s:' % _('Travelled distance'),
'%.*f %s' % (self.decimals, obj.travel_distance, self.app.defaults['units'].lower())
],
True
)
# treeWidget.addChild(separator, [''])
def update_area_chull(self, area, length, width, chull_area, copper_area, location):
# add dimensions
self.treeWidget.addChild(
location,
['%s:' % _('Length'), '%.*f %s' % (self.decimals, length, self.app.defaults['units'].lower())],
True
)
self.treeWidget.addChild(
location,
['%s:' % _('Width'), '%.*f %s' % (self.decimals, width, self.app.defaults['units'].lower())],
True
)
# add box area
if self.app.defaults['units'].lower() == 'mm':
self.treeWidget.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'cm2')], True)
self.treeWidget.addChild(
location,
['%s:' % _('Convex_Hull Area'), '%.*f %s' % (self.decimals, chull_area, 'cm2')],
True
)
else:
self.treeWidget.addChild(location, ['%s:' % _('Box Area'), '%.*f %s' % (self.decimals, area, 'in2')], True)
self.treeWidget.addChild(
location,
['%s:' % _('Convex_Hull Area'), '%.*f %s' % (self.decimals, chull_area, 'in2')],
True
)
# add copper area
if self.app.defaults['units'].lower() == 'mm':
self.treeWidget.addChild(
location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'cm2')], True)
else:
self.treeWidget.addChild(
location, ['%s:' % _('Copper Area'), '%.*f %s' % (self.decimals, copper_area, 'in2')], True)
@staticmethod
def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors]
@ -471,8 +920,8 @@ class FlatCAMObj(QtCore.QObject):
current_visibility = self.shapes.visible
# self.shapes.visible = value # maybe this is slower in VisPy? use enabled property?
def task(current_visibility):
if current_visibility is True:
def task(visibility):
if visibility is True:
if value is False:
self.shapes.visible = False
else:
@ -517,4 +966,4 @@ class FlatCAMObj(QtCore.QObject):
del self.options
# Set flag
self.deleted = True
self.deleted = True

View File

@ -971,7 +971,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
except Exception as e:
log.debug("Nothing to remove. %s" % str(e))
self.app.setup_component_editor()
self.app.setup_default_properties_tab()
return
if obj:

View File

@ -374,7 +374,7 @@ class Gerber(Geometry):
geo_f = None
# Polygons are stored here until there is a change in polarity.
# Only then they are combined via cascaded_union and added or
# Only then they are combined via unary_union and added or
# subtracted from solid_geometry. This is ~100 times faster than
# applying a union for every new polygon.
poly_buffer = []
@ -1680,7 +1680,7 @@ class Gerber(Geometry):
#
# self.do_flashes()
#
# self.solid_geometry = cascaded_union(self.buffered_paths +
# self.solid_geometry = unary_union(self.buffered_paths +
# [poly['polygon'] for poly in self.regions] +
# self.flash_geometry)

View File

@ -228,8 +228,10 @@ def svgrect2shapely(rect, n_points=32, factor=1.0):
else:
y = 0
rxstr = rect.get('rx') * factor
rystr = rect.get('ry') * factor
rxstr = rect.get('rx')
rxstr = rxstr * factor if rxstr else rxstr
rystr = rect.get('ry')
rystr = rystr * factor if rystr else rystr
if rxstr is None and rystr is None: # Sharp corners
pts = [
@ -290,9 +292,12 @@ def svgcircle2shapely(circle, n_points=64, factor=1.0):
# cx = float(circle.get('cx'))
# cy = float(circle.get('cy'))
# r = float(circle.get('r'))
cx = svgparselength(circle.get('cx'))[0] * factor # TODO: No units support yet
cy = svgparselength(circle.get('cy'))[0] * factor # TODO: No units support yet
r = svgparselength(circle.get('r'))[0] * factor # TODO: No units support yet
cx = svgparselength(circle.get('cx'))[0] # TODO: No units support yet
cx = cx * factor if cx else cx
cy = svgparselength(circle.get('cy'))[0] # TODO: No units support yet
cy = cy * factor if cy else cy
r = svgparselength(circle.get('r'))[0] # TODO: No units support yet
r = r * factor if r else r
return Point(cx, cy).buffer(r, resolution=n_points)
@ -309,11 +314,15 @@ def svgellipse2shapely(ellipse, n_points=64, factor=1.0):
:rtype: shapely.geometry.polygon.LinearRing
"""
cx = svgparselength(ellipse.get('cx'))[0] * factor # TODO: No units support yet
cy = svgparselength(ellipse.get('cy'))[0] * factor # TODO: No units support yet
cx = svgparselength(ellipse.get('cx'))[0] # TODO: No units support yet
cx = cx * factor if cx else cx
cy = svgparselength(ellipse.get('cy'))[0] # TODO: No units support yet
cy = cy * factor if cy else cy
rx = svgparselength(ellipse.get('rx'))[0] * factor # TODO: No units support yet
ry = svgparselength(ellipse.get('ry'))[0] * factor # TODO: No units support yet
rx = svgparselength(ellipse.get('rx'))[0] # TODO: No units support yet
rx = rx * factor if rx else rx
ry = svgparselength(ellipse.get('ry'))[0] # TODO: No units support yet
ry = ry * factor if ry else ry
t = np.arange(n_points, dtype=float) / n_points
x = cx + rx * np.cos(2 * np.pi * t)

View File

@ -12,7 +12,7 @@ from appTool import AppTool
from appGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox
import shapely.geometry.base as base
from shapely.ops import cascaded_union, unary_union
from shapely.ops import unary_union
from shapely.geometry import Polygon, MultiPolygon, Point, LineString
from shapely.geometry import box as box
import shapely.affinity as affinity
@ -428,7 +428,7 @@ class ToolCopperThieving(AppTool):
if len(self.sel_rect) == 0:
return
self.sel_rect = cascaded_union(self.sel_rect)
self.sel_rect = unary_union(self.sel_rect)
if not isinstance(self.sel_rect, Iterable):
self.sel_rect = [self.sel_rect]
@ -606,9 +606,9 @@ class ToolCopperThieving(AppTool):
env_obj = geo_n.convex_hull
elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
(isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
else:
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
env_obj = env_obj.convex_hull
bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
else:
@ -660,10 +660,10 @@ class ToolCopperThieving(AppTool):
raise grace
geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
bounding_box = cascaded_union(geo_buff_list)
bounding_box = unary_union(geo_buff_list)
elif working_obj.kind == 'gerber':
geo_n = cascaded_union(geo_n).convex_hull
bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
geo_n = unary_union(geo_n).convex_hull
bounding_box = unary_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
else:
app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))

View File

@ -11,7 +11,7 @@ from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
FCLabel
from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing, MultiLineString
from shapely.ops import cascaded_union, unary_union, linemerge
from shapely.ops import unary_union, linemerge
import shapely.affinity as affinity
from matplotlib.backend_bases import KeyEvent as mpl_key_event
@ -217,6 +217,7 @@ class CutOut(AppTool):
"toolchangez": float(self.app.defaults["geometry_toolchangez"]),
"startz": self.app.defaults["geometry_startz"],
"endz": float(self.app.defaults["geometry_endz"]),
"endxy": self.app.defaults["geometry_endxy"],
"area_exclusion": self.app.defaults["geometry_area_exclusion"],
"area_shape": self.app.defaults["geometry_area_shape"],
"area_strategy": self.app.defaults["geometry_area_strategy"],
@ -414,6 +415,7 @@ class CutOut(AppTool):
"toolchangez": float(self.app.defaults["geometry_toolchangez"]),
"startz": self.app.defaults["geometry_startz"],
"endz": float(self.app.defaults["geometry_endz"]),
"endxy": self.app.defaults["geometry_endxy"],
"area_exclusion": self.app.defaults["geometry_area_exclusion"],
"area_shape": self.app.defaults["geometry_area_shape"],
"area_strategy": self.app.defaults["geometry_area_strategy"],
@ -1832,7 +1834,7 @@ class CutOut(AppTool):
log.debug("%d paths" % len(flat_geometry))
polygon = Polygon(points)
toolgeo = cascaded_union(polygon)
toolgeo = unary_union(polygon)
diffs = []
for target in flat_geometry:
if type(target) == LineString or type(target) == LinearRing:
@ -1906,7 +1908,7 @@ class CutOut(AppTool):
:param target_geo: geometry from which to subtract
:param subtractor: a list of Points, a LinearRing or a Polygon that will be subtracted from target_geo
:return: a cascaded union of the resulting geometry
:return: a unary_union of the resulting geometry
"""
if target_geo is None:
@ -2082,8 +2084,9 @@ class CutoutUI:
self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
self.addtool_from_db_btn.setToolTip(
_("Add a new tool to the Tool Table\n"
"from the Tool Database.\n"
"Tool database administration in Menu: Options -> Tools Database")
"from the Tools Database.\n"
"Tools database administration in in:\n"
"Menu: Options -> Tools Database")
)
hlay.addWidget(self.addtool_from_db_btn)

View File

@ -11,7 +11,7 @@ from appGUI.GUIElements import FCEntry
from shapely.ops import nearest_points
from shapely.geometry import Point, MultiPolygon
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import math
import logging
@ -113,12 +113,12 @@ class DistanceMin(AppTool):
try:
selected_objs[0].solid_geometry = MultiPolygon(selected_objs[0].solid_geometry)
except Exception:
selected_objs[0].solid_geometry = cascaded_union(selected_objs[0].solid_geometry)
selected_objs[0].solid_geometry = unary_union(selected_objs[0].solid_geometry)
try:
selected_objs[1].solid_geometry = MultiPolygon(selected_objs[1].solid_geometry)
except Exception:
selected_objs[1].solid_geometry = cascaded_union(selected_objs[1].solid_geometry)
selected_objs[1].solid_geometry = unary_union(selected_objs[1].solid_geometry)
first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry)

View File

@ -9,11 +9,13 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from appTool import AppTool
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
OptionalHideInputSection, FCComboBox, FCFileSaveDialog, FCButton, FCLabel
OptionalHideInputSection, FCComboBox, FCFileSaveDialog, FCButton, FCLabel, FCSpinner
from copy import deepcopy
import logging
from shapely.geometry import Polygon, MultiPolygon, Point
import shapely.affinity as affinity
from shapely.ops import unary_union
from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas
@ -138,6 +140,8 @@ class Film(AppTool):
self.ui.orientation_radio.set_value(self.app.defaults["tools_film_orientation"])
self.ui.pagesize_combo.set_value(self.app.defaults["tools_film_pagesize"])
self.ui.png_dpi_spinner.set_value(self.app.defaults["tools_film_png_dpi"])
self.ui.tf_type_obj_combo.set_value('grb')
self.ui.tf_type_box_combo.set_value('grb')
# run once to update the obj_type attribute in the FCCombobox so the last object is showed in cb
@ -187,8 +191,8 @@ class Film(AppTool):
def generate_positive_normal_film(self, name, boxname, factor, ftype='svg'):
log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
scale_factor_x = None
scale_factor_y = None
scale_factor_x = 1
scale_factor_y = 1
skew_factor_x = None
skew_factor_y = None
mirror = None
@ -328,8 +332,8 @@ class Film(AppTool):
def generate_negative_film(self, name, boxname, factor, ftype='svg'):
log.debug("ToolFilm.Film.generate_negative_film() started ...")
scale_factor_x = None
scale_factor_y = None
scale_factor_x = 1
scale_factor_y = 1
skew_factor_x = None
skew_factor_y = None
mirror = None
@ -351,7 +355,7 @@ class Film(AppTool):
if self.ui.film_mirror_axis.get_value() != 'none':
mirror = self.ui.film_mirror_axis.get_value()
border = float(self.ui.boundary_entry.get_value())
border = self.ui.boundary_entry.get_value()
if border is None:
border = 0
@ -390,7 +394,7 @@ class Film(AppTool):
def export_negative(self, obj_name, box_name, filename, boundary,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
scale_factor_x=1, scale_factor_y=1,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None,
use_thread=True, ftype='svg'):
@ -434,17 +438,86 @@ class Film(AppTool):
self.app.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
box = obj
def make_negative_film():
scale_factor_x = scale_factor_x
scale_factor_y = scale_factor_y
def make_negative_film(scale_factor_x, scale_factor_y):
log.debug("FilmTool.export_negative().make_negative_film()")
scale_reference = 'center'
default_dpi = 96
new_png_dpi = self.ui.png_dpi_spinner.get_value()
dpi_rate = new_png_dpi / default_dpi
# Determine bounding area for svg export
bounds = box.bounds()
tr_scale_reference = (bounds[0], bounds[1])
if dpi_rate != 1 and ftype == 'png':
scale_factor_x += dpi_rate
scale_factor_y += dpi_rate
scale_reference = (bounds[0], bounds[1])
if box.kind.lower() == 'geometry':
flat_geo = []
if box.multigeo:
for tool in box.tools:
flat_geo += box.flatten(box.tools[tool]['solid_geometry'])
box_geo = unary_union(flat_geo)
else:
box_geo = unary_union(box.flatten())
else:
box_geo = unary_union(box.flatten())
skew_ref = 'center'
if skew_reference != 'center':
xmin, ymin, xmax, ymax = box_geo.bounds
if skew_reference == 'topleft':
skew_ref = (xmin, ymax)
elif skew_reference == 'bottomleft':
skew_ref = (xmin, ymin)
elif skew_reference == 'topright':
skew_ref = (xmax, ymax)
elif skew_reference == 'bottomright':
skew_ref = (xmax, ymin)
transformed_box_geo = box_geo
if scale_factor_x and not scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, scale_factor_x, 1.0,
origin=tr_scale_reference)
elif not scale_factor_x and scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, 1.0, scale_factor_y,
origin=tr_scale_reference)
elif scale_factor_x and scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, scale_factor_x, scale_factor_y,
origin=tr_scale_reference)
if skew_factor_x and not skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, skew_factor_x, 0.0, origin=skew_ref)
elif not skew_factor_x and skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, 0.0, skew_factor_y, origin=skew_ref)
elif skew_factor_x and skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, skew_factor_x, skew_factor_y, origin=skew_ref)
if mirror:
if mirror == 'x':
transformed_box_geo = affinity.scale(transformed_box_geo, 1.0, -1.0)
if mirror == 'y':
transformed_box_geo = affinity.scale(transformed_box_geo, -1.0, 1.0)
if mirror == 'both':
transformed_box_geo = affinity.scale(transformed_box_geo, -1.0, -1.0)
bounds = transformed_box_geo.bounds
size = bounds[2] - bounds[0], bounds[3] - bounds[1]
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
mirror=mirror,
scale_reference=scale_reference, skew_reference=skew_reference
)
# 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
@ -514,6 +587,11 @@ class Film(AppTool):
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
renderPM.drawToFile(drawing, filename, 'PNG')
# if new_png_dpi == default_dpi:
# renderPM.drawToFile(drawing, filename, 'PNG')
# else:
# renderPM.drawToFile(drawing, filename, 'PNG', dpi=new_png_dpi)
except Exception as e:
log.debug("FilmTool.export_negative() --> PNG output --> %s" % str(e))
return 'fail'
@ -528,8 +606,7 @@ class Film(AppTool):
drawing = svg2rlg(doc_final)
p_size = self.ui.pagesize_combo.get_value()
if p_size == 'Bounds':
renderPDF.drawToFile(drawing, filename)
if p_size == 'Bounds': renderPDF.drawToFile(drawing, filename)
else:
if self.ui.orientation_radio.get_value() == 'p':
page_size = portrait(self.ui.pagesize[p_size])
@ -550,23 +627,21 @@ class Film(AppTool):
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():
with self.app.proc_container.new(_("Working...")):
try:
make_negative_film(scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y)
except Exception as e:
log.debug("export_negative() process -> %s" % str(e))
return
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]})
self.app.worker_task.emit({'fcn': job_thread_film, 'params': []})
else:
make_negative_film()
make_negative_film(scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y)
def export_positive(self, obj_name, box_name, filename,
scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
scale_factor_x=1, scale_factor_y=1,
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
mirror=None, orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
use_thread=True, ftype='svg'):
@ -615,18 +690,89 @@ class Film(AppTool):
self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
box = obj
scale_factor_x = scale_factor_x
scale_factor_y = scale_factor_y
p_size = pagesize_val
orientation = orientation_val
color = color_val
transparency_level = opacity_val
def make_positive_film(p_size, orientation, color, transparency_level):
def make_positive_film(p_size, orientation, color, transparency_level, scale_factor_x, scale_factor_y):
log.debug("FilmTool.export_positive().make_positive_film()")
scale_reference = 'center'
default_dpi = 96
new_png_dpi = self.ui.png_dpi_spinner.get_value()
dpi_rate = new_png_dpi / default_dpi
# Determine bounding area for svg export
bounds = box.bounds()
tr_scale_reference = (bounds[0], bounds[1])
if dpi_rate != 1 and ftype == 'png':
scale_factor_x += dpi_rate
scale_factor_y += dpi_rate
scale_reference = (bounds[0], bounds[1])
if box.kind.lower() == 'geometry':
flat_geo = []
if box.multigeo:
for tool in box.tools:
flat_geo += box.flatten(box.tools[tool]['solid_geometry'])
box_geo = unary_union(flat_geo)
else:
box_geo = unary_union(box.flatten())
else:
box_geo = unary_union(box.flatten())
skew_ref = 'center'
if skew_reference != 'center':
xmin, ymin, xmax, ymax = box_geo.bounds
if skew_reference == 'topleft':
skew_ref = (xmin, ymax)
elif skew_reference == 'bottomleft':
skew_ref = (xmin, ymin)
elif skew_reference == 'topright':
skew_ref = (xmax, ymax)
elif skew_reference == 'bottomright':
skew_ref = (xmax, ymin)
transformed_box_geo = box_geo
if scale_factor_x and not scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, scale_factor_x, 1.0,
origin=tr_scale_reference)
elif not scale_factor_x and scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, 1.0, scale_factor_y,
origin=tr_scale_reference)
elif scale_factor_x and scale_factor_y:
transformed_box_geo = affinity.scale(transformed_box_geo, scale_factor_x, scale_factor_y,
origin=tr_scale_reference)
if skew_factor_x and not skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, skew_factor_x, 0.0, origin=skew_ref)
elif not skew_factor_x and skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, 0.0, skew_factor_y, origin=skew_ref)
elif skew_factor_x and skew_factor_y:
transformed_box_geo = affinity.skew(transformed_box_geo, skew_factor_x, skew_factor_y, origin=skew_ref)
if mirror:
if mirror == 'x':
transformed_box_geo = affinity.scale(transformed_box_geo, 1.0, -1.0)
if mirror == 'y':
transformed_box_geo = affinity.scale(transformed_box_geo, -1.0, 1.0)
if mirror == 'both':
transformed_box_geo = affinity.scale(transformed_box_geo, -1.0, -1.0)
bounds = transformed_box_geo.bounds
size = bounds[2] - bounds[0], bounds[3] - bounds[1]
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
mirror=mirror,
scale_reference=scale_reference, skew_reference=skew_reference
)
# Change the attributes of the exported SVG
@ -641,10 +787,6 @@ class Film(AppTool):
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()
@ -693,6 +835,11 @@ class Film(AppTool):
doc_final = StringIO(doc_final)
drawing = svg2rlg(doc_final)
renderPM.drawToFile(drawing, filename, 'PNG')
# if new_png_dpi == default_dpi:
# renderPM.drawToFile(drawing, filename, 'PNG')
# else:
# renderPM.drawToFile(drawing, filename, 'PNG', dpi=new_png_dpi)
except Exception as e:
log.debug("FilmTool.export_positive() --> PNG output --> %s" % str(e))
return 'fail'
@ -710,9 +857,9 @@ class Film(AppTool):
renderPDF.drawToFile(drawing, filename)
else:
if orientation == 'p':
page_size = portrait(self.pagesize[p_size])
page_size = portrait(self.ui.pagesize[p_size])
else:
page_size = landscape(self.pagesize[p_size])
page_size = landscape(self.ui.pagesize[p_size])
my_canvas = canvas.Canvas(filename, pagesize=page_size)
my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
@ -728,21 +875,21 @@ class Film(AppTool):
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():
try:
make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
except Exception:
proc.done()
return
proc.done()
with self.app.proc_container.new(_("Working...")):
try:
make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y)
except Exception as e:
log.debug("export_positive() process -> %s" % str(e))
return
self.app.worker_task.emit({'fcn': job_thread_film, 'params': []})
else:
make_positive_film(p_size=p_size, orientation=orientation, color=color,
transparency_level=transparency_level)
transparency_level=transparency_level,
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y)
def reset_fields(self):
self.ui.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@ -1199,6 +1346,20 @@ class FilmUI:
self.on_film_type(val='hide')
# PNG DPI
self.png_dpi_label = FCLabel('%s:' % "PNG DPI")
self.png_dpi_label.setToolTip(
_("Default value is 96 DPI. Change this value to scale the PNG file.")
)
self.png_dpi_spinner = FCSpinner(callback=self.confirmation_message_int)
self.png_dpi_spinner.set_range(0, 100000)
grid1.addWidget(self.png_dpi_label, 4, 0)
grid1.addWidget(self.png_dpi_spinner, 4, 1)
self.png_dpi_label.hide()
self.png_dpi_spinner.hide()
# Buttons
self.film_object_button = FCButton(_("Save Film"))
self.film_object_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
@ -1214,7 +1375,7 @@ class FilmUI:
font-weight: bold;
}
""")
grid1.addWidget(self.film_object_button, 4, 0, 1, 2)
grid1.addWidget(self.film_object_button, 6, 0, 1, 2)
self.layout.addStretch()
@ -1254,11 +1415,22 @@ class FilmUI:
self.orientation_radio.show()
self.pagesize_label.show()
self.pagesize_combo.show()
self.png_dpi_label.hide()
self.png_dpi_spinner.hide()
elif val == 'png':
self.png_dpi_label.show()
self.png_dpi_spinner.show()
self.orientation_label.hide()
self.orientation_radio.hide()
self.pagesize_label.hide()
self.pagesize_combo.hide()
else:
self.orientation_label.hide()
self.orientation_radio.hide()
self.pagesize_label.hide()
self.pagesize_combo.hide()
self.png_dpi_label.hide()
self.png_dpi_spinner.hide()
def on_punch_source(self, val):
if val == 'pad' and self.punch_cb.get_value():

View File

@ -19,7 +19,7 @@ import numpy as np
import simplejson as json
import sys
from shapely.ops import cascaded_union, nearest_points
from shapely.ops import unary_union, nearest_points
from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing, Point
from matplotlib.backend_bases import KeyEvent as mpl_key_event
@ -119,6 +119,8 @@ class ToolIsolation(AppTool, Gerber):
self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
self.tooldia = None
# store here the tool diameter that is guaranteed to isolate the object
self.safe_tooldia = None
# multiprocessing
self.pool = self.app.pool
@ -225,6 +227,9 @@ class ToolIsolation(AppTool, Gerber):
def set_tool_ui(self):
self.units = self.app.defaults['units'].upper()
# reset the value to prepare for another isolation
self.safe_tooldia = None
# try to select in the Gerber combobox the active object
try:
selected_obj = self.app.collection.get_active()
@ -313,6 +318,7 @@ class ToolIsolation(AppTool, Gerber):
self.ui.iso_overlap_entry.set_value(self.app.defaults["tools_iso_overlap"])
self.ui.milling_type_radio.set_value(self.app.defaults["tools_iso_milling_type"])
self.ui.combine_passes_cb.set_value(self.app.defaults["tools_iso_combine_passes"])
self.ui.valid_cb.set_value(self.app.defaults["tools_iso_check_valid"])
self.ui.area_shape_radio.set_value(self.app.defaults["tools_iso_area_shape"])
self.ui.poly_int_cb.set_value(self.app.defaults["tools_iso_poly_ints"])
self.ui.forced_rest_iso_cb.set_value(self.app.defaults["tools_iso_force"])
@ -888,6 +894,9 @@ class ToolIsolation(AppTool, Gerber):
})
def on_find_optimal_tooldia(self):
self.find_safe_tooldia_worker(is_displayed=True)
def find_safe_tooldia_worker(self, is_displayed):
self.units = self.app.defaults['units'].upper()
obj_name = self.ui.object_combo.currentText()
@ -903,85 +912,111 @@ class ToolIsolation(AppTool, Gerber):
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
return
proc = self.app.proc_container.new(_("Working..."))
def job_thread(app_obj, is_display):
with self.app.proc_container.new(_("Working...")) as proc:
try:
old_disp_number = 0
pol_nr = 0
app_obj.proc_container.update_view_text(' %d%%' % 0)
total_geo = []
def job_thread(app_obj):
try:
old_disp_number = 0
pol_nr = 0
app_obj.proc_container.update_view_text(' %d%%' % 0)
total_geo = []
for ap in list(fcobj.apertures.keys()):
if 'geometry' in fcobj.apertures[ap]:
for geo_el in fcobj.apertures[ap]['geometry']:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
for ap in list(fcobj.apertures.keys()):
if 'geometry' in fcobj.apertures[ap]:
for geo_el in fcobj.apertures[ap]['geometry']:
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
total_geo.append(geo_el['solid'])
total_geo = MultiPolygon(total_geo)
total_geo = total_geo.buffer(0)
try:
__ = iter(total_geo)
geo_len = len(total_geo)
geo_len = (geo_len * (geo_len - 1)) / 2
except TypeError:
msg = _("The Gerber object has one Polygon as geometry.\n"
"There are no distances between geometry elements to be found.")
app_obj.inform.emit('[ERROR_NOTCL] %s' % msg)
return 'fail'
min_dict = {}
idx = 1
for geo in total_geo:
for s_geo in total_geo[idx:]:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
total_geo.append(geo_el['solid'])
# minimize the number of distances by not taking into considerations those
# that are too small
dist = geo.distance(s_geo)
dist = float('%.*f' % (self.decimals, dist))
loc_1, loc_2 = nearest_points(geo, s_geo)
total_geo = MultiPolygon(total_geo)
total_geo = total_geo.buffer(0)
proc_loc = (
(float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
(float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
)
try:
__ = iter(total_geo)
geo_len = len(total_geo)
geo_len = (geo_len * (geo_len - 1)) / 2
except TypeError:
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("The Gerber object has one Polygon as geometry.\n"
"There are no distances between geometry elements to be found."))
return 'fail'
if dist in min_dict:
min_dict[dist].append(proc_loc)
else:
min_dict[dist] = [proc_loc]
min_dict = {}
idx = 1
for geo in total_geo:
for s_geo in total_geo[idx:]:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
# minimize the number of distances by not taking into considerations those that are too small
dist = geo.distance(s_geo)
dist = float('%.*f' % (self.decimals, dist))
loc_1, loc_2 = nearest_points(geo, s_geo)
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
idx += 1
proc_loc = (
(float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
(float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
)
min_list = list(min_dict.keys())
min_dist = min(min_list)
if dist in min_dict:
min_dict[dist].append(proc_loc)
else:
min_dict[dist] = [proc_loc]
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
self.safe_tooldia = min_dist_truncated
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if is_display:
self.optimal_found_sig.emit(min_dist_truncated)
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
idx += 1
app_obj.inform.emit('[success] %s: %s %s' %
(_("Optimal tool diameter found"), str(min_dist_truncated),
self.units.lower()))
else:
if self.safe_tooldia:
# find the selected tool ID's
sorted_tools = []
table_items = self.ui.tools_table.selectedItems()
sel_rows = {t.row() for t in table_items}
for row in sel_rows:
tid = int(self.ui.tools_table.item(row, 3).text())
sorted_tools.append(tid)
if not sorted_tools:
msg = _("There are no tools selected in the Tool Table.")
self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
return 'fail'
min_list = list(min_dict.keys())
min_dist = min(min_list)
# check if the tools diameters are less then the safe tool diameter
for tool in sorted_tools:
tool_dia = float(self.iso_tools[tool]['tooldia'])
if tool_dia > self.safe_tooldia:
msg = _("Incomplete isolation. "
"At least one tool could not do a complete isolation.")
self.app.inform.emit('[WARNING] %s' % msg)
break
min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
# reset the value to prepare for another isolation
self.safe_tooldia = None
except Exception as ee:
log.debug(str(ee))
return
self.optimal_found_sig.emit(min_dist_truncated)
app_obj.inform.emit('[success] %s: %s %s' %
(_("Optimal tool diameter found"), str(min_dist_truncated), self.units.lower()))
except Exception as ee:
proc.done()
log.debug(str(ee))
return
proc.done()
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, is_displayed]})
def on_tool_add(self, custom_dia=None):
self.blockSignals(True)
@ -1333,14 +1368,17 @@ class ToolIsolation(AppTool, Gerber):
# Get source object.
try:
self.grb_obj = self.app.collection.get_by_name(self.obj_name)
except Exception as e:
except Exception:
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
return
if self.grb_obj is None:
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
return
if self.ui.valid_cb.get_value() is True:
self.find_safe_tooldia_worker(is_displayed=False)
def worker_task(iso_obj):
with self.app.proc_container.new(_("Isolating...")):
self.isolate_handler(iso_obj)
@ -1436,8 +1474,8 @@ class ToolIsolation(AppTool, Gerber):
elif selection == _("Reference Object"):
ref_obj = self.app.collection.get_by_name(self.ui.reference_combo.get_value())
ref_geo = cascaded_union(ref_obj.solid_geometry)
use_geo = cascaded_union(isolated_obj.solid_geometry).difference(ref_geo)
ref_geo = unary_union(ref_obj.solid_geometry)
use_geo = unary_union(isolated_obj.solid_geometry).difference(ref_geo)
self.isolate(isolated_obj=isolated_obj, geometry=use_geo)
def isolate(self, isolated_obj, geometry=None, limited_area=None, negative_dia=None, plot=True):
@ -1467,7 +1505,7 @@ class ToolIsolation(AppTool, Gerber):
tid = int(self.ui.tools_table.item(row, 3).text())
sorted_tools.append(tid)
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return 'fail'
# update the Common Parameters values in the self.iso_tools
@ -1669,7 +1707,7 @@ class ToolIsolation(AppTool, Gerber):
sorted_tools.append(float('%.*f' % (self.decimals, tdia)))
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return 'fail'
order = self.ui.order_radio.get_value()
@ -1856,7 +1894,7 @@ class ToolIsolation(AppTool, Gerber):
tid = int(self.ui.tools_table.item(row, 3).text())
sorted_tools.append(tid)
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return 'fail'
for tool in sorted_tools:
@ -2010,11 +2048,11 @@ class ToolIsolation(AppTool, Gerber):
target_geo = geo
if subtraction_geo:
sub_union = cascaded_union(subtraction_geo)
sub_union = unary_union(subtraction_geo)
else:
name = self.ui.exc_obj_combo.currentText()
subtractor_obj = self.app.collection.get_by_name(name)
sub_union = cascaded_union(subtractor_obj.solid_geometry)
sub_union = unary_union(subtractor_obj.solid_geometry)
try:
for geo_elem in target_geo:
@ -2068,7 +2106,7 @@ class ToolIsolation(AppTool, Gerber):
new_geometry = []
target_geo = geo
intersect_union = cascaded_union(intersection_geo)
intersect_union = unary_union(intersection_geo)
try:
for geo_elem in target_geo:
@ -2245,7 +2283,7 @@ class ToolIsolation(AppTool, Gerber):
except TypeError:
if self.solid_geometry not in self.poly_dict.values():
if sel_type is True:
if self.solid_geometry.within(poly_selection):
if poly_selection.contains(self.solid_geometry):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
@ -2389,7 +2427,7 @@ class ToolIsolation(AppTool, Gerber):
if len(self.sel_rect) == 0:
return
self.sel_rect = cascaded_union(self.sel_rect)
self.sel_rect = unary_union(self.sel_rect)
self.isolate(isolated_obj=self.grb_obj, limited_area=self.sel_rect, plot=True)
self.sel_rect = []
@ -3096,8 +3134,9 @@ class IsoUI:
self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
self.addtool_from_db_btn.setToolTip(
_("Add a new tool to the Tool Table\n"
"from the Tool Database.\n"
"Tool database administration in Menu: Options -> Tools Database")
"from the Tools Database.\n"
"Tools database administration in in:\n"
"Menu: Options -> Tools Database")
)
bhlay.addWidget(self.addtool_from_db_btn)
@ -3277,13 +3316,23 @@ class IsoUI:
self.grid3.addWidget(self.combine_passes_cb, 26, 0, 1, 2)
# Check Tool validity
self.valid_cb = FCCheckBox(label=_('Check validity'))
self.valid_cb.setToolTip(
_("If checked then the tools diameters are verified\n"
"if they will provide a complete isolation.")
)
self.valid_cb.setObjectName("i_check")
self.grid3.addWidget(self.valid_cb, 28, 0, 1, 2)
# Exception Areas
self.except_cb = FCCheckBox(label=_('Except'))
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
"by checking this, the area of the object below\n"
"will be subtracted from the isolation geometry."))
self.except_cb.setObjectName("i_except")
self.grid3.addWidget(self.except_cb, 27, 0)
self.grid3.addWidget(self.except_cb, 30, 0)
# Type of object to be excepted
self.type_excobj_radio = RadioSet([{'label': _("Geometry"), 'value': 'geometry'},
@ -3295,7 +3344,7 @@ class IsoUI:
"of objects that will populate the 'Object' combobox.")
)
self.grid3.addWidget(self.type_excobj_radio, 27, 1)
self.grid3.addWidget(self.type_excobj_radio, 30, 1)
# The object to be excepted
self.exc_obj_combo = FCComboBox()
@ -3307,7 +3356,7 @@ class IsoUI:
self.exc_obj_combo.is_last = True
self.exc_obj_combo.obj_type = "gerber"
self.grid3.addWidget(self.exc_obj_combo, 28, 0, 1, 2)
self.grid3.addWidget(self.exc_obj_combo, 32, 0, 1, 2)
self.e_ois = OptionalInputSection(self.except_cb,
[
@ -3330,8 +3379,8 @@ class IsoUI:
)
self.select_combo.setObjectName("i_selection")
self.grid3.addWidget(self.select_label, 33, 0)
self.grid3.addWidget(self.select_combo, 33, 1)
self.grid3.addWidget(self.select_label, 34, 0)
self.grid3.addWidget(self.select_combo, 34, 1)
self.reference_combo_type_label = FCLabel('%s:' % _("Ref. Type"))
self.reference_combo_type_label.setToolTip(
@ -3341,8 +3390,8 @@ class IsoUI:
self.reference_combo_type = FCComboBox()
self.reference_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
self.grid3.addWidget(self.reference_combo_type_label, 34, 0)
self.grid3.addWidget(self.reference_combo_type, 34, 1)
self.grid3.addWidget(self.reference_combo_type_label, 36, 0)
self.grid3.addWidget(self.reference_combo_type, 36, 1)
self.reference_combo_label = FCLabel('%s:' % _("Ref. Object"))
self.reference_combo_label.setToolTip(
@ -3353,8 +3402,8 @@ class IsoUI:
self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.reference_combo.is_last = True
self.grid3.addWidget(self.reference_combo_label, 35, 0)
self.grid3.addWidget(self.reference_combo, 35, 1)
self.grid3.addWidget(self.reference_combo_label, 38, 0)
self.grid3.addWidget(self.reference_combo, 38, 1)
self.reference_combo.hide()
self.reference_combo_label.hide()
@ -3368,7 +3417,7 @@ class IsoUI:
"(holes in the polygon).")
)
self.grid3.addWidget(self.poly_int_cb, 36, 0)
self.grid3.addWidget(self.poly_int_cb, 40, 0)
self.poly_int_cb.hide()
@ -3381,8 +3430,8 @@ class IsoUI:
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
self.grid3.addWidget(self.area_shape_label, 38, 0)
self.grid3.addWidget(self.area_shape_radio, 38, 1)
self.grid3.addWidget(self.area_shape_label, 42, 0)
self.grid3.addWidget(self.area_shape_radio, 42, 1)
self.area_shape_label.hide()
self.area_shape_radio.hide()
@ -3390,7 +3439,7 @@ class IsoUI:
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.grid3.addWidget(separator_line, 39, 0, 1, 2)
self.grid3.addWidget(separator_line, 44, 0, 1, 2)
self.generate_iso_button = FCButton("%s" % _("Generate Geometry"))
self.generate_iso_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png'))

View File

@ -17,7 +17,7 @@ from copy import deepcopy
# import numpy as np
# import math
# from shapely.ops import cascaded_union
# from shapely.ops import unary_union
from shapely.geometry import Point, LineString
from matplotlib.backend_bases import KeyEvent as mpl_key_event

View File

@ -18,7 +18,7 @@ from copy import deepcopy
import numpy as np
from shapely.geometry import base
from shapely.ops import cascaded_union, nearest_points
from shapely.ops import unary_union, nearest_points
from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing
from matplotlib.backend_bases import KeyEvent as mpl_key_event
@ -548,6 +548,7 @@ class NonCopperClear(AppTool, Gerber):
"area_shape": self.app.defaults["geometry_area_shape"],
"area_strategy": self.app.defaults["geometry_area_strategy"],
"area_overz": float(self.app.defaults["geometry_area_overz"]),
"optimization_type": self.app.defaults["geometry_optimization_type"],
"tools_nccoperation": self.app.defaults["tools_nccoperation"],
"tools_nccmargin": self.app.defaults["tools_nccmargin"],
@ -1292,7 +1293,7 @@ class NonCopperClear(AppTool, Gerber):
else:
self.ncc_dia_list.append(self.tooldia)
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return
self.o_name = '%s_ncc' % self.obj_name
@ -1460,7 +1461,7 @@ class NonCopperClear(AppTool, Gerber):
if len(self.sel_rect) == 0:
return
self.sel_rect = cascaded_union(self.sel_rect)
self.sel_rect = unary_union(self.sel_rect)
self.clear_copper(ncc_obj=self.ncc_obj, sel_obj=self.bound_obj, ncctooldia=self.ncc_dia_list,
isotooldia=self.iso_dia_list, outname=self.o_name)
@ -1622,16 +1623,16 @@ class NonCopperClear(AppTool, Gerber):
env_obj = geo_n.convex_hull
elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
(isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
else:
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
env_obj = env_obj.convex_hull
except Exception as e:
log.debug("NonCopperClear.calculate_bounding_box() 'itself' --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
return None
elif ncc_select == _("Area Selection"):
env_obj = cascaded_union(self.sel_rect)
env_obj = unary_union(self.sel_rect)
try:
__ = iter(env_obj)
except Exception:
@ -1649,8 +1650,8 @@ class NonCopperClear(AppTool, Gerber):
env_obj = [box_geo]
elif box_kind == 'gerber':
box_geo = cascaded_union(box_obj.solid_geometry).convex_hull
ncc_geo = cascaded_union(ncc_obj.solid_geometry).convex_hull
box_geo = unary_union(box_obj.solid_geometry).convex_hull
ncc_geo = unary_union(ncc_obj.solid_geometry).convex_hull
env_obj = ncc_geo.intersection(box_geo)
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
@ -1692,7 +1693,7 @@ class NonCopperClear(AppTool, Gerber):
# graceful abort requested by the user
raise grace
geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
new_bounding_box = cascaded_union(geo_buff_list)
new_bounding_box = unary_union(geo_buff_list)
elif ncc_select == _("Reference Object"):
if box_kind == 'geometry':
geo_buff_list = []
@ -1702,7 +1703,7 @@ class NonCopperClear(AppTool, Gerber):
raise grace
geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
new_bounding_box = cascaded_union(geo_buff_list)
new_bounding_box = unary_union(geo_buff_list)
elif box_kind == 'gerber':
new_bounding_box = bbox.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
else:
@ -1873,7 +1874,7 @@ class NonCopperClear(AppTool, Gerber):
geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
break
sol_geo = cascaded_union(isolated_geo)
sol_geo = unary_union(isolated_geo)
if has_offset is True:
self.app.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -1886,7 +1887,7 @@ class NonCopperClear(AppTool, Gerber):
return 'fail'
elif ncc_obj.kind == 'geometry':
sol_geo = cascaded_union(ncc_obj.solid_geometry)
sol_geo = unary_union(ncc_obj.solid_geometry)
if has_offset is True:
self.app.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -2430,7 +2431,7 @@ class NonCopperClear(AppTool, Gerber):
log.debug("There are no geometries in the cleared polygon.")
# Area to clear next
buffered_cleared = cascaded_union(cleared_geo).buffer(tool / 2.0)
buffered_cleared = unary_union(cleared_geo).buffer(tool / 2.0)
area = area.difference(buffered_cleared)
if not area or area.is_empty:
@ -2442,7 +2443,7 @@ class NonCopperClear(AppTool, Gerber):
# new_area = [p.buffer(buff_distance) for p in area if not p.is_empty]
# except TypeError:
# new_area = [area.buffer(tool * ncc_overlap)]
# area = cascaded_union(area)
# area = unary_union(area)
geo_obj.multigeo = True
geo_obj.options["cnctooldia"] = '0.0'
@ -2615,9 +2616,9 @@ class NonCopperClear(AppTool, Gerber):
env_obj = geo_n.convex_hull
elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
(isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
else:
env_obj = cascaded_union(geo_n)
env_obj = unary_union(geo_n)
env_obj = env_obj.convex_hull
bounding_box = env_obj.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
@ -2627,7 +2628,7 @@ class NonCopperClear(AppTool, Gerber):
return 'fail'
elif ncc_select == 'area':
geo_n = cascaded_union(self.sel_rect)
geo_n = unary_union(self.sel_rect)
try:
__ = iter(geo_n)
except Exception as e:
@ -2641,7 +2642,7 @@ class NonCopperClear(AppTool, Gerber):
raise grace
geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
bounding_box = cascaded_union(geo_buff_list)
bounding_box = unary_union(geo_buff_list)
elif ncc_select == _("Reference Object"):
geo_n = ncc_sel_obj.solid_geometry
@ -2659,10 +2660,10 @@ class NonCopperClear(AppTool, Gerber):
raise grace
geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
bounding_box = cascaded_union(geo_buff_list)
bounding_box = unary_union(geo_buff_list)
elif ncc_sel_obj.kind == 'gerber':
geo_n = cascaded_union(geo_n).convex_hull
bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n)
geo_n = unary_union(geo_n).convex_hull
bounding_box = unary_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n)
bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
@ -2837,7 +2838,7 @@ class NonCopperClear(AppTool, Gerber):
break
geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
sol_geo = cascaded_union(isolated_geo)
sol_geo = unary_union(isolated_geo)
if has_offset is True:
app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -2852,7 +2853,7 @@ class NonCopperClear(AppTool, Gerber):
return 'fail'
elif ncc_obj.kind == 'geometry':
sol_geo = cascaded_union(ncc_obj.solid_geometry)
sol_geo = unary_union(ncc_obj.solid_geometry)
if has_offset is True:
app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -3219,7 +3220,7 @@ class NonCopperClear(AppTool, Gerber):
break
geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
sol_geo = cascaded_union(isolated_geo)
sol_geo = unary_union(isolated_geo)
if has_offset is True:
app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -3234,7 +3235,7 @@ class NonCopperClear(AppTool, Gerber):
return 'fail'
elif ncc_obj.kind == 'geometry':
sol_geo = cascaded_union(ncc_obj.solid_geometry)
sol_geo = unary_union(ncc_obj.solid_geometry)
if has_offset is True:
app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
sol_geo = sol_geo.buffer(distance=ncc_offset)
@ -3857,26 +3858,31 @@ class NccUI:
# ### Tool Diameter ####
self.new_tooldia_lbl = FCLabel('%s:' % _('Tool Dia'))
self.new_tooldia_lbl.setToolTip(
_("Diameter for the new tool to add in the Tool Table.\n"
"If the tool is V-shape type then this value is automatically\n"
"calculated from the other parameters.")
_("Diameter for the new tool")
)
self.grid3.addWidget(self.new_tooldia_lbl, 2, 0)
new_tool_lay = QtWidgets.QHBoxLayout()
self.new_tooldia_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.new_tooldia_entry.set_precision(self.decimals)
self.new_tooldia_entry.set_range(0.000, 9999.9999)
self.new_tooldia_entry.setObjectName(_("Tool Dia"))
self.grid3.addWidget(self.new_tooldia_lbl, 2, 0)
self.grid3.addWidget(self.new_tooldia_entry, 2, 1)
new_tool_lay.addWidget(self.new_tooldia_entry)
# Find Optimal Tooldia
self.find_optimal_button = FCButton(_('Find Optimal'))
self.find_optimal_button = QtWidgets.QToolButton()
self.find_optimal_button.setText(_('Optimal'))
self.find_optimal_button.setIcon(QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'))
self.find_optimal_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.find_optimal_button.setToolTip(
_("Find a tool diameter that is guaranteed\n"
"to do a complete isolation.")
)
self.grid3.addWidget(self.find_optimal_button, 4, 0, 1, 2)
new_tool_lay.addWidget(self.find_optimal_button)
self.grid3.addLayout(new_tool_lay, 2, 1)
hlay = QtWidgets.QHBoxLayout()
@ -3895,8 +3901,9 @@ class NccUI:
self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
self.addtool_from_db_btn.setToolTip(
_("Add a new tool to the Tool Table\n"
"from the Tool Database.\n"
"Tool database administration in Menu: Options -> Tools Database")
"from the Tools Database.\n"
"Tools database administration in in:\n"
"Menu: Options -> Tools Database")
)
hlay.addWidget(self.addtool_from_db_btn)

View File

@ -17,7 +17,7 @@ from appGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDial
FCLabel
from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point
from shapely.ops import cascaded_union, unary_union, linemerge
from shapely.ops import unary_union, linemerge
from matplotlib.backend_bases import KeyEvent as mpl_key_event
@ -466,6 +466,7 @@ class ToolPaint(AppTool, Gerber):
"area_shape": self.app.defaults["geometry_area_shape"],
"area_strategy": self.app.defaults["geometry_area_strategy"],
"area_overz": float(self.app.defaults["geometry_area_overz"]),
"optimization_type": self.app.defaults["geometry_optimization_type"],
"tooldia": self.app.defaults["tools_painttooldia"],
"tools_paintoffset": self.app.defaults["tools_paintoffset"],
@ -1027,7 +1028,7 @@ class ToolPaint(AppTool, Gerber):
continue
self.tooldia_list.append(self.tooldia)
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return
self.select_method = self.ui.selectmethod_combo.get_value()
@ -1293,7 +1294,7 @@ class ToolPaint(AppTool, Gerber):
if len(self.sel_rect) == 0:
return
self.sel_rect = cascaded_union(self.sel_rect)
self.sel_rect = unary_union(self.sel_rect)
self.paint_poly_area(obj=self.paint_obj, tooldia=self.tooldia_list, sel_obj=self.sel_rect,
outname=self.o_name)
@ -1740,7 +1741,7 @@ class ToolPaint(AppTool, Gerber):
continue
sorted_tools.append(self.tooldia)
if not sorted_tools:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("There are no tools selected in the Tool Table."))
return 'fail'
# Initializes the new geometry object
@ -1880,7 +1881,7 @@ class ToolPaint(AppTool, Gerber):
geo_obj.tools.clear()
geo_obj.tools = dict(tools_storage)
geo_obj.solid_geometry = cascaded_union(final_solid_geometry)
geo_obj.solid_geometry = unary_union(final_solid_geometry)
try:
if isinstance(geo_obj.solid_geometry, list):
@ -1934,7 +1935,7 @@ class ToolPaint(AppTool, Gerber):
except TypeError:
poly_buf.append(buffered_pol)
poly_buf = cascaded_union(poly_buf)
poly_buf = unary_union(poly_buf)
if not poly_buf:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Margin parameter too big. Tool is not used"))
@ -2000,7 +2001,7 @@ class ToolPaint(AppTool, Gerber):
prog_plot=prog_plot)
geo_elems = list(geo_res.get_objects())
# See if the polygon was completely cleared
pp_cleared = cascaded_union(geo_elems).buffer(tool_dia / 2.0)
pp_cleared = unary_union(geo_elems).buffer(tool_dia / 2.0)
rest = pp.difference(pp_cleared)
if rest and not rest.is_empty:
try:
@ -2040,7 +2041,7 @@ class ToolPaint(AppTool, Gerber):
geo_elems = list(geo_res.get_objects())
# See if the polygon was completely cleared
pp_cleared = cascaded_union(geo_elems).buffer(tool_dia / 2.0)
pp_cleared = unary_union(geo_elems).buffer(tool_dia / 2.0)
rest = poly_buf.difference(pp_cleared)
if rest and not rest.is_empty:
try:
@ -2094,7 +2095,7 @@ class ToolPaint(AppTool, Gerber):
poly_buf = MultiPolygon(tmp)
if not poly_buf.is_valid:
poly_buf = cascaded_union(tmp)
poly_buf = unary_union(tmp)
if not poly_buf or poly_buf.is_empty or not poly_buf.is_valid:
log.debug("Rest geometry empty. Breaking.")
@ -2134,7 +2135,7 @@ class ToolPaint(AppTool, Gerber):
"Change the painting parameters and try again.")
)
return "fail"
geo_obj.solid_geometry = cascaded_union(final_solid_geometry)
geo_obj.solid_geometry = unary_union(final_solid_geometry)
else:
return 'fail'
try:
@ -2446,9 +2447,9 @@ class ToolPaint(AppTool, Gerber):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = unary_union(self.bound_obj.solid_geometry)
else:
env_obj = cascaded_union(self.bound_obj.solid_geometry)
env_obj = unary_union(self.bound_obj.solid_geometry)
env_obj = env_obj.convex_hull
sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
except Exception as e:
@ -2909,8 +2910,9 @@ class PaintUI:
self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
self.addtool_from_db_btn.setToolTip(
_("Add a new tool to the Tool Table\n"
"from the Tool Database.\n"
"Tool database administration in Menu: Options -> Tools Database")
"from the Tools Database.\n"
"Tools database administration in in:\n"
"Menu: Options -> Tools Database")
)
hlay.addWidget(self.addtool_from_db_btn)

View File

@ -589,8 +589,8 @@ class Panelize(AppTool):
obj_fin.source_file = self.app.export_dxf(obj_name=self.outname, filename=None,
local_use=obj_fin, use_thread=False)
# obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
# app_obj.log.debug("Finished creating a cascaded union for the panel.")
# obj_fin.solid_geometry = unary_union(obj_fin.solid_geometry)
# app_obj.log.debug("Finished creating a unary_union for the panel.")
app_obj.proc_container.update_view_text('')
self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))

View File

@ -10,7 +10,7 @@ from appTool import AppTool
from appGUI.GUIElements import FCTree
from shapely.geometry import MultiPolygon, Polygon
from shapely.ops import cascaded_union
from shapely.ops import unary_union
from copy import deepcopy
import math
@ -237,7 +237,7 @@ class Properties(AppTool):
if obj_prop.kind.lower() == 'cncjob':
try:
for tool_k in obj_prop.exc_cnc_tools:
x0, y0, x1, y1 = cascaded_union(obj_prop.exc_cnc_tools[tool_k]['solid_geometry']).bounds
x0, y0, x1, y1 = unary_union(obj_prop.exc_cnc_tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
@ -247,7 +247,7 @@ class Properties(AppTool):
try:
for tool_k in obj_prop.cnc_tools:
x0, y0, x1, y1 = cascaded_union(obj_prop.cnc_tools[tool_k]['solid_geometry']).bounds
x0, y0, x1, y1 = unary_union(obj_prop.cnc_tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
@ -257,7 +257,7 @@ class Properties(AppTool):
else:
try:
for tool_k in obj_prop.tools:
x0, y0, x1, y1 = cascaded_union(obj_prop.tools[tool_k]['solid_geometry']).bounds
x0, y0, x1, y1 = unary_union(obj_prop.tools[tool_k]['solid_geometry']).bounds
xmin.append(x0)
ymin.append(y0)
xmax.append(x1)
@ -308,10 +308,10 @@ class Properties(AppTool):
env_obj = geo.convex_hull
elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
(isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
env_obj = cascaded_union(geo)
env_obj = unary_union(geo)
env_obj = env_obj.convex_hull
else:
env_obj = cascaded_union(geo)
env_obj = unary_union(geo)
env_obj = env_obj.convex_hull
area_chull = env_obj.area
@ -321,7 +321,7 @@ class Properties(AppTool):
try:
area_chull = []
for tool_k in obj_prop.tools:
area_el = cascaded_union(obj_prop.tools[tool_k]['solid_geometry']).convex_hull
area_el = unary_union(obj_prop.tools[tool_k]['solid_geometry']).convex_hull
area_chull.append(area_el.area)
area_chull = max(area_chull)
except Exception as er:

View File

@ -430,7 +430,6 @@ class QRCode(AppTool):
units = self.app.defaults['units'] if units is None else units
res = self.app.defaults['geometry_circle_steps']
factor = svgparse_viewbox(svg_root)
geos = getsvggeo(svg_root, object_type, units=units, res=res, factor=factor)
if flip:

View File

@ -19,7 +19,7 @@ from copy import deepcopy
from datetime import datetime
from shapely.geometry import Polygon, LineString
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import traceback
from io import StringIO
@ -941,7 +941,7 @@ class SolderPaste(AppTool):
tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
# TODO this serve for bounding box creation only; should be optimized
tool_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
tool_cnc_dict['solid_geometry'] = unary_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
# tell gcode_parse from which point to start drawing the lines depending on what kind of
# object is the source of gcode

View File

@ -11,7 +11,7 @@ from appTool import AppTool
from appGUI.GUIElements import FCCheckBox, FCButton, FCComboBox
from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import traceback
from copy import deepcopy
@ -396,7 +396,7 @@ class ToolSub(AppTool):
else:
self.promises.append("single")
self.sub_union = cascaded_union(self.sub_geo_obj.solid_geometry)
self.sub_union = unary_union(self.sub_geo_obj.solid_geometry)
# start the QTimer to check for promises with 0.5 second period check
self.periodic_check(500, reset=True)
@ -421,7 +421,7 @@ class ToolSub(AppTool):
with self.app.proc_container.new(text):
# resulting paths are closed resulting into Polygons
if self.ui.close_paths_cb.isChecked():
new_geo = (cascaded_union(geo)).difference(self.sub_union)
new_geo = (unary_union(geo)).difference(self.sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)

View File

@ -427,7 +427,7 @@ class App(QtCore.QObject):
current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig")
if user_defaults:
self.defaults.load(filename=current_defaults_path)
self.defaults.load(filename=current_defaults_path, inform=self.inform)
if self.defaults['units'] == 'MM':
self.decimals = int(self.defaults['decimals_metric'])
@ -1092,7 +1092,7 @@ class App(QtCore.QObject):
self.ui.splitter.setSizes([0, 1])
# Sets up FlatCAMObj, FCProcess and FCProcessContainer.
self.setup_component_editor()
self.setup_default_properties_tab()
# ###########################################################################################################
# ####################################### Auto-complete KEYWORDS ############################################
@ -2576,7 +2576,7 @@ class App(QtCore.QObject):
return
# Load in the defaults from the chosen file
self.defaults.load(filename=filename)
self.defaults.load(filename=filename, inform=self.inform)
self.preferencesUiManager.on_preferences_edited()
self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename))
@ -4655,7 +4655,7 @@ class App(QtCore.QObject):
self.collection.delete_active()
# Clear form
self.setup_component_editor()
self.setup_default_properties_tab()
self.inform.emit('%s: %s' % (_("Object deleted"), name))
@ -6965,13 +6965,13 @@ class App(QtCore.QObject):
self.collection.delete_all()
# add in Selected tab an initial text that describe the flow of work in FlatCAm
self.setup_component_editor()
self.setup_default_properties_tab()
# Clear project filename
self.project_filename = None
# Load the application defaults
self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig'))
self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig'), inform=self.inform)
# Re-fresh project options
self.on_options_app2project()
@ -9677,9 +9677,9 @@ class App(QtCore.QObject):
self.log.debug("Recent items list has been populated.")
def setup_component_editor(self):
def setup_default_properties_tab(self):
"""
Default text for the Selected tab when is not taken by the Object UI.
Default text for the Properties tab when is not taken by the Object UI.
:return:
"""
@ -9698,58 +9698,7 @@ class App(QtCore.QObject):
tsize = fsize + int(fsize / 2)
selected_text = '''
<p><span style="font-size:{tsize}px"><strong>{title}</strong></span></p>
<p><span style="font-size:{fsize}px"><strong>{subtitle}</strong>:<br />
{s1}</span></p>
<ol>
<li><span style="font-size:{fsize}px">{s2}<br />
<br />
{s3}</span><br />
&nbsp;</li>
<li><span style="font-size:{fsize}px">{s4}<br />
&nbsp;</li>
<br />
<li><span style="font-size:{fsize}px">{s5}<br />
&nbsp;</li>
<br />
<li><span style="font-size:{fsize}px">{s6}<br />
<br />
{s7}</span></li>
</ol>
<p><span style="font-size:{fsize}px">{s8}</span></p>
'''.format(
title=_("Properties Tab - Choose an Item from Project Tab"),
subtitle=_("Details"),
s1=_("The normal flow when working with the application is the following:"),
s2=_("Load/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into the application "
"using either the toolbars, key shortcuts or even dragging and dropping the "
"files on the GUI."),
s3=_("You can also load a project by double clicking on the project file, "
"drag and drop of the file into the GUI or through the menu (or toolbar) "
"actions offered within the app."),
s4=_("Once an object is available in the Project Tab, by selecting it and then focusing "
"on Properties TAB (more simpler is to double click the object name in the Project Tab, "
"Properties TAB will be updated with the object properties according to its kind: "
"Gerber, Excellon, Geometry or CNCJob object."),
s5=_("If the selection of the object is done on the canvas by single click instead, "
"and the Properties TAB is in focus, again the object properties will be displayed into the "
"Properties Tab. Alternatively, double clicking on the object on the canvas will bring "
"the Properties TAB and populate it even if it was out of focus."),
s6=_("You can change the parameters in this screen and the flow direction is like this:"),
s7=_("Gerber/Excellon Object --> Change Parameter --> Generate Geometry --> Geometry Object --> "
"Add tools (change param in Selected Tab) --> Generate CNCJob --> CNCJob Object --> "
"Verify GCode (through Edit CNC Code) and/or append/prepend to GCode "
"(again, done in SELECTED TAB) --> Save GCode."),
s8=_("A list of key shortcuts is available through an menu entry in Help --> Shortcuts List "
"or through its own key shortcut: <b>F3</b>."),
tsize=tsize,
fsize=fsize
)
selected_text = ''
sel_title.setText(selected_text)
sel_title.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)

154
camlib.py
View File

@ -25,7 +25,7 @@ from lxml import etree as ET
from shapely.geometry import Polygon, Point, LinearRing
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union, unary_union, substring, linemerge
from shapely.ops import unary_union, substring, linemerge
import shapely.affinity as affinity
from shapely.wkt import loads as sloads
from shapely.wkt import dumps as sdumps
@ -225,13 +225,14 @@ class ApertureMacro:
Pads the ``mods`` list with zeros resulting in an
list of length n.
:param n: Length of the resulting list.
:type n: int
:param mods: List to be padded.
:type mods: list
:return: Zero-padded list.
:rtype: list
:param n: Length of the resulting list.
:type n: int
:param mods: List to be padded.
:type mods: list
:return: Zero-padded list.
:rtype: list
"""
x = [0.0] * n
na = len(mods)
x[0:na] = mods
@ -244,9 +245,12 @@ class ApertureMacro:
:param mods: (Exposure 0/1, Diameter >=0, X-coord, Y-coord)
:return:
"""
pol, dia, x, y = ApertureMacro.default2zero(4, mods)
val = ApertureMacro.default2zero(4, mods)
pol = val[0]
dia = val[1]
x = val[2]
y = val[3]
# pol, dia, x, y = ApertureMacro.default2zero(4, mods)
return {"pol": int(pol), "geometry": Point(x, y).buffer(dia / 2)}
@staticmethod
@ -257,7 +261,15 @@ class ApertureMacro:
rotation angle around origin in degrees)
:return:
"""
pol, width, xs, ys, xe, ye, angle = ApertureMacro.default2zero(7, mods)
val = ApertureMacro.default2zero(7, mods)
pol = val[0]
width = val[1]
xs = val[2]
ys = val[3]
xe = val[4]
ye = val[5]
angle = val[6]
# pol, width, xs, ys, xe, ye, angle = ApertureMacro.default2zero(7, mods)
line = LineString([(xs, ys), (xe, ye)])
box = line.buffer(width / 2, cap_style=2)
@ -274,7 +286,14 @@ class ApertureMacro:
:return:
"""
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
# pol, width, height, x, y, angle = ApertureMacro.default2zero(4, mods)
val = ApertureMacro.default2zero(4, mods)
pol = val[0]
width = val[1]
height = val[2]
x = val[3]
y = val[4]
angle = val[5]
box = shply_box(x - width / 2, y - height / 2, x + width / 2, y + height / 2)
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
@ -290,7 +309,14 @@ class ApertureMacro:
:return:
"""
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
# pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
val = ApertureMacro.default2zero(6, mods)
pol = val[0]
width = val[1]
height = val[2]
x = val[3]
y = val[4]
angle = val[5]
box = shply_box(x, y, x + width, y + height)
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
@ -330,7 +356,15 @@ class ApertureMacro:
:return:
"""
pol, nverts, x, y, dia, angle = ApertureMacro.default2zero(6, mods)
# pol, nverts, x, y, dia, angle = ApertureMacro.default2zero(6, mods)
val = ApertureMacro.default2zero(6, mods)
pol = val[0]
nverts = val[1]
x = val[2]
y = val[3]
dia = val[4]
angle = val[5]
points = [(0, 0)] * nverts
for i in range(nverts):
@ -354,7 +388,17 @@ class ApertureMacro:
:return:
"""
x, y, dia, thickness, gap, nrings, cross_th, cross_len, angle = ApertureMacro.default2zero(9, mods)
# x, y, dia, thickness, gap, nrings, cross_th, cross_len, angle = ApertureMacro.default2zero(9, mods)
val = ApertureMacro.default2zero(9, mods)
x = val[0]
y = val[1]
dia = val[2]
thickness = val[3]
gap = val[4]
nrings = val[5]
cross_th = val[6]
cross_len = val[7]
angle = val[8]
r = dia / 2 - thickness / 2
result = Point((x, y)).buffer(r).exterior.buffer(thickness / 2.0)
@ -369,13 +413,13 @@ class ApertureMacro:
if r <= 0:
break
ring = Point((x, y)).buffer(r).exterior.buffer(thickness / 2.0)
result = cascaded_union([result, ring])
result = unary_union([result, ring])
i += 1
# ## Crosshair
hor = LineString([(x - cross_len, y), (x + cross_len, y)]).buffer(cross_th / 2.0, cap_style=2)
ver = LineString([(x, y - cross_len), (x, y + cross_len)]).buffer(cross_th / 2.0, cap_style=2)
result = cascaded_union([result, hor, ver])
result = unary_union([result, hor, ver])
return {"pol": 1, "geometry": result}
@ -390,7 +434,14 @@ class ApertureMacro:
:return:
"""
x, y, dout, din, t, angle = ApertureMacro.default2zero(6, mods)
# x, y, dout, din, t, angle = ApertureMacro.default2zero(6, mods)
val = ApertureMacro.default2zero(6, mods)
x = val[0]
y = val[1]
dout = val[2]
din = val[3]
t = val[4]
angle = val[5]
ring = Point((x, y)).buffer(dout / 2.0).difference(Point((x, y)).buffer(din / 2.0))
hline = LineString([(x - dout / 2.0, y), (x + dout / 2.0, y)]).buffer(t / 2.0, cap_style=3)
@ -675,7 +726,7 @@ class Geometry(object):
polygon = Polygon(points)
else:
polygon = points
toolgeo = cascaded_union(polygon)
toolgeo = unary_union(polygon)
diffs = []
for target in flat_geometry:
if isinstance(target, LineString) or isinstance(target, LineString) or isinstance(target, MultiLineString):
@ -787,7 +838,7 @@ class Geometry(object):
# if len(self.solid_geometry) == 0:
# log.debug('solid_geometry is empty []')
# return 0, 0, 0, 0
# return cascaded_union(flatten(self.solid_geometry)).bounds
# return unary_union(flatten(self.solid_geometry)).bounds
# else:
# return self.solid_geometry.bounds
# except Exception as e:
@ -802,7 +853,7 @@ class Geometry(object):
# if len(self.solid_geometry) == 0:
# log.debug('solid_geometry is empty []')
# return 0, 0, 0, 0
# return cascaded_union(self.solid_geometry).bounds
# return unary_union(self.solid_geometry).bounds
# else:
# return self.solid_geometry.bounds
@ -1325,7 +1376,7 @@ class Geometry(object):
self.solid_geometry = []
if type(self.solid_geometry) is list:
# self.solid_geometry.append(cascaded_union(geos))
# self.solid_geometry.append(unary_union(geos))
if type(geos) is list:
self.solid_geometry += geos
else:
@ -1335,7 +1386,7 @@ class Geometry(object):
# flatten the self.solid_geometry list for import_svg() to import SVG as Gerber
self.solid_geometry = list(self.flatten_list(self.solid_geometry))
self.solid_geometry = cascaded_union(self.solid_geometry)
self.solid_geometry = unary_union(self.solid_geometry)
# self.solid_geometry = MultiPolygon(self.solid_geometry)
# self.solid_geometry = self.solid_geometry.buffer(0.00000001)
@ -2211,17 +2262,17 @@ class Geometry(object):
def union(self):
"""
Runs a cascaded union on the list of objects in
Runs a unary_union on the list of objects in
solid_geometry.
:return: None
"""
self.solid_geometry = [cascaded_union(self.solid_geometry)]
self.solid_geometry = [unary_union(self.solid_geometry)]
def export_svg(self, scale_stroke_factor=0.00,
scale_factor_x=None, scale_factor_y=None,
skew_factor_x=None, skew_factor_y=None,
skew_reference='center',
skew_reference='center', scale_reference='center',
mirror=None):
"""
Exports the Geometry Object as a SVG Element
@ -2235,11 +2286,11 @@ class Geometry(object):
if self.multigeo:
for tool in self.tools:
flat_geo += self.flatten(self.tools[tool]['solid_geometry'])
geom_svg = cascaded_union(flat_geo)
geom_svg = unary_union(flat_geo)
else:
geom_svg = cascaded_union(self.flatten())
geom_svg = unary_union(self.flatten())
else:
geom_svg = cascaded_union(self.flatten())
geom_svg = unary_union(self.flatten())
skew_ref = 'center'
if skew_reference != 'center':
@ -2255,14 +2306,20 @@ class Geometry(object):
geom = geom_svg
if scale_factor_x:
geom = affinity.scale(geom_svg, scale_factor_x, 1.0)
if scale_factor_y:
geom = affinity.scale(geom_svg, 1.0, scale_factor_y)
if skew_factor_x:
if scale_factor_x and not scale_factor_y:
geom = affinity.scale(geom_svg, scale_factor_x, 1.0, origin=scale_reference)
elif not scale_factor_x and scale_factor_y:
geom = affinity.scale(geom_svg, 1.0, scale_factor_y, origin=scale_reference)
elif scale_factor_x and scale_factor_y:
geom = affinity.scale(geom_svg, scale_factor_x, scale_factor_y, origin=scale_reference)
if skew_factor_x and not skew_factor_y:
geom = affinity.skew(geom_svg, skew_factor_x, 0.0, origin=skew_ref)
if skew_factor_y:
elif not skew_factor_x and skew_factor_y:
geom = affinity.skew(geom_svg, 0.0, skew_factor_y, origin=skew_ref)
elif skew_factor_x and skew_factor_y:
geom = affinity.skew(geom_svg, skew_factor_x, skew_factor_y, origin=skew_ref)
if mirror:
if mirror == 'x':
geom = affinity.scale(geom_svg, 1.0, -1.0)
@ -4818,7 +4875,7 @@ class CNCjob(Geometry):
:return: GCode - string
"""
log.debug("Generate_from_multitool_geometry()")
log.debug("generate_from_multitool_geometry()")
temp_solid_geometry = []
if offset != 0.0:
@ -5128,7 +5185,7 @@ class CNCjob(Geometry):
:rtype: str
"""
log.debug("Generate_from_multitool_geometry()")
log.debug("geometry_tool_gcode_gen()")
t_gcode = ''
temp_solid_geometry = []
@ -5244,7 +5301,11 @@ class CNCjob(Geometry):
self.feedrate_rapid = float(tool_dict['feedrate_rapid'])
self.spindlespeed = float(tool_dict['spindlespeed'])
self.spindledir = tool_dict['spindledir']
try:
self.spindledir = tool_dict['spindledir']
except KeyError:
self.spindledir = self.app.defaults["geometry_spindledir"]
self.dwell = tool_dict['dwell']
self.dwelltime = float(tool_dict['dwelltime'])
@ -5253,8 +5314,9 @@ class CNCjob(Geometry):
self.startz = None
self.z_end = float(tool_dict['endz'])
self.xy_end = tool_dict['endxy']
try:
if self.xy_end == '':
if self.xy_end == '' or self.xy_end is None:
self.xy_end = None
else:
# either originally it was a string or not, xy_end will be made string
@ -6807,7 +6869,7 @@ class CNCjob(Geometry):
# This takes forever. Too much data?
# self.app.inform.emit('%s: %s' % (_("Unifying Geometry from parsed Geometry segments"),
# str(len(self.gcode_parsed))))
# self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
# self.solid_geometry = unary_union([geo['geom'] for geo in self.gcode_parsed])
# This is much faster but not so nice to look at as you can see different segments of the geometry
self.solid_geometry = [geo['geom'] for geo in self.gcode_parsed]
@ -7413,18 +7475,18 @@ class CNCjob(Geometry):
travels.append(g)
# Used to determine the overall board size
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
self.solid_geometry = unary_union([geo['geom'] for geo in self.gcode_parsed])
# Convert the cuts and travels into single geometry objects we can render as svg xml
if travels:
travelsgeom = cascaded_union([geo['geom'] for geo in travels])
travelsgeom = unary_union([geo['geom'] for geo in travels])
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
if cuts:
cutsgeom = cascaded_union([geo['geom'] for geo in cuts])
cutsgeom = unary_union([geo['geom'] for geo in cuts])
# Render the SVG Xml
# The scale factor affects the size of the lines, and the stroke color adds different formatting for each set
@ -7691,7 +7753,7 @@ class CNCjob(Geometry):
self.app.proc_container.update_view_text(' %d%%' % disp_number)
self.old_disp_number = disp_number
v['solid_geometry'] = cascaded_union([geo['geom'] for geo in v['gcode_parsed']])
v['solid_geometry'] = unary_union([geo['geom'] for geo in v['gcode_parsed']])
self.create_geometry()
self.app.proc_container.new_text = ''
@ -7800,7 +7862,7 @@ class CNCjob(Geometry):
self.old_disp_number = disp_number
# for the bounding box
v['solid_geometry'] = cascaded_union([geo['geom'] for geo in v['gcode_parsed']])
v['solid_geometry'] = unary_union([geo['geom'] for geo in v['gcode_parsed']])
self.app.proc_container.new_text = ''
@ -8172,7 +8234,7 @@ def dict2obj(d):
#
# m = MultiLineString(edge_points)
# triangles = list(polygonize(m))
# return cascaded_union(triangles), edge_points
# return unary_union(triangles), edge_points
# def voronoi(P):
# """

View File

@ -339,6 +339,11 @@ class FlatCAMDefaults:
"geometry_area_shape": "polygon",
"geometry_area_strategy": "over",
"geometry_area_overz": 1.0,
"geometry_polish": False,
"geometry_polish_dia": 10.0,
"geometry_polish_pressure": -1.0,
"geometry_polish_overlap": 1.0,
"geometry_polish_method": _("Standard"),
# Geometry Editor
"geometry_editor_sel_limit": 30,
@ -410,6 +415,7 @@ class FlatCAMDefaults:
"tools_iso_rest": False,
"tools_iso_combine_passes": True,
"tools_iso_check_valid": False,
"tools_iso_isoexcept": False,
"tools_iso_selection": _("All"),
"tools_iso_poly_ints": False,
@ -533,6 +539,7 @@ class FlatCAMDefaults:
"tools_film_file_type_radio": 'svg',
"tools_film_orientation": 'p',
"tools_film_pagesize": 'A4',
"tools_film_png_dpi": 96,
# Panel Tool
"tools_panelize_spacing_columns": 0.0,
@ -833,8 +840,13 @@ class FlatCAMDefaults:
with open(filename, "w") as file:
simplejson.dump(self.defaults, file, default=to_dict, indent=2, sort_keys=True)
def load(self, filename: str):
"""Loads the defaults from a file on disk, performing migration if required."""
def load(self, filename: str, inform):
"""
Loads the defaults from a file on disk, performing migration if required.
:param filename: a path to the file that is to be loaded
:param inform: a pyqtSignal used to display information's in the StatusBar of the GUI
"""
# Read in the file
try:
@ -843,7 +855,7 @@ class FlatCAMDefaults:
f.close()
except IOError:
log.error("Could not load defaults file.")
self.inform.emit('[ERROR] %s' % _("Could not load defaults file."))
inform.emit('[ERROR] %s' % _("Could not load defaults file."))
# in case the defaults file can't be loaded, show all toolbars
self.defaults["global_toolbar_view"] = 511
return
@ -856,7 +868,7 @@ class FlatCAMDefaults:
self.defaults["global_toolbar_view"] = 511
e = sys.exc_info()[0]
log.error(str(e))
self.inform.emit('[ERROR] %s' % _("Failed to parse defaults file."))
inform.emit('[ERROR] %s' % _("Failed to parse defaults file."))
return
if defaults is None:
return

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import collections
from tclCommands.TclCommand import TclCommand
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import gettext
import appTranslation as fcTranslate
@ -94,7 +94,7 @@ class TclCommandBbox(TclCommand):
# assert geo_obj.kind == 'geometry'
# Bounding box with rounded corners
geo = cascaded_union(obj.solid_geometry)
geo = unary_union(obj.solid_geometry)
bounding_box = geo.envelope.buffer(float(margin))
if not rounded: # Remove rounded corners
bounding_box = bounding_box.envelope

View File

@ -3,7 +3,7 @@ from tclCommands.TclCommand import TclCommand
import collections
import logging
from shapely.ops import cascaded_union
from shapely.ops import unary_union
from shapely.geometry import LineString
log = logging.getLogger('base')
@ -134,7 +134,7 @@ class TclCommandCutout(TclCommand):
[pts[6], pts[7], pts[8]],
[pts[9], pts[10], pts[11]]]}
cuts = cases[gaps_par]
geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
geo_obj.solid_geometry = unary_union([LineString(segment) for segment in cuts])
try:
self.app.app_obj.new_object("geometry", outname, geo_init_me, plot=False)

View File

@ -21,12 +21,12 @@ class TclCommandExportSVG(TclCommand):
arg_names = collections.OrderedDict([
('name', str),
('filename', str),
('scale_factor', float)
('scale_stroke_factor', float)
])
# Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
option_types = collections.OrderedDict([
('scale_factor', float)
('scale_stroke_factor', float)
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
@ -39,7 +39,7 @@ class TclCommandExportSVG(TclCommand):
('name', 'Name of the object export. Required.'),
('filename', 'Absolute path to file to export.\n'
'WARNING: no spaces are allowed. If unsure enclose the entire path with quotes.'),
('scale_factor', 'Multiplication factor used for scaling line widths during export.')
('scale_stroke_factor', 'Multiplication factor used for scaling line widths during export.')
]),
'examples': ['export_svg my_geometry my_file.svg']
}

View File

@ -3,7 +3,7 @@ from tclCommands.TclCommand import TclCommandSignaled
import logging
import collections
from copy import deepcopy
from shapely.ops import cascaded_union
from shapely.ops import unary_union
from shapely.geometry import Polygon, LineString, LinearRing
import gettext
@ -131,14 +131,14 @@ class TclCommandGeoCutout(TclCommandSignaled):
flat_geometry = flatten(geo, pathonly=True)
polygon = Polygon(pts)
toolgeo = cascaded_union(polygon)
toolgeo = unary_union(polygon)
diffs = []
for target in flat_geometry:
if type(target) == LineString or type(target) == LinearRing:
diffs.append(target.difference(toolgeo))
else:
log.warning("Not implemented.")
return cascaded_union(diffs)
return unary_union(diffs)
if 'name' in args:
name = args['name']

View File

@ -1,6 +1,6 @@
from tclCommands.TclCommand import TclCommand
from shapely.ops import cascaded_union
from shapely.ops import unary_union
import collections
@ -92,7 +92,7 @@ class TclCommandNregions(TclCommand):
def geo_init(geo_obj, app_obj):
assert geo_obj.kind == 'geometry'
geo = cascaded_union(obj.solid_geometry)
geo = unary_union(obj.solid_geometry)
bounding_box = geo.envelope.buffer(float(margin))
if not rounded:
bounding_box = bounding_box.envelope

View File

@ -1,5 +1,5 @@
from shapely.geometry import LineString, Polygon
from shapely.ops import cascaded_union, unary_union
from shapely.ops import unary_union
from matplotlib.pyplot import plot, subplot, show, axes
from matplotlib.axes import *
from camlib import *

View File

@ -1,7 +1,7 @@
import unittest
from shapely.geometry import LineString, Polygon
from shapely.ops import cascaded_union, unary_union
from shapely.ops import unary_union
from matplotlib.pyplot import plot, subplot, show, cla, clf, xlim, ylim, title
from matplotlib.axes import *
from camlib import *

View File

@ -1,7 +1,7 @@
import unittest
from shapely.geometry import LineString, Polygon
from shapely.ops import cascaded_union, unary_union
from shapely.ops import unary_union
from matplotlib.pyplot import plot, subplot, show, cla, clf, xlim, ylim, title
from camlib import *
from random import random