@@ -1,5 +1,5 @@
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
|
||||
from AppGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
|
||||
FCTree, RadioSet, FCFileSaveDialog
|
||||
from camlib import to_dict
|
||||
|
||||
@@ -8,8 +8,10 @@ import json
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import math
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -655,7 +657,7 @@ class ToolsDB(QtWidgets.QWidget):
|
||||
l_save=str(self.app.get_last_save_folder()),
|
||||
n=_("Tools_Database"),
|
||||
date=date),
|
||||
filter=filter__)
|
||||
ext_filter=filter__)
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
@@ -1030,6 +1032,7 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
self.advanced_box.setTitle(_("Advanced Geo Parameters"))
|
||||
self.advanced_box.setFixedWidth(250)
|
||||
|
||||
# NCC TOOL BOX
|
||||
self.ncc_box = QtWidgets.QGroupBox()
|
||||
self.ncc_box.setStyleSheet("""
|
||||
QGroupBox
|
||||
@@ -1042,6 +1045,7 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
self.ncc_box.setTitle(_("NCC Parameters"))
|
||||
self.ncc_box.setFixedWidth(250)
|
||||
|
||||
# PAINT TOOL BOX
|
||||
self.paint_box = QtWidgets.QGroupBox()
|
||||
self.paint_box.setStyleSheet("""
|
||||
QGroupBox
|
||||
@@ -1054,10 +1058,24 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
self.paint_box.setTitle(_("Paint Parameters"))
|
||||
self.paint_box.setFixedWidth(250)
|
||||
|
||||
# ISOLATION TOOL BOX
|
||||
self.iso_box = QtWidgets.QGroupBox()
|
||||
self.iso_box.setStyleSheet("""
|
||||
QGroupBox
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.iso_vlay = QtWidgets.QVBoxLayout()
|
||||
self.iso_box.setTitle(_("Isolation Parameters"))
|
||||
self.iso_box.setFixedWidth(250)
|
||||
|
||||
self.basic_box.setLayout(self.basic_vlay)
|
||||
self.advanced_box.setLayout(self.advanced_vlay)
|
||||
self.ncc_box.setLayout(self.ncc_vlay)
|
||||
self.paint_box.setLayout(self.paint_vlay)
|
||||
self.iso_box.setLayout(self.iso_vlay)
|
||||
|
||||
geo_vlay = QtWidgets.QVBoxLayout()
|
||||
geo_vlay.addWidget(self.basic_box)
|
||||
@@ -1067,6 +1085,7 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
tools_vlay = QtWidgets.QVBoxLayout()
|
||||
tools_vlay.addWidget(self.ncc_box)
|
||||
tools_vlay.addWidget(self.paint_box)
|
||||
tools_vlay.addWidget(self.iso_box)
|
||||
tools_vlay.addStretch()
|
||||
|
||||
param_hlay.addLayout(geo_vlay)
|
||||
@@ -1478,7 +1497,7 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
|
||||
self.ncc_method_combo = FCComboBox()
|
||||
self.ncc_method_combo.addItems(
|
||||
[_("Standard"), _("Seed"), _("Lines")]
|
||||
[_("Standard"), _("Seed"), _("Lines"), _("Combo")]
|
||||
)
|
||||
self.ncc_method_combo.setObjectName("gdb_n_method")
|
||||
|
||||
@@ -1621,6 +1640,101 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
self.grid3.addWidget(self.pathconnect_cb, 10, 0)
|
||||
self.grid3.addWidget(self.paintcontour_cb, 10, 1)
|
||||
|
||||
# ###########################################################################
|
||||
# ############### Paint UI form #############################################
|
||||
# ###########################################################################
|
||||
|
||||
self.grid4 = QtWidgets.QGridLayout()
|
||||
self.iso_vlay.addLayout(self.grid4)
|
||||
self.grid4.setColumnStretch(0, 0)
|
||||
self.grid4.setColumnStretch(1, 1)
|
||||
self.iso_vlay.addStretch()
|
||||
|
||||
# Passes
|
||||
passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
|
||||
passlabel.setToolTip(
|
||||
_("Width of the isolation gap in\n"
|
||||
"number (integer) of tool widths.")
|
||||
)
|
||||
self.passes_entry = FCSpinner()
|
||||
self.passes_entry.set_range(1, 999)
|
||||
self.passes_entry.setObjectName("gdb_i_passes")
|
||||
|
||||
self.grid4.addWidget(passlabel, 0, 0)
|
||||
self.grid4.addWidget(self.passes_entry, 0, 1)
|
||||
|
||||
# Overlap Entry
|
||||
overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
|
||||
overlabel.setToolTip(
|
||||
_("How much (percentage) of the tool width to overlap each tool pass.")
|
||||
)
|
||||
self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
|
||||
self.iso_overlap_entry.set_precision(self.decimals)
|
||||
self.iso_overlap_entry.setWrapping(True)
|
||||
self.iso_overlap_entry.set_range(0.0000, 99.9999)
|
||||
self.iso_overlap_entry.setSingleStep(0.1)
|
||||
self.iso_overlap_entry.setObjectName("gdb_i_overlap")
|
||||
|
||||
self.grid4.addWidget(overlabel, 2, 0)
|
||||
self.grid4.addWidget(self.iso_overlap_entry, 2, 1)
|
||||
|
||||
# Milling Type Radio Button
|
||||
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
|
||||
self.milling_type_label.setToolTip(
|
||||
_("Milling type when the selected tool is of type: 'iso_op':\n"
|
||||
"- climb / best for precision milling and to reduce tool usage\n"
|
||||
"- conventional / useful when there is no backlash compensation")
|
||||
)
|
||||
|
||||
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
|
||||
{'label': _('Conventional'), 'value': 'cv'}])
|
||||
self.milling_type_radio.setToolTip(
|
||||
_("Milling type when the selected tool is of type: 'iso_op':\n"
|
||||
"- climb / best for precision milling and to reduce tool usage\n"
|
||||
"- conventional / useful when there is no backlash compensation")
|
||||
)
|
||||
self.milling_type_radio.setObjectName("gdb_i_milling_type")
|
||||
|
||||
self.grid4.addWidget(self.milling_type_label, 4, 0)
|
||||
self.grid4.addWidget(self.milling_type_radio, 4, 1)
|
||||
|
||||
# Follow
|
||||
self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
|
||||
self.follow_label.setToolTip(
|
||||
_("Generate a 'Follow' geometry.\n"
|
||||
"This means that it will cut through\n"
|
||||
"the middle of the trace.")
|
||||
)
|
||||
|
||||
self.follow_cb = FCCheckBox()
|
||||
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
|
||||
"This means that it will cut through\n"
|
||||
"the middle of the trace."))
|
||||
self.follow_cb.setObjectName("gdb_i_follow")
|
||||
|
||||
self.grid4.addWidget(self.follow_label, 6, 0)
|
||||
self.grid4.addWidget(self.follow_cb, 6, 1)
|
||||
|
||||
# Isolation Type
|
||||
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
|
||||
self.iso_type_label.setToolTip(
|
||||
_("Choose how the isolation will be executed:\n"
|
||||
"- 'Full' -> complete isolation of polygons\n"
|
||||
"- 'Ext' -> will isolate only on the outside\n"
|
||||
"- 'Int' -> will isolate only on the inside\n"
|
||||
"'Exterior' isolation is almost always possible\n"
|
||||
"(with the right tool) but 'Interior'\n"
|
||||
"isolation can be done only when there is an opening\n"
|
||||
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
|
||||
)
|
||||
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
|
||||
{'label': _('Ext'), 'value': 'ext'},
|
||||
{'label': _('Int'), 'value': 'int'}])
|
||||
self.iso_type_radio.setObjectName("gdb_i_iso_type")
|
||||
|
||||
self.grid4.addWidget(self.iso_type_label, 8, 0)
|
||||
self.grid4.addWidget(self.iso_type_radio, 8, 1)
|
||||
|
||||
# ####################################################################
|
||||
# ####################################################################
|
||||
# GUI for the lower part of the window
|
||||
@@ -1743,6 +1857,13 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
"tools_paintmethod": self.paintmethod_combo,
|
||||
"tools_pathconnect": self.pathconnect_cb,
|
||||
"tools_paintcontour": self.paintcontour_cb,
|
||||
|
||||
# Isolation
|
||||
"tools_iso_passes": self.passes_entry,
|
||||
"tools_iso_overlap": self.iso_overlap_entry,
|
||||
"tools_iso_milling_type": self.milling_type_radio,
|
||||
"tools_iso_follow": self.follow_cb,
|
||||
"tools_iso_isotype": self.iso_type_radio
|
||||
}
|
||||
|
||||
self.name2option = {
|
||||
@@ -1787,6 +1908,13 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
'gdb_p_method': "tools_paintmethod",
|
||||
'gdb_p_connect': "tools_pathconnect",
|
||||
'gdb_p_contour': "tools_paintcontour",
|
||||
|
||||
# Isolation
|
||||
"gdb_i_passes": "tools_iso_passes",
|
||||
"gdb_i_overlap": "tools_iso_overlap",
|
||||
"gdb_i_milling_type": "tools_iso_milling_type",
|
||||
"gdb_i_follow": "tools_iso_follow",
|
||||
"gdb_i_iso_type": "tools_iso_isotype"
|
||||
}
|
||||
|
||||
self.current_toolid = None
|
||||
@@ -1939,21 +2067,23 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
if self.db_tool_dict:
|
||||
self.storage_to_form(self.db_tool_dict['1'])
|
||||
|
||||
# Enable GUI
|
||||
# Enable AppGUI
|
||||
self.basic_box.setEnabled(True)
|
||||
self.advanced_box.setEnabled(True)
|
||||
self.ncc_box.setEnabled(True)
|
||||
self.paint_box.setEnabled(True)
|
||||
self.iso_box.setEnabled(True)
|
||||
|
||||
self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
|
||||
# self.tree_widget.setFocus()
|
||||
|
||||
else:
|
||||
# Disable GUI
|
||||
# Disable AppGUI
|
||||
self.basic_box.setEnabled(False)
|
||||
self.advanced_box.setEnabled(False)
|
||||
self.ncc_box.setEnabled(False)
|
||||
self.paint_box.setEnabled(False)
|
||||
self.iso_box.setEnabled(False)
|
||||
else:
|
||||
self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
|
||||
|
||||
@@ -2006,6 +2136,13 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
"tools_paintmethod": self.app.defaults["tools_paintmethod"],
|
||||
"tools_pathconnect": self.app.defaults["tools_pathconnect"],
|
||||
"tools_paintcontour": self.app.defaults["tools_paintcontour"],
|
||||
|
||||
# Isolation
|
||||
"tools_iso_passes": int(self.app.defaults["tools_iso_passes"]),
|
||||
"tools_iso_overlap": float(self.app.defaults["tools_iso_overlap"]),
|
||||
"tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"],
|
||||
"tools_iso_follow": self.app.defaults["tools_iso_follow"],
|
||||
"tools_iso_isotype": self.app.defaults["tools_iso_isotype"],
|
||||
})
|
||||
|
||||
dict_elem = {}
|
||||
@@ -2117,7 +2254,7 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
l_save=str(self.app.get_last_save_folder()),
|
||||
n=_("Tools_Database"),
|
||||
date=date),
|
||||
filter=filter__)
|
||||
ext_filter=filter__)
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
@@ -2218,6 +2355,18 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
self.app.tools_db_changed_flag = False
|
||||
self.on_save_tools_db()
|
||||
|
||||
def on_calculate_tooldia(self):
|
||||
if self.shape_combo.get_value() == 'V':
|
||||
tip_dia = float(self.vdia_entry.get_value())
|
||||
half_tip_angle = float(self.vangle_entry.get_value()) / 2.0
|
||||
cut_z = float(self.cutz_entry.get_value())
|
||||
cut_z = -cut_z if cut_z < 0 else cut_z
|
||||
|
||||
# calculated tool diameter so the cut_z parameter is obeyed
|
||||
tool_dia = tip_dia + (2 * cut_z * math.tan(math.radians(half_tip_angle)))
|
||||
|
||||
self.dia_entry.set_value(tool_dia)
|
||||
|
||||
def ui_connect(self):
|
||||
# make sure that we don't make multiple connections to the widgets
|
||||
self.ui_disconnect()
|
||||
@@ -2247,12 +2396,40 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
|
||||
wdg.valueChanged.connect(self.update_storage)
|
||||
|
||||
# connect the calculate tooldia method to the controls
|
||||
# if the tool shape is 'V' the tool dia will be calculated to obey Cut Z parameter
|
||||
self.shape_combo.currentIndexChanged.connect(self.on_calculate_tooldia)
|
||||
self.cutz_entry.valueChanged.connect(self.on_calculate_tooldia)
|
||||
self.vdia_entry.valueChanged.connect(self.on_calculate_tooldia)
|
||||
self.vangle_entry.valueChanged.connect(self.on_calculate_tooldia)
|
||||
|
||||
|
||||
def ui_disconnect(self):
|
||||
try:
|
||||
self.name_entry.editingFinished.disconnect(self.update_tree_name)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
self.shape_combo.currentIndexChanged.disconnect(self.on_calculate_tooldia)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
self.cutz_entry.valueChanged.disconnect(self.on_calculate_tooldia)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
self.vdia_entry.valueChanged.disconnect(self.on_calculate_tooldia)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
self.vangle_entry.valueChanged.disconnect(self.on_calculate_tooldia)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
for key in self.form_fields:
|
||||
wdg = self.form_fields[key]
|
||||
|
||||
@@ -2398,6 +2575,18 @@ class ToolsDB2(QtWidgets.QWidget):
|
||||
elif wdg_name == "gdb_p_contour":
|
||||
self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
|
||||
|
||||
# Isolation Tool
|
||||
elif wdg_name == "gdb_i_passes":
|
||||
self.db_tool_dict[tool_id]['data']['tools_iso_passes'] = val
|
||||
elif wdg_name == "gdb_i_overlap":
|
||||
self.db_tool_dict[tool_id]['data']['tools_iso_overlap'] = val
|
||||
elif wdg_name == "gdb_i_milling_type":
|
||||
self.db_tool_dict[tool_id]['data']['tools_iso_milling_type'] = val
|
||||
elif wdg_name == "gdb_i_follow":
|
||||
self.db_tool_dict[tool_id]['data']['tools_iso_follow'] = val
|
||||
elif wdg_name == "gdb_i_iso_type":
|
||||
self.db_tool_dict[tool_id]['data']['tools_iso_isotype'] = val
|
||||
|
||||
self.callback_app()
|
||||
|
||||
def on_tool_requested_from_app(self):
|
||||
@@ -9,9 +9,9 @@ from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from camlib import distance, arc, FlatCAMRTreeStorage
|
||||
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
|
||||
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
|
||||
from flatcamParsers.ParseExcellon import Excellon
|
||||
from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
|
||||
from AppEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
|
||||
from AppParsers.ParseExcellon import Excellon
|
||||
|
||||
from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
|
||||
import shapely.affinity as affinity
|
||||
@@ -26,7 +26,7 @@ import logging
|
||||
from copy import deepcopy
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -2123,7 +2123,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
else:
|
||||
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
else:
|
||||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
|
||||
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_exc_editor')
|
||||
|
||||
@@ -2239,7 +2239,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
|
||||
# store the status of the editor so the Delete at object level will not work until the edit is finished
|
||||
self.editor_active = False
|
||||
log.debug("Initialization of the FlatCAM Excellon Editor is finished ...")
|
||||
log.debug("Initialization of the Excellon Editor is finished ...")
|
||||
|
||||
def pool_recreated(self, pool):
|
||||
self.shapes.pool = pool
|
||||
@@ -2312,7 +2312,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
tool_dia = float('%.*f' % (self.decimals, v['C']))
|
||||
self.tool2tooldia[int(k)] = tool_dia
|
||||
|
||||
# Init GUI
|
||||
# Init AppGUI
|
||||
self.addtool_entry.set_value(float(self.app.defaults['excellon_editor_newdia']))
|
||||
self.drill_array_size_entry.set_value(int(self.app.defaults['excellon_editor_array_size']))
|
||||
self.drill_axis_radio.set_value(self.app.defaults['excellon_editor_lin_dir'])
|
||||
@@ -2819,10 +2819,8 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
self.tool_shape.enabled = True
|
||||
# self.app.app_cursor.enabled = True
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(True)
|
||||
self.app.ui.corner_snap_btn.setEnabled(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
|
||||
self.app.ui.exc_editor_menu.setDisabled(False)
|
||||
self.app.ui.exc_editor_menu.menuAction().setVisible(True)
|
||||
@@ -2832,12 +2830,11 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
|
||||
self.app.ui.exc_edit_toolbar.setDisabled(False)
|
||||
self.app.ui.exc_edit_toolbar.setVisible(True)
|
||||
# self.app.ui.snap_toolbar.setDisabled(False)
|
||||
# self.app.ui.status_toolbar.setDisabled(False)
|
||||
|
||||
# start with GRID toolbar activated
|
||||
if self.app.ui.grid_snap_btn.isChecked() is False:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
self.app.ui.on_grid_snap_triggered(state=True)
|
||||
|
||||
self.app.ui.popmenu_disable.setVisible(False)
|
||||
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
|
||||
@@ -2869,30 +2866,8 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
self.clear()
|
||||
self.app.ui.exc_edit_toolbar.setDisabled(True)
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("layout"):
|
||||
layout = settings.value('layout', type=str)
|
||||
if layout == 'standard':
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
else:
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(True)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
else:
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
|
||||
# set the Editor Toolbar visibility to what was before entering in the Editor
|
||||
self.app.ui.exc_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
|
||||
@@ -3068,7 +3043,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
|
||||
self.set_ui()
|
||||
|
||||
# now that we hava data, create the GUI interface and add it to the Tool Tab
|
||||
# now that we hava data, create the AppGUI interface and add it to the Tool Tab
|
||||
self.build_ui(first_run=True)
|
||||
|
||||
# we activate this after the initial build as we don't need to see the tool been populated
|
||||
@@ -3361,15 +3336,17 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
with self.app.proc_container.new(_("Creating Excellon.")):
|
||||
|
||||
try:
|
||||
edited_obj = self.app.new_object("excellon", outname, obj_init)
|
||||
edited_obj = self.app.app_obj.new_object("excellon", outname, obj_init)
|
||||
edited_obj.source_file = self.app.export_excellon(obj_name=edited_obj.options['name'],
|
||||
local_use=edited_obj,
|
||||
filename=None,
|
||||
use_thread=False)
|
||||
except Exception as e:
|
||||
self.deactivate()
|
||||
log.error("Error on Edited object creation: %s" % str(e))
|
||||
return
|
||||
|
||||
self.deactivate()
|
||||
self.app.inform.emit('[success] %s' % _("Excellon editing finished."))
|
||||
|
||||
def on_tool_select(self, tool):
|
||||
@@ -3463,8 +3440,8 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
self.pos = (self.pos[0], self.pos[1])
|
||||
|
||||
if event.button == 1:
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (0, 0))
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (0, 0))
|
||||
|
||||
# Selection with left mouse button
|
||||
if self.active_tool is not None and event.button == 1:
|
||||
@@ -3801,18 +3778,22 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
self.snap_x = x
|
||||
self.snap_y = y
|
||||
|
||||
# update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f" % (x, y))
|
||||
|
||||
if self.pos is None:
|
||||
self.pos = (0, 0)
|
||||
self.app.dx = x - self.pos[0]
|
||||
self.app.dy = y - self.pos[1]
|
||||
|
||||
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (self.app.dx, self.app.dy))
|
||||
# # update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f " % (x, y))
|
||||
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (self.app.dx, self.app.dy))
|
||||
|
||||
units = self.app.defaults["units"].lower()
|
||||
self.app.plotcanvas.text_hud.text = \
|
||||
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
|
||||
self.app.dx, units, self.app.dy, units, x, units, y, units)
|
||||
|
||||
# ## Utility geometry (animated)
|
||||
self.update_utility_geometry(data=(x, y))
|
||||
@@ -4045,7 +4026,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
||||
|
||||
def select_tool(self, toolname):
|
||||
"""
|
||||
Selects a drawing tool. Impacts the object and GUI.
|
||||
Selects a drawing tool. Impacts the object and AppGUI.
|
||||
|
||||
:param toolname: Name of the tool.
|
||||
:return: None
|
||||
@@ -15,11 +15,10 @@ from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from flatcamGUI.ObjectUI import RadioSet
|
||||
from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
|
||||
FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
|
||||
from flatcamParsers.ParseFont import *
|
||||
from AppTool import AppTool
|
||||
from AppGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
|
||||
FCDoubleSpinner, FCButton, FCInputDialog, FCTree
|
||||
from AppParsers.ParseFont import *
|
||||
|
||||
from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
|
||||
from shapely.ops import cascaded_union, unary_union, linemerge
|
||||
@@ -34,7 +33,7 @@ from rtree import index as rtindex
|
||||
from copy import deepcopy
|
||||
# from vispy.io import read_png
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -42,7 +41,7 @@ if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class BufferSelectionTool(FlatCAMTool):
|
||||
class BufferSelectionTool(AppTool):
|
||||
"""
|
||||
Simple input for buffer distance.
|
||||
"""
|
||||
@@ -50,7 +49,7 @@ class BufferSelectionTool(FlatCAMTool):
|
||||
toolName = "Buffer Selection"
|
||||
|
||||
def __init__(self, app, draw_app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
self.draw_app = draw_app
|
||||
self.decimals = app.decimals
|
||||
@@ -118,12 +117,12 @@ class BufferSelectionTool(FlatCAMTool):
|
||||
self.buffer_int_button.clicked.connect(self.on_buffer_int)
|
||||
self.buffer_ext_button.clicked.connect(self.on_buffer_ext)
|
||||
|
||||
# Init GUI
|
||||
# Init AppGUI
|
||||
self.buffer_distance_entry.set_value(0.01)
|
||||
|
||||
def run(self):
|
||||
self.app.defaults.report_usage("Geo Editor ToolBuffer()")
|
||||
FlatCAMTool.run(self)
|
||||
AppTool.run(self)
|
||||
|
||||
# if the splitter us hidden, display it
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
@@ -187,7 +186,7 @@ class BufferSelectionTool(FlatCAMTool):
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
||||
|
||||
|
||||
class TextInputTool(FlatCAMTool):
|
||||
class TextInputTool(AppTool):
|
||||
"""
|
||||
Simple input for buffer distance.
|
||||
"""
|
||||
@@ -195,7 +194,7 @@ class TextInputTool(FlatCAMTool):
|
||||
toolName = "Text Input Tool"
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.text_path = []
|
||||
@@ -340,7 +339,7 @@ class TextInputTool(FlatCAMTool):
|
||||
|
||||
def run(self):
|
||||
self.app.defaults.report_usage("Geo Editor TextInputTool()")
|
||||
FlatCAMTool.run(self)
|
||||
AppTool.run(self)
|
||||
|
||||
# if the splitter us hidden, display it
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
@@ -405,7 +404,7 @@ class TextInputTool(FlatCAMTool):
|
||||
self.app.ui.notebook.setTabText(2, _("Tool"))
|
||||
|
||||
|
||||
class PaintOptionsTool(FlatCAMTool):
|
||||
class PaintOptionsTool(AppTool):
|
||||
"""
|
||||
Inputs to specify how to paint the selected polygons.
|
||||
"""
|
||||
@@ -413,7 +412,7 @@ class PaintOptionsTool(FlatCAMTool):
|
||||
toolName = "Paint Tool"
|
||||
|
||||
def __init__(self, app, fcdraw):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.fcdraw = fcdraw
|
||||
@@ -538,7 +537,7 @@ class PaintOptionsTool(FlatCAMTool):
|
||||
|
||||
def run(self):
|
||||
self.app.defaults.report_usage("Geo Editor ToolPaint()")
|
||||
FlatCAMTool.run(self)
|
||||
AppTool.run(self)
|
||||
|
||||
# if the splitter us hidden, display it
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
@@ -547,7 +546,7 @@ class PaintOptionsTool(FlatCAMTool):
|
||||
self.app.ui.notebook.setTabText(2, _("Paint Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
# Init GUI
|
||||
# Init AppGUI
|
||||
if self.app.defaults["tools_painttooldia"]:
|
||||
self.painttooldia_entry.set_value(self.app.defaults["tools_painttooldia"])
|
||||
else:
|
||||
@@ -599,7 +598,7 @@ class PaintOptionsTool(FlatCAMTool):
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
|
||||
|
||||
class TransformEditorTool(FlatCAMTool):
|
||||
class TransformEditorTool(AppTool):
|
||||
"""
|
||||
Inputs to specify how to paint the selected polygons.
|
||||
"""
|
||||
@@ -612,7 +611,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
offsetName = _("Offset")
|
||||
|
||||
def __init__(self, app, draw_app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.draw_app = draw_app
|
||||
@@ -933,7 +932,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
"the 'y' in (x, y) will be used when using Flip on Y.")
|
||||
)
|
||||
self.flip_ref_label.setFixedWidth(50)
|
||||
self.flip_ref_entry = FCEntry("(0, 0)")
|
||||
self.flip_ref_entry = FCEntry("0, 0")
|
||||
|
||||
self.flip_ref_button = FCButton()
|
||||
self.flip_ref_button.set_value(_("Add"))
|
||||
@@ -981,7 +980,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
|
||||
def run(self):
|
||||
self.app.defaults.report_usage("Geo Editor Transform Tool()")
|
||||
FlatCAMTool.run(self)
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
# if the splitter us hidden, display it
|
||||
@@ -991,7 +990,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
# Initialize form
|
||||
@@ -1048,7 +1047,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
if self.app.defaults["tools_transform_mirror_point"]:
|
||||
self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
|
||||
else:
|
||||
self.flip_ref_entry.set_value((0, 0))
|
||||
self.flip_ref_entry.set_value("0, 0")
|
||||
|
||||
def template(self):
|
||||
if not self.draw_app.selected:
|
||||
@@ -3383,7 +3382,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
else:
|
||||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
|
||||
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
|
||||
|
||||
@@ -3467,22 +3466,32 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
self.options[opt] = float(entry.text())
|
||||
text_value = entry.text()
|
||||
if ',' in text_value:
|
||||
text_value = text_value.replace(',', '.')
|
||||
self.options[opt] = float(text_value)
|
||||
except Exception as e:
|
||||
entry.set_value(self.app.defaults[opt])
|
||||
log.debug("FlatCAMGeoEditor.__init__().entry2option() --> %s" % str(e))
|
||||
return
|
||||
|
||||
def gridx_changed(goption, gentry):
|
||||
def grid_changed(goption, gentry):
|
||||
"""
|
||||
|
||||
:param goption: String. Can be either 'global_gridx' or 'global_gridy'
|
||||
:param gentry: A GUI element which text value is read and used
|
||||
:return:
|
||||
"""
|
||||
if goption not in ['global_gridx', 'global_gridy']:
|
||||
return
|
||||
|
||||
entry2option(opt=goption, entry=gentry)
|
||||
# if the grid link is checked copy the value in the GridX field to GridY
|
||||
try:
|
||||
val = float(gentry.get_value())
|
||||
text_value = gentry.text()
|
||||
if ',' in text_value:
|
||||
text_value = text_value.replace(',', '.')
|
||||
val = float(text_value)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
@@ -3491,7 +3500,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
|
||||
self.app.ui.grid_gap_x_entry.textChanged.connect(
|
||||
lambda: gridx_changed("global_gridx", self.app.ui.grid_gap_x_entry))
|
||||
lambda: grid_changed("global_gridx", self.app.ui.grid_gap_x_entry))
|
||||
|
||||
self.app.ui.grid_gap_y_entry.setValidator(QtGui.QDoubleValidator())
|
||||
self.app.ui.grid_gap_y_entry.textChanged.connect(
|
||||
@@ -3542,7 +3551,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
# store the status of the editor so the Delete at object level will not work until the edit is finished
|
||||
self.editor_active = False
|
||||
log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
|
||||
log.debug("Initialization of the Geometry Editor is finished ...")
|
||||
|
||||
def pool_recreated(self, pool):
|
||||
self.shapes.pool = pool
|
||||
@@ -3559,14 +3568,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
# Remove anything else in the GUI Selected Tab
|
||||
self.app.ui.selected_scroll_area.takeWidget()
|
||||
# Put ourselves in the GUI Selected Tab
|
||||
# Put ourselves in the AppGUI Selected Tab
|
||||
self.app.ui.selected_scroll_area.setWidget(self.geo_edit_widget)
|
||||
# Switch notebook to Selected page
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
|
||||
|
||||
def build_ui(self):
|
||||
"""
|
||||
Build the GUI in the Selected Tab for this editor
|
||||
Build the AppGUI in the Selected Tab for this editor
|
||||
|
||||
:return:
|
||||
"""
|
||||
@@ -3644,10 +3653,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.tool_shape.enabled = True
|
||||
self.app.app_cursor.enabled = True
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(True)
|
||||
self.app.ui.corner_snap_btn.setEnabled(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
|
||||
self.app.ui.geo_editor_menu.setDisabled(False)
|
||||
self.app.ui.geo_editor_menu.menuAction().setVisible(True)
|
||||
@@ -3658,7 +3665,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.app.ui.geo_edit_toolbar.setDisabled(False)
|
||||
self.app.ui.geo_edit_toolbar.setVisible(True)
|
||||
|
||||
self.app.ui.snap_toolbar.setDisabled(False)
|
||||
self.app.ui.status_toolbar.setDisabled(False)
|
||||
|
||||
self.app.ui.popmenu_disable.setVisible(False)
|
||||
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
|
||||
@@ -3675,7 +3682,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
self.item_selected.connect(self.on_geo_elem_selected)
|
||||
|
||||
# ## GUI Events
|
||||
# ## AppGUI Events
|
||||
self.tw.itemSelectionChanged.connect(self.on_tree_selection_change)
|
||||
# self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
|
||||
# self.tw.customContextMenuRequested.connect(self.on_menu_request)
|
||||
@@ -3703,27 +3710,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.app.ui.geo_edit_toolbar.setDisabled(True)
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("layout"):
|
||||
layout = settings.value('layout', type=str)
|
||||
if layout == 'standard':
|
||||
# self.app.ui.geo_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
else:
|
||||
# self.app.ui.geo_edit_toolbar.setVisible(True)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
else:
|
||||
# self.app.ui.geo_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
|
||||
# set the Editor Toolbar visibility to what was before entering in the Editor
|
||||
self.app.ui.geo_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
|
||||
@@ -3757,7 +3745,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
pass
|
||||
|
||||
try:
|
||||
# ## GUI Events
|
||||
# ## AppGUI Events
|
||||
self.tw.itemSelectionChanged.disconnect(self.on_tree_selection_change)
|
||||
# self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
|
||||
# self.tw.customContextMenuRequested.connect(self.on_menu_request)
|
||||
@@ -4100,7 +4088,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
# start with GRID toolbar activated
|
||||
if self.app.ui.grid_snap_btn.isChecked() is False:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
self.app.ui.on_grid_snap_triggered(state=True)
|
||||
|
||||
def on_buffer_tool(self):
|
||||
buff_tool = BufferSelectionTool(self.app, self)
|
||||
@@ -4148,9 +4135,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
# make sure that the cursor shape is enabled/disabled, too
|
||||
if self.options['grid_snap'] is True:
|
||||
self.app.inform[str, bool].emit(_("Grid Snap enabled."), False)
|
||||
self.app.app_cursor.enabled = True
|
||||
else:
|
||||
self.app.app_cursor.enabled = False
|
||||
self.app.inform[str, bool].emit(_("Grid Snap disabled."), False)
|
||||
|
||||
def on_canvas_click(self, event):
|
||||
"""
|
||||
@@ -4173,8 +4162,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.pos = (self.pos[0], self.pos[1])
|
||||
|
||||
if event.button == 1:
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (0, 0))
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (0, 0))
|
||||
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
# If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
|
||||
@@ -4261,18 +4250,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
self.snap_y = y
|
||||
self.app.mouse = [x, y]
|
||||
|
||||
# update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f" % (x, y))
|
||||
|
||||
if self.pos is None:
|
||||
self.pos = (0, 0)
|
||||
self.app.dx = x - self.pos[0]
|
||||
self.app.dy = y - self.pos[1]
|
||||
|
||||
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (self.app.dx, self.app.dy))
|
||||
# # update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f " % (x, y))
|
||||
#
|
||||
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (self.app.dx, self.app.dy))
|
||||
|
||||
units = self.app.defaults["units"].lower()
|
||||
self.app.plotcanvas.text_hud.text = \
|
||||
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
|
||||
self.app.dx, units, self.app.dy, units, x, units, y, units)
|
||||
|
||||
if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
|
||||
pass
|
||||
@@ -4665,7 +4659,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
|
||||
def select_tool(self, toolname):
|
||||
"""
|
||||
Selects a drawing tool. Impacts the object and GUI.
|
||||
Selects a drawing tool. Impacts the object and AppGUI.
|
||||
|
||||
:param toolname: Name of the tool.
|
||||
:return: None
|
||||
@@ -4776,6 +4770,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
||||
new_geo = linemerge(new_geo)
|
||||
fcgeometry.solid_geometry.append(new_geo)
|
||||
|
||||
self.deactivate()
|
||||
|
||||
def update_options(self, obj):
|
||||
if self.paint_tooldia:
|
||||
obj.options['cnctooldia'] = deepcopy(str(self.paint_tooldia))
|
||||
@@ -14,15 +14,13 @@ import shapely.affinity as affinity
|
||||
|
||||
from vispy.geometry import Rect
|
||||
|
||||
import threading
|
||||
import time
|
||||
from copy import copy, deepcopy
|
||||
import logging
|
||||
|
||||
from camlib import distance, arc, three_point_circle
|
||||
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
|
||||
from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
|
||||
EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from AppTool import AppTool
|
||||
|
||||
import numpy as np
|
||||
from numpy.linalg import norm as numpy_norm
|
||||
@@ -32,7 +30,7 @@ import math
|
||||
# import pngcanvas
|
||||
import traceback
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1086,15 +1084,6 @@ class FCRegion(FCShapeTool):
|
||||
|
||||
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.apertures_table.clearSelection()
|
||||
self.draw_app.plot_all()
|
||||
try:
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
def on_key(self, key):
|
||||
# Jump to coords
|
||||
if key == QtCore.Qt.Key_J or key == 'J':
|
||||
@@ -1160,16 +1149,36 @@ class FCRegion(FCShapeTool):
|
||||
|
||||
return msg
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.apertures_table.clearSelection()
|
||||
self.draw_app.plot_all()
|
||||
try:
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
class FCTrack(FCRegion):
|
||||
|
||||
class FCTrack(FCShapeTool):
|
||||
"""
|
||||
Resulting type: Polygon
|
||||
"""
|
||||
def __init__(self, draw_app):
|
||||
FCRegion.__init__(self, draw_app)
|
||||
DrawTool.__init__(self, draw_app)
|
||||
self.name = 'track'
|
||||
self.draw_app = draw_app
|
||||
|
||||
self.steps_per_circle = self.draw_app.app.defaults["gerber_circle_steps"]
|
||||
|
||||
size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size'])
|
||||
self.buf_val = (size_ap / 2) if size_ap > 0 else 0.0000001
|
||||
|
||||
self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
|
||||
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
|
||||
|
||||
self.temp_points = []
|
||||
|
||||
self. final_click = False
|
||||
try:
|
||||
QtGui.QGuiApplication.restoreOverrideCursor()
|
||||
except Exception as e:
|
||||
@@ -1183,52 +1192,23 @@ class FCTrack(FCRegion):
|
||||
|
||||
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
|
||||
|
||||
def make(self):
|
||||
new_geo_el = {}
|
||||
if len(self.temp_points) == 1:
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
new_geo_el['follow'] = Point(self.temp_points)
|
||||
else:
|
||||
new_geo_el['solid'] = (LineString(self.temp_points).buffer(
|
||||
self.buf_val, resolution=int(self.steps_per_circle / 4))).buffer(0)
|
||||
new_geo_el['follow'] = LineString(self.temp_points)
|
||||
|
||||
self.geometry = DrawToolShape(new_geo_el)
|
||||
|
||||
self.draw_app.in_action = False
|
||||
self.complete = True
|
||||
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
|
||||
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.apertures_table.clearSelection()
|
||||
self.draw_app.plot_all()
|
||||
try:
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
def click(self, point):
|
||||
self.draw_app.in_action = True
|
||||
try:
|
||||
if point != self.points[-1]:
|
||||
|
||||
if not self.points:
|
||||
self.points.append(point)
|
||||
except IndexError:
|
||||
elif point != self.points[-1]:
|
||||
self.points.append(point)
|
||||
else:
|
||||
return
|
||||
|
||||
new_geo_el = {}
|
||||
|
||||
if len(self.temp_points) == 1:
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
new_geo_el['follow'] = Point(self.temp_points)
|
||||
else:
|
||||
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
new_geo_el['follow'] = LineString(self.temp_points)
|
||||
|
||||
self.draw_app.add_gerber_shape(DrawToolShape(new_geo_el),
|
||||
@@ -1241,23 +1221,25 @@ class FCTrack(FCRegion):
|
||||
|
||||
return ""
|
||||
|
||||
def update_grid_info(self):
|
||||
self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
|
||||
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
|
||||
|
||||
def utility_geometry(self, data=None):
|
||||
self.update_grid_info()
|
||||
new_geo_el = {}
|
||||
|
||||
if len(self.points) == 0:
|
||||
new_geo_el['solid'] = Point(data).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
|
||||
if not self.points:
|
||||
new_geo_el['solid'] = Point(data).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
return DrawToolUtilityShape(new_geo_el)
|
||||
elif len(self.points) > 0:
|
||||
|
||||
self.temp_points = [self.points[-1]]
|
||||
else:
|
||||
old_x = self.points[-1][0]
|
||||
old_y = self.points[-1][1]
|
||||
x = data[0]
|
||||
y = data[1]
|
||||
|
||||
self.temp_points = [self.points[-1]]
|
||||
|
||||
mx = abs(round((x - old_x) / self.gridx_size))
|
||||
my = abs(round((y - old_y) / self.gridy_size))
|
||||
|
||||
@@ -1305,14 +1287,30 @@ class FCTrack(FCRegion):
|
||||
|
||||
self.temp_points.append(data)
|
||||
if len(self.temp_points) == 1:
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
return DrawToolUtilityShape(new_geo_el)
|
||||
|
||||
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
|
||||
resolution=int(self.steps_per_circle / 4))
|
||||
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
return DrawToolUtilityShape(new_geo_el)
|
||||
|
||||
def make(self):
|
||||
new_geo_el = {}
|
||||
if len(self.temp_points) == 1:
|
||||
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
new_geo_el['follow'] = Point(self.temp_points)
|
||||
else:
|
||||
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
|
||||
new_geo_el['solid'] = new_geo_el['solid'].buffer(0) # try to clean the geometry
|
||||
new_geo_el['follow'] = LineString(self.temp_points)
|
||||
|
||||
self.geometry = DrawToolShape(new_geo_el)
|
||||
|
||||
self.draw_app.in_action = False
|
||||
self.complete = True
|
||||
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
|
||||
|
||||
def on_key(self, key):
|
||||
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
|
||||
if len(self.points) > 0:
|
||||
@@ -1405,6 +1403,15 @@ class FCTrack(FCRegion):
|
||||
|
||||
return msg
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.apertures_table.clearSelection()
|
||||
self.draw_app.plot_all()
|
||||
try:
|
||||
self.draw_app.app.jump_signal.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
class FCDisc(FCShapeTool):
|
||||
"""
|
||||
@@ -2955,7 +2962,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
# this var will store the state of the toolbar before starting the editor
|
||||
self.toolbar_old_state = False
|
||||
|
||||
# Init GUI
|
||||
# Init AppGUI
|
||||
self.apdim_lbl.hide()
|
||||
self.apdim_entry.hide()
|
||||
self.gerber_obj = None
|
||||
@@ -2967,7 +2974,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.tool_shape = self.canvas.new_shape_collection(layers=1)
|
||||
self.ma_annotation = self.canvas.new_text_group()
|
||||
else:
|
||||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_grb_editor')
|
||||
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_grb_editor')
|
||||
self.ma_annotation = ShapeCollectionLegacy(
|
||||
@@ -3110,7 +3117,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.complete = True
|
||||
|
||||
self.set_ui()
|
||||
log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
|
||||
log.debug("Initialization of the Gerber Editor is finished ...")
|
||||
|
||||
def pool_recreated(self, pool):
|
||||
self.shapes.pool = pool
|
||||
@@ -3139,7 +3146,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
tt_aperture = self.sorted_apcode[i]
|
||||
self.tid2apcode[i + 1] = tt_aperture
|
||||
|
||||
# Init GUI
|
||||
# Init AppGUI
|
||||
|
||||
self.buffer_distance_entry.set_value(self.app.defaults["gerber_editor_buff_f"])
|
||||
self.scale_factor_entry.set_value(self.app.defaults["gerber_editor_scale_f"])
|
||||
@@ -3685,10 +3692,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.shapes.enabled = True
|
||||
self.tool_shape.enabled = True
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(True)
|
||||
self.app.ui.corner_snap_btn.setEnabled(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
|
||||
self.app.ui.grb_editor_menu.setDisabled(False)
|
||||
self.app.ui.grb_editor_menu.menuAction().setVisible(True)
|
||||
@@ -3698,12 +3703,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
|
||||
self.app.ui.grb_edit_toolbar.setDisabled(False)
|
||||
self.app.ui.grb_edit_toolbar.setVisible(True)
|
||||
# self.app.ui.snap_toolbar.setDisabled(False)
|
||||
# self.app.ui.status_toolbar.setDisabled(False)
|
||||
|
||||
# start with GRID toolbar activated
|
||||
if self.app.ui.grid_snap_btn.isChecked() is False:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
self.app.ui.on_grid_snap_triggered(state=True)
|
||||
|
||||
# adjust the visibility of some of the canvas context menu
|
||||
self.app.ui.popmenu_edit.setVisible(False)
|
||||
@@ -3736,29 +3740,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.app.ui.grb_edit_toolbar.setDisabled(True)
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("layout"):
|
||||
layout = settings.value('layout', type=str)
|
||||
if layout == 'standard':
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
else:
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(True)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
else:
|
||||
# self.app.ui.exc_edit_toolbar.setVisible(False)
|
||||
|
||||
self.app.ui.snap_max_dist_entry.setEnabled(False)
|
||||
self.app.ui.corner_snap_btn.setEnabled(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
|
||||
# set the Editor Toolbar visibility to what was before entering in the Editor
|
||||
self.app.ui.grb_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
|
||||
@@ -4210,7 +4193,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
|
||||
def on_multiprocessing_finished(self):
|
||||
self.app.proc_container.update_view_text(' %s' % _("Setting up the UI"))
|
||||
self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the GUI"))
|
||||
self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the AppGUI"))
|
||||
self.set_ui()
|
||||
self.build_ui(first_run=True)
|
||||
self.plot_all()
|
||||
@@ -4245,6 +4228,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
new_grb_name = self.edited_obj_name + "_edit"
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [new_grb_name, self.storage_dict]})
|
||||
# self.new_edited_gerber(new_grb_name, self.storage_dict)
|
||||
|
||||
@staticmethod
|
||||
def update_options(obj):
|
||||
@@ -4269,6 +4253,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
:param outname: Name of the resulting object. None causes the name to be that of the file.
|
||||
:type outname: str
|
||||
:param aperture_storage: a dictionary that holds all the objects geometry
|
||||
:type aperture_storage: dict
|
||||
:return: None
|
||||
"""
|
||||
|
||||
@@ -4360,26 +4345,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("There are no Aperture definitions in the file. Aborting Gerber creation."))
|
||||
except Exception:
|
||||
msg = '[ERROR] %s' % \
|
||||
_("An internal error has occurred. See shell.\n")
|
||||
msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
|
||||
msg += traceback.format_exc()
|
||||
app_obj.inform.emit(msg)
|
||||
raise
|
||||
|
||||
grb_obj.source_file = self.app.export_gerber(obj_name=out_name, filename=None,
|
||||
local_use=grb_obj, use_thread=False)
|
||||
|
||||
with self.app.proc_container.new(_("Creating Gerber.")):
|
||||
try:
|
||||
self.app.new_object("gerber", outname, obj_init)
|
||||
self.app.app_obj.new_object("gerber", outname, obj_init)
|
||||
except Exception as e:
|
||||
log.error("Error on Edited object creation: %s" % str(e))
|
||||
# make sure to clean the previous results
|
||||
self.results = []
|
||||
return
|
||||
|
||||
self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
|
||||
# make sure to clean the previous results
|
||||
self.results = []
|
||||
self.deactivate_grb_editor()
|
||||
self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
|
||||
|
||||
def on_tool_select(self, tool):
|
||||
"""
|
||||
@@ -4537,8 +4523,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.pos = (self.pos[0], self.pos[1])
|
||||
|
||||
if event.button == 1:
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (0, 0))
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (0, 0))
|
||||
|
||||
# Selection with left mouse button
|
||||
if self.active_tool is not None:
|
||||
@@ -4550,8 +4536,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.app.defaults["global_point_clipboard_format"] %
|
||||
(self.decimals, self.pos[0], self.decimals, self.pos[1])
|
||||
)
|
||||
self.app.inform.emit('[success] %s' %
|
||||
_("Coordinates copied to clipboard."))
|
||||
self.app.inform.emit('[success] %s' % _("Coordinates copied to clipboard."))
|
||||
return
|
||||
|
||||
# Dispatch event to active_tool
|
||||
@@ -4562,6 +4547,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
if self.current_storage is not None:
|
||||
self.on_grb_shape_complete(self.current_storage)
|
||||
self.build_ui()
|
||||
|
||||
# MS: always return to the Select Tool if modifier key is not pressed
|
||||
# else return to the current tool
|
||||
key_modifier = QtWidgets.QApplication.keyboardModifiers()
|
||||
@@ -4569,6 +4555,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
modifier_to_use = Qt.ControlModifier
|
||||
else:
|
||||
modifier_to_use = Qt.ShiftModifier
|
||||
|
||||
# if modifier key is pressed then we add to the selected list the current shape but if it's already
|
||||
# in the selected list, we removed it. Therefore first click selects, second deselects.
|
||||
if key_modifier == modifier_to_use:
|
||||
@@ -4629,12 +4616,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
# if right click on canvas and the active tool need to be finished (like Path or Polygon)
|
||||
# right mouse click will finish the action
|
||||
if isinstance(self.active_tool, FCShapeTool):
|
||||
if isinstance(self.active_tool, FCTrack):
|
||||
self.active_tool.make()
|
||||
else:
|
||||
self.active_tool.click(self.app.geo_editor.snap(self.x, self.y))
|
||||
self.active_tool.make()
|
||||
if self.active_tool.complete:
|
||||
self.on_grb_shape_complete()
|
||||
self.app.inform.emit('[success] %s' %
|
||||
_("Done."))
|
||||
self.app.inform.emit('[success] %s' % _("Done."))
|
||||
|
||||
# MS: always return to the Select Tool if modifier key is not pressed
|
||||
# else return to the current tool but not for FCTrack
|
||||
@@ -4774,18 +4763,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
|
||||
self.app.mouse = [x, y]
|
||||
|
||||
# update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f" % (x, y))
|
||||
|
||||
if self.pos is None:
|
||||
self.pos = (0, 0)
|
||||
self.app.dx = x - self.pos[0]
|
||||
self.app.dy = y - self.pos[1]
|
||||
|
||||
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (self.app.dx, self.app.dy))
|
||||
# # update the position label in the infobar since the APP mouse event handlers are disconnected
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f " % (x, y))
|
||||
#
|
||||
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
|
||||
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
# "%.4f " % (self.app.dx, self.app.dy))
|
||||
|
||||
units = self.app.defaults["units"].lower()
|
||||
self.app.plotcanvas.text_hud.text = \
|
||||
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
|
||||
self.app.dx, units, self.app.dy, units, x, units, y, units)
|
||||
|
||||
self.update_utility_geometry(data=(x, y))
|
||||
|
||||
@@ -5032,7 +5026,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
|
||||
def select_tool(self, toolname):
|
||||
"""
|
||||
Selects a drawing tool. Impacts the object and GUI.
|
||||
Selects a drawing tool. Impacts the object and AppGUI.
|
||||
|
||||
:param toolname: Name of the tool.
|
||||
:return: None
|
||||
@@ -5298,7 +5292,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
|
||||
|
||||
|
||||
class TransformEditorTool(FlatCAMTool):
|
||||
class TransformEditorTool(AppTool):
|
||||
"""
|
||||
Inputs to specify how to paint the selected polygons.
|
||||
"""
|
||||
@@ -5311,7 +5305,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
offsetName = _("Offset")
|
||||
|
||||
def __init__(self, app, draw_app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
AppTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.draw_app = draw_app
|
||||
@@ -5633,7 +5627,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
"the 'y' in (x, y) will be used when using Flip on Y.")
|
||||
)
|
||||
self.flip_ref_label.setMinimumWidth(50)
|
||||
self.flip_ref_entry = EvalEntry2("(0, 0)")
|
||||
self.flip_ref_entry = FCEntry()
|
||||
self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.flip_ref_entry.setFixedWidth(60)
|
||||
|
||||
@@ -5697,13 +5691,13 @@ class TransformEditorTool(FlatCAMTool):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
FlatCAMTool.run(self)
|
||||
AppTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
|
||||
AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
# Initialize form
|
||||
@@ -5760,7 +5754,7 @@ class TransformEditorTool(FlatCAMTool):
|
||||
if self.app.defaults["tools_transform_mirror_point"]:
|
||||
self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
|
||||
else:
|
||||
self.flip_ref_entry.set_value((0, 0))
|
||||
self.flip_ref_entry.set_value("0, 0")
|
||||
|
||||
def template(self):
|
||||
if not self.draw_app.selected:
|
||||
@@ -5,7 +5,7 @@
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from flatcamGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
|
||||
from AppGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
|
||||
from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui
|
||||
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph
|
||||
@@ -15,7 +15,7 @@ from reportlab.lib.units import inch, mm
|
||||
# from io import StringIO
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -214,10 +214,10 @@ class TextEditor(QtWidgets.QWidget):
|
||||
filename = str(FCFileSaveDialog.get_saved_filename(
|
||||
caption=_("Export Code ..."),
|
||||
directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
|
||||
filter=_filter_
|
||||
ext_filter=_filter_
|
||||
)[0])
|
||||
except TypeError:
|
||||
filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), filter=_filter_)[0])
|
||||
filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
|
||||
|
||||
if filename == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
|
||||
@@ -20,9 +20,10 @@ from copy import copy
|
||||
import re
|
||||
import logging
|
||||
import html
|
||||
import sys
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
log = logging.getLogger('base')
|
||||
@@ -569,9 +570,13 @@ class FCEntry3(FCEntry):
|
||||
|
||||
|
||||
class EvalEntry(QtWidgets.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, border_color=None, parent=None):
|
||||
super(EvalEntry, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
|
||||
if border_color:
|
||||
self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
|
||||
|
||||
self.editingFinished.connect(self.on_edit_finished)
|
||||
|
||||
def on_edit_finished(self):
|
||||
@@ -598,7 +603,6 @@ class EvalEntry(QtWidgets.QLineEdit):
|
||||
|
||||
def get_value(self):
|
||||
raw = str(self.text()).strip(' ')
|
||||
evaled = 0.0
|
||||
try:
|
||||
evaled = eval(raw)
|
||||
except Exception as e:
|
||||
@@ -638,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
|
||||
|
||||
def get_value(self):
|
||||
raw = str(self.text()).strip(' ')
|
||||
evaled = 0.0
|
||||
|
||||
try:
|
||||
evaled = eval(raw)
|
||||
except Exception as e:
|
||||
@@ -655,6 +659,30 @@ class EvalEntry2(QtWidgets.QLineEdit):
|
||||
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
|
||||
|
||||
|
||||
class NumericalEvalEntry(EvalEntry):
|
||||
"""
|
||||
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
|
||||
"""
|
||||
def __init__(self, border_color=None):
|
||||
super().__init__(border_color=border_color)
|
||||
|
||||
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
|
||||
validator = QtGui.QRegExpValidator(regex, self)
|
||||
self.setValidator(validator)
|
||||
|
||||
|
||||
class NumericalEvalTupleEntry(FCEntry):
|
||||
"""
|
||||
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
|
||||
"""
|
||||
def __init__(self, border_color=None):
|
||||
super().__init__(border_color=border_color)
|
||||
|
||||
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
|
||||
validator = QtGui.QRegExpValidator(regex, self)
|
||||
self.setValidator(validator)
|
||||
|
||||
|
||||
class FCSpinner(QtWidgets.QSpinBox):
|
||||
|
||||
returnPressed = QtCore.pyqtSignal()
|
||||
@@ -683,6 +711,8 @@ class FCSpinner(QtWidgets.QSpinBox):
|
||||
self.setAlignment(align_val)
|
||||
|
||||
self.prev_readyToEdit = True
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
|
||||
self.setSizePolicy(sizePolicy)
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QtCore.QEvent.MouseButtonPress and self.prev_readyToEdit is True:
|
||||
@@ -815,6 +845,8 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
|
||||
self.setAlignment(align_val)
|
||||
|
||||
self.prev_readyToEdit = True
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
|
||||
self.setSizePolicy(sizePolicy)
|
||||
|
||||
def on_edit_finished(self):
|
||||
self.clearFocus()
|
||||
@@ -1336,7 +1368,8 @@ class FCComboBox(QtWidgets.QComboBox):
|
||||
return str(self.currentText())
|
||||
|
||||
def set_value(self, val):
|
||||
self.setCurrentIndex(self.findText(str(val)))
|
||||
idx = self.findText(str(val))
|
||||
self.setCurrentIndex(idx)
|
||||
|
||||
@property
|
||||
def is_last(self):
|
||||
@@ -1480,9 +1513,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
From here:
|
||||
https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
|
||||
"""
|
||||
tab_detached = QtCore.pyqtSignal(str)
|
||||
tab_attached = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, protect=None, protect_by_name=None, parent=None):
|
||||
super().__init__()
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.tabBar = self.FCTabBar(self)
|
||||
self.tabBar.onMoveTabSignal.connect(self.moveTab)
|
||||
@@ -1619,7 +1654,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
self.insertTab(toIndex, widget, icon, text)
|
||||
self.setCurrentIndex(toIndex)
|
||||
|
||||
@pyqtSlot(int, QtCore.QPoint)
|
||||
# @pyqtSlot(int, QtCore.QPoint)
|
||||
def detachTab(self, index, point):
|
||||
"""
|
||||
Detach the tab by removing it's contents and placing them in
|
||||
@@ -1656,6 +1691,8 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
# Create a reference to maintain access to the detached tab
|
||||
self.detachedTabs[name] = detachedTab
|
||||
|
||||
self.tab_detached.emit(name)
|
||||
|
||||
def attachTab(self, contentWidget, name, icon, insertAt=None):
|
||||
"""
|
||||
Re-attach the tab by removing the content from the DetachedTab window,
|
||||
@@ -1668,11 +1705,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
:return:
|
||||
"""
|
||||
|
||||
old_name = name
|
||||
|
||||
# Make the content widget a child of this widget
|
||||
contentWidget.setParent(self)
|
||||
|
||||
# Remove the reference
|
||||
del self.detachedTabs[name]
|
||||
# make sure that we strip the 'FlatCAM' part of the detached name otherwise the tab name will be too long
|
||||
name = name.partition(' ')[2]
|
||||
|
||||
@@ -1712,6 +1749,9 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
else:
|
||||
index = self.insertTab(insert_index, contentWidget, icon, name)
|
||||
|
||||
obj_name = contentWidget.objectName()
|
||||
self.tab_attached.emit(obj_name)
|
||||
|
||||
# on reattaching the tab if protect is true then the closure button is not added
|
||||
if self.protect_tab is True:
|
||||
self.protectTab(index)
|
||||
@@ -1727,6 +1767,14 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
||||
if index > -1:
|
||||
self.setCurrentIndex(insert_index) if self.use_old_index else self.setCurrentIndex(index)
|
||||
|
||||
# Remove the reference
|
||||
# Unix-like OS's crash with segmentation fault after this. FOr whatever reason, they loose reference
|
||||
if sys.platform == 'win32':
|
||||
try:
|
||||
del self.detachedTabs[old_name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def removeTabByName(self, name):
|
||||
"""
|
||||
Remove the tab with the given name, even if it is detached
|
||||
@@ -2180,10 +2228,13 @@ class OptionalHideInputSection:
|
||||
Associates the a checkbox with a set of inputs.
|
||||
|
||||
:param cb: Checkbox that enables the optional inputs.
|
||||
:type cb: QtWidgets.QCheckBox
|
||||
:param optinputs: List of widgets that are optional.
|
||||
:type optinputs: list
|
||||
:param logic: When True the logic is normal, when False the logic is in reverse
|
||||
It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
|
||||
for logic=False, when the checkbox is checked the widgets are Disabled
|
||||
:type logic: bool
|
||||
:return:
|
||||
"""
|
||||
assert isinstance(cb, FCCheckBox), \
|
||||
@@ -2215,6 +2266,7 @@ class OptionalHideInputSection:
|
||||
class FCTable(QtWidgets.QTableWidget):
|
||||
|
||||
drag_drop_sig = QtCore.pyqtSignal()
|
||||
lost_focus = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, drag_drop=False, protected_rows=None, parent=None):
|
||||
super(FCTable, self).__init__(parent)
|
||||
@@ -2278,6 +2330,10 @@ class FCTable(QtWidgets.QTableWidget):
|
||||
else:
|
||||
QtWidgets.QTableWidget.mousePressEvent(self, event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
self.lost_focus.emit()
|
||||
super().focusOutEvent(event)
|
||||
|
||||
def setupContextMenu(self):
|
||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
|
||||
@@ -2449,7 +2505,8 @@ class SpinBoxDelegate(QtWidgets.QItemDelegate):
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
editor.setGeometry(option.rect)
|
||||
|
||||
def setDecimals(self, spinbox, digits):
|
||||
@staticmethod
|
||||
def setDecimals(spinbox, digits):
|
||||
spinbox.setDecimals(digits)
|
||||
|
||||
|
||||
@@ -2497,7 +2554,7 @@ class DialogBoxRadio(QtWidgets.QDialog):
|
||||
:param title: string with the window title
|
||||
:param label: string with the message inside the dialog box
|
||||
"""
|
||||
super(DialogBoxRadio, self).__init__()
|
||||
super(DialogBoxRadio, self).__init__(parent=parent)
|
||||
if initial_text is None:
|
||||
self.location = str((0, 0))
|
||||
else:
|
||||
@@ -2740,9 +2797,12 @@ class MyCompleter(QCompleter):
|
||||
insertText = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QCompleter.__init__(self)
|
||||
QCompleter.__init__(self, parent=parent)
|
||||
self.setCompletionMode(QCompleter.PopupCompletion)
|
||||
self.highlighted.connect(self.setHighlighted)
|
||||
|
||||
self.lastSelected = ''
|
||||
|
||||
# self.popup().installEventFilter(self)
|
||||
|
||||
# def eventFilter(self, obj, event):
|
||||
@@ -2898,9 +2958,9 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
|
||||
super(FCFileSaveDialog, self).__init__(*args)
|
||||
|
||||
@staticmethod
|
||||
def get_saved_filename(parent=None, caption='', directory='', filter='', initialFilter=''):
|
||||
def get_saved_filename(parent=None, caption='', directory='', ext_filter='', initialFilter=''):
|
||||
filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
|
||||
directory=directory, filter=filter,
|
||||
directory=directory, filter=ext_filter,
|
||||
initialFilter=initialFilter)
|
||||
|
||||
filename = str(filename)
|
||||
@@ -2916,6 +2976,225 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
|
||||
return filename, _filter
|
||||
|
||||
|
||||
class FCDock(QtWidgets.QDockWidget):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FCDock, self).__init__(*args)
|
||||
self.close_callback = kwargs["close_callback"] if "close_callback" in kwargs else None
|
||||
|
||||
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
|
||||
self.close_callback()
|
||||
super().closeEvent(event)
|
||||
|
||||
def show(self) -> None:
|
||||
if self.isFloating():
|
||||
self.setFloating(False)
|
||||
super().show()
|
||||
|
||||
|
||||
class FlatCAMActivityView(QtWidgets.QWidget):
|
||||
"""
|
||||
This class create and control the activity icon displayed in the App status bar
|
||||
"""
|
||||
|
||||
def __init__(self, app, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.app = app
|
||||
|
||||
if self.app.defaults["global_activity_icon"] == "Ball green":
|
||||
icon = self.app.resource_location + '/active_2_static.png'
|
||||
movie = self.app.resource_location + "/active_2.gif"
|
||||
elif self.app.defaults["global_activity_icon"] == "Ball black":
|
||||
icon = self.app.resource_location + '/active_static.png'
|
||||
movie = self.app.resource_location + "/active.gif"
|
||||
elif self.app.defaults["global_activity_icon"] == "Arrow green":
|
||||
icon = self.app.resource_location + '/active_3_static.png'
|
||||
movie = self.app.resource_location + "/active_3.gif"
|
||||
elif self.app.defaults["global_activity_icon"] == "Eclipse green":
|
||||
icon = self.app.resource_location + '/active_4_static.png'
|
||||
movie = self.app.resource_location + "/active_4.gif"
|
||||
else:
|
||||
icon = self.app.resource_location + '/active_static.png'
|
||||
movie = self.app.resource_location + "/active.gif"
|
||||
|
||||
self.setMinimumWidth(200)
|
||||
self.movie_path = movie
|
||||
self.icon_path = icon
|
||||
|
||||
self.icon = FCLabel(self)
|
||||
self.icon.setGeometry(0, 0, 16, 12)
|
||||
self.movie = QtGui.QMovie(self.movie_path)
|
||||
|
||||
self.icon.setMovie(self.movie)
|
||||
# self.movie.start()
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(5, 0, 5, 0)
|
||||
layout.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(self.icon)
|
||||
self.text = QtWidgets.QLabel(self)
|
||||
self.text.setText(_("Idle."))
|
||||
self.icon.setPixmap(QtGui.QPixmap(self.icon_path))
|
||||
|
||||
layout.addWidget(self.text)
|
||||
|
||||
self.icon.clicked.connect(self.app.on_toolbar_replot)
|
||||
|
||||
def set_idle(self):
|
||||
self.movie.stop()
|
||||
self.text.setText(_("Idle."))
|
||||
|
||||
def set_busy(self, msg, no_movie=None):
|
||||
if no_movie is not True:
|
||||
self.icon.setMovie(self.movie)
|
||||
self.movie.start()
|
||||
self.text.setText(msg)
|
||||
|
||||
|
||||
class FlatCAMInfoBar(QtWidgets.QWidget):
|
||||
"""
|
||||
This class create a place to display the App messages in the Status Bar
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, app=None):
|
||||
super(FlatCAMInfoBar, self).__init__(parent=parent)
|
||||
|
||||
self.app = app
|
||||
|
||||
self.icon = QtWidgets.QLabel(self)
|
||||
self.icon.setGeometry(0, 0, 12, 12)
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
|
||||
self.icon.setPixmap(self.pmap)
|
||||
|
||||
self.lock_pmaps = False
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(self.icon)
|
||||
|
||||
self.text = QtWidgets.QLabel(self)
|
||||
self.text.setText(_("Application started ..."))
|
||||
self.text.setToolTip(_("Hello!"))
|
||||
|
||||
layout.addWidget(self.text)
|
||||
layout.addStretch()
|
||||
|
||||
def set_text_(self, text, color=None):
|
||||
self.text.setText(text)
|
||||
self.text.setToolTip(text)
|
||||
if color:
|
||||
self.text.setStyleSheet('color: %s' % str(color))
|
||||
|
||||
def set_status(self, text, level="info"):
|
||||
level = str(level)
|
||||
|
||||
if self.lock_pmaps is not True:
|
||||
self.pmap.fill()
|
||||
if level == "ERROR" or level == "ERROR_NOTCL":
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/redlight12.png')
|
||||
elif level.lower() == "success":
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/greenlight12.png')
|
||||
elif level == "WARNING" or level == "WARNING_NOTCL":
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/yellowlight12.png')
|
||||
elif level.lower() == "selected":
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/bluelight12.png')
|
||||
else:
|
||||
self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
|
||||
|
||||
try:
|
||||
self.set_text_(text)
|
||||
self.icon.setPixmap(self.pmap)
|
||||
except Exception as e:
|
||||
log.debug("FlatCAMInfoBar.set_status() --> %s" % str(e))
|
||||
|
||||
|
||||
class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon):
|
||||
"""
|
||||
This class create the Sys Tray icon for the app
|
||||
"""
|
||||
|
||||
def __init__(self, app, icon, headless=None, parent=None):
|
||||
# QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
|
||||
super().__init__(icon, parent=parent)
|
||||
self.app = app
|
||||
|
||||
menu = QtWidgets.QMenu(parent)
|
||||
|
||||
menu_runscript = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/script14.png'),
|
||||
'%s' % _('Run Script ...'), self)
|
||||
menu_runscript.setToolTip(
|
||||
_("Will run the opened Tcl Script thus\n"
|
||||
"enabling the automation of certain\n"
|
||||
"functions of FlatCAM.")
|
||||
)
|
||||
menu.addAction(menu_runscript)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
if headless is None:
|
||||
self.menu_open = menu.addMenu(QtGui.QIcon(self.app.resource_location + '/folder32_bis.png'), _('Open'))
|
||||
|
||||
# Open Project ...
|
||||
menu_openproject = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/folder16.png'),
|
||||
_('Open Project ...'), self)
|
||||
self.menu_open.addAction(menu_openproject)
|
||||
self.menu_open.addSeparator()
|
||||
|
||||
# Open Gerber ...
|
||||
menu_opengerber = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/flatcam_icon24.png'),
|
||||
_('Open &Gerber ...\tCtrl+G'), self)
|
||||
self.menu_open.addAction(menu_opengerber)
|
||||
|
||||
# Open Excellon ...
|
||||
menu_openexcellon = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'),
|
||||
_('Open &Excellon ...\tCtrl+E'), self)
|
||||
self.menu_open.addAction(menu_openexcellon)
|
||||
|
||||
# Open G-Code ...
|
||||
menu_opengcode = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/code.png'),
|
||||
_('Open G-&Code ...'), self)
|
||||
self.menu_open.addAction(menu_opengcode)
|
||||
|
||||
self.menu_open.addSeparator()
|
||||
|
||||
menu_openproject.triggered.connect(self.app.on_file_openproject)
|
||||
menu_opengerber.triggered.connect(self.app.on_fileopengerber)
|
||||
menu_openexcellon.triggered.connect(self.app.on_fileopenexcellon)
|
||||
menu_opengcode.triggered.connect(self.app.on_fileopengcode)
|
||||
|
||||
exitAction = menu.addAction(_("Exit"))
|
||||
exitAction.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png'))
|
||||
self.setContextMenu(menu)
|
||||
|
||||
menu_runscript.triggered.connect(lambda: self.app.on_filerunscript(
|
||||
silent=True if self.app.cmd_line_headless == 1 else False))
|
||||
|
||||
exitAction.triggered.connect(self.app.final_save)
|
||||
|
||||
|
||||
def message_dialog(title, message, kind="info", parent=None):
|
||||
"""
|
||||
Builds and show a custom QMessageBox to be used in FlatCAM.
|
||||
|
||||
:param title: title of the QMessageBox
|
||||
:param message: message to be displayed
|
||||
:param kind: type of QMessageBox; will display a specific icon.
|
||||
:param parent: parent
|
||||
:return: None
|
||||
"""
|
||||
icon = {"info": QtWidgets.QMessageBox.Information,
|
||||
"warning": QtWidgets.QMessageBox.Warning,
|
||||
"error": QtWidgets.QMessageBox.Critical}[str(kind)]
|
||||
dlg = QtWidgets.QMessageBox(icon, title, message, parent=parent)
|
||||
dlg.setText(message)
|
||||
dlg.exec_()
|
||||
|
||||
|
||||
def rreplace(s, old, new, occurrence):
|
||||
"""
|
||||
Credits go here:
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,21 @@
|
||||
from PyQt5 import QtCore
|
||||
|
||||
import logging
|
||||
from flatcamGUI.VisPyCanvas import VisPyCanvas, Color
|
||||
from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
|
||||
from vispy.scene.visuals import InfiniteLine, Line
|
||||
from AppGUI.VisPyCanvas import VisPyCanvas, Color
|
||||
from AppGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
|
||||
from vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
import numpy as np
|
||||
from vispy.geometry import Rect
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
||||
@@ -54,8 +62,12 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
|
||||
if theme == 'white':
|
||||
self.line_color = (0.3, 0.0, 0.0, 1.0)
|
||||
self.rect_hud_color = Color('#0000FF10')
|
||||
self.text_hud_color = 'black'
|
||||
else:
|
||||
self.line_color = (0.4, 0.4, 0.4, 1.0)
|
||||
self.rect_hud_color = Color('#80808040')
|
||||
self.text_hud_color = 'white'
|
||||
|
||||
# workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
|
||||
# which might decrease performance
|
||||
@@ -129,11 +141,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
|
||||
parent=self.view.scene)
|
||||
|
||||
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
||||
# all CNC have a limited workspace
|
||||
if self.fcapp.defaults['global_workspace'] is True:
|
||||
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
|
||||
|
||||
self.line_parent = None
|
||||
if self.fcapp.defaults["global_cursor_color_enabled"]:
|
||||
c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
|
||||
@@ -146,13 +153,61 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
|
||||
parent=self.line_parent)
|
||||
|
||||
# font size
|
||||
qsettings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if qsettings.contains("hud_font_size"):
|
||||
fsize = qsettings.value('hud_font_size', type=int)
|
||||
else:
|
||||
fsize = 8
|
||||
|
||||
# units
|
||||
units = self.fcapp.defaults["units"].lower()
|
||||
|
||||
# coordinates and anchors
|
||||
height = fsize * 11 # 90. Constant 11 is something that works
|
||||
width = height * 2 # width is double the height = it is something that works
|
||||
center_x = (width / 2) + 5
|
||||
center_y = (height / 2) + 5
|
||||
|
||||
# text
|
||||
self.text_hud = Text('', color=self.text_hud_color, pos=(10, center_y), method='gpu', anchor_x='left',
|
||||
parent=None)
|
||||
self.text_hud.font_size = fsize
|
||||
self.text_hud.text = 'Dx:\t%s [%s]\nDy:\t%s [%s]\n\nX: \t%s [%s]\nY: \t%s [%s]' % \
|
||||
('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
|
||||
|
||||
# rectangle
|
||||
self.rect_hud = Rectangle(center=(center_x, center_y), width=width, height=height, radius=[5, 5, 5, 5],
|
||||
border_color=self.rect_hud_color, color=self.rect_hud_color, parent=None)
|
||||
self.rect_hud.set_gl_state(depth_test=False)
|
||||
|
||||
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
||||
# all CNC have a limited workspace
|
||||
if self.fcapp.defaults['global_workspace'] is True:
|
||||
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
|
||||
|
||||
# HUD Display
|
||||
self.hud_enabled = False
|
||||
|
||||
# enable the HUD if it is activated in FlatCAM Preferences
|
||||
if self.fcapp.defaults['global_hud'] is True:
|
||||
self.on_toggle_hud(state=True)
|
||||
|
||||
# Axis Display
|
||||
self.axis_enabled = True
|
||||
|
||||
# enable Axis
|
||||
self.on_toggle_axis(state=True)
|
||||
|
||||
# enable Grid lines
|
||||
self.grid_lines_enabled = True
|
||||
|
||||
self.shape_collections = []
|
||||
|
||||
self.shape_collection = self.new_shape_collection()
|
||||
self.fcapp.pool_recreated.connect(self.on_pool_recreated)
|
||||
self.text_collection = self.new_text_collection()
|
||||
|
||||
# TODO: Should be setting to show/hide CNC job annotations (global or per object)
|
||||
self.text_collection.enabled = True
|
||||
|
||||
self.c = None
|
||||
@@ -163,6 +218,76 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
|
||||
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
|
||||
|
||||
def on_toggle_axis(self, signal=None, state=None):
|
||||
if state is None:
|
||||
state = not self.axis_enabled
|
||||
|
||||
if state:
|
||||
self.axis_enabled = True
|
||||
self.v_line.parent = self.view.scene
|
||||
self.h_line.parent = self.view.scene
|
||||
self.fcapp.ui.axis_status_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: peachpuff;
|
||||
}
|
||||
""")
|
||||
self.fcapp.inform[str, bool].emit(_("Axis enabled."), False)
|
||||
else:
|
||||
self.axis_enabled = False
|
||||
self.v_line.parent = None
|
||||
self.h_line.parent = None
|
||||
self.fcapp.ui.axis_status_label.setStyleSheet("")
|
||||
self.fcapp.inform[str, bool].emit(_("Axis disabled."), False)
|
||||
|
||||
def on_toggle_hud(self, signal=None, state=None):
|
||||
if state is None:
|
||||
state = not self.hud_enabled
|
||||
|
||||
if state:
|
||||
self.hud_enabled = True
|
||||
self.rect_hud.parent = self.view
|
||||
self.text_hud.parent = self.view
|
||||
self.fcapp.defaults['global_hud'] = True
|
||||
self.fcapp.ui.hud_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: lightblue;
|
||||
}
|
||||
""")
|
||||
self.fcapp.inform[str, bool].emit(_("HUD enabled."), False)
|
||||
|
||||
else:
|
||||
self.hud_enabled = False
|
||||
self.rect_hud.parent = None
|
||||
self.text_hud.parent = None
|
||||
self.fcapp.defaults['global_hud'] = False
|
||||
self.fcapp.ui.hud_label.setStyleSheet("")
|
||||
self.fcapp.inform[str, bool].emit(_("HUD disabled."), False)
|
||||
|
||||
def on_toggle_grid_lines(self):
|
||||
state = not self.grid_lines_enabled
|
||||
|
||||
if state:
|
||||
self.grid_lines_enabled = True
|
||||
self.grid.parent = self.view.scene
|
||||
self.fcapp.inform[str, bool].emit(_("Grid enabled."), False)
|
||||
else:
|
||||
self.grid_lines_enabled = False
|
||||
self.grid.parent = None
|
||||
self.fcapp.inform[str, bool].emit(_("Grid disabled."), False)
|
||||
|
||||
# HACK: enabling/disabling the cursor seams to somehow update the shapes on screen
|
||||
# - perhaps is a bug in VisPy implementation
|
||||
if self.fcapp.grid_status():
|
||||
self.fcapp.app_cursor.enabled = False
|
||||
self.fcapp.app_cursor.enabled = True
|
||||
else:
|
||||
self.fcapp.app_cursor.enabled = True
|
||||
self.fcapp.app_cursor.enabled = False
|
||||
|
||||
def draw_workspace(self, workspace_size):
|
||||
"""
|
||||
Draw a rectangular shape on canvas to specify our valid workspace.
|
||||
@@ -183,17 +308,30 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
|
||||
a = np.array([(0, 0), (dims[0], 0), (dims[0], dims[1]), (0, dims[1])])
|
||||
|
||||
if not self.workspace_line:
|
||||
# if not self.workspace_line:
|
||||
# self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
|
||||
# antialias=True, method='agg', parent=self.view.scene)
|
||||
# else:
|
||||
# self.workspace_line.parent = self.view.scene
|
||||
self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
|
||||
antialias=True, method='agg', parent=self.view.scene)
|
||||
else:
|
||||
self.workspace_line.parent = self.view.scene
|
||||
|
||||
self.fcapp.ui.wplace_label.set_value(workspace_size[:3])
|
||||
self.fcapp.ui.wplace_label.setToolTip(workspace_size)
|
||||
self.fcapp.ui.wplace_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: lightgreen;
|
||||
}
|
||||
""")
|
||||
|
||||
def delete_workspace(self):
|
||||
try:
|
||||
self.workspace_line.parent = None
|
||||
except Exception:
|
||||
pass
|
||||
self.fcapp.ui.wplace_label.setStyleSheet("")
|
||||
|
||||
# redraw the workspace lines on the plot by re adding them to the parent view.scene
|
||||
def restore_workspace(self):
|
||||
@@ -287,6 +425,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||
def on_mouse_scroll(self, event):
|
||||
# key modifiers
|
||||
modifiers = event.modifiers
|
||||
|
||||
pan_delta_x = self.fcapp.defaults["global_gridx"]
|
||||
pan_delta_y = self.fcapp.defaults["global_gridy"]
|
||||
curr_pos = event.pos
|
||||
@@ -19,8 +19,10 @@ from shapely.geometry import Polygon, LineString, LinearRing
|
||||
from copy import deepcopy
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
# Prevent conflict with Qt5 and above.
|
||||
@@ -29,13 +31,13 @@ mpl_use("Qt5Agg")
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.lines import Line2D
|
||||
from matplotlib.offsetbox import AnchoredText
|
||||
# from matplotlib.widgets import Cursor
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
||||
@@ -45,9 +47,9 @@ class CanvasCache(QtCore.QObject):
|
||||
Case story #1:
|
||||
|
||||
1) No objects in the project.
|
||||
2) Object is created (new_object() emits object_created(obj)).
|
||||
2) Object is created (app_obj.new_object() emits object_created(obj)).
|
||||
on_object_created() adds (i) object to collection and emits
|
||||
(ii) new_object_available() then calls (iii) object.plot()
|
||||
(ii) app_obj.new_object_available() then calls (iii) object.plot()
|
||||
3) object.plot() creates axes if necessary on
|
||||
app.collection.figure. Then plots on it.
|
||||
4) Plots on a cache-size canvas (in background).
|
||||
@@ -113,7 +115,7 @@ class CanvasCache(QtCore.QObject):
|
||||
|
||||
# Continue to update the cache.
|
||||
|
||||
# def on_new_object_available(self):
|
||||
# def on_app_obj.new_object_available(self):
|
||||
#
|
||||
# log.debug("A new object is available. Should plot it!")
|
||||
|
||||
@@ -147,9 +149,13 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
theme_color = '#FFFFFF'
|
||||
tick_color = '#000000'
|
||||
self.rect_hud_color = '#0000FF10'
|
||||
self.text_hud_color = '#000000'
|
||||
else:
|
||||
theme_color = '#000000'
|
||||
tick_color = '#FFFFFF'
|
||||
self.rect_hud_color = '#80808040'
|
||||
self.text_hud_color = '#FFFFFF'
|
||||
|
||||
# workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
|
||||
# which might decrease performance
|
||||
@@ -295,14 +301,163 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
# signal is the mouse is dragging
|
||||
self.is_dragging = False
|
||||
|
||||
self.mouse_press_pos = None
|
||||
|
||||
# signal if there is a doubleclick
|
||||
self.is_dblclk = False
|
||||
|
||||
self.hud_enabled = False
|
||||
self.text_hud = self.Thud(plotcanvas=self)
|
||||
|
||||
# enable Grid lines
|
||||
self.grid_lines_enabled = True
|
||||
|
||||
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
||||
# all CNC have a limited workspace
|
||||
if self.app.defaults['global_workspace'] is True:
|
||||
self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
|
||||
|
||||
if self.app.defaults['global_hud'] is True:
|
||||
self.on_toggle_hud(state=True)
|
||||
|
||||
# Axis Display
|
||||
self.axis_enabled = True
|
||||
|
||||
# enable Axis
|
||||
self.on_toggle_axis(state=True)
|
||||
|
||||
def on_toggle_axis(self, signal=None, state=None):
|
||||
if state is None:
|
||||
state = not self.axis_enabled
|
||||
|
||||
if state:
|
||||
self.axis_enabled = True
|
||||
if self.h_line not in self.axes.lines and self.v_line not in self.axes.lines:
|
||||
self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
|
||||
self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
|
||||
self.app.ui.axis_status_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: peachpuff;
|
||||
}
|
||||
""")
|
||||
self.app.inform[str, bool].emit(_("Axis enabled."), False)
|
||||
else:
|
||||
self.axis_enabled = False
|
||||
if self.h_line in self.axes.lines and self.v_line in self.axes.lines:
|
||||
self.axes.lines.remove(self.h_line)
|
||||
self.axes.lines.remove(self.v_line)
|
||||
self.app.ui.axis_status_label.setStyleSheet("")
|
||||
self.app.inform[str, bool].emit(_("Axis disabled."), False)
|
||||
|
||||
self.canvas.draw()
|
||||
|
||||
def on_toggle_hud(self, signal=None, state=None):
|
||||
if state is None:
|
||||
state = not self.hud_enabled
|
||||
|
||||
if state:
|
||||
self.hud_enabled = True
|
||||
self.text_hud.add_artist()
|
||||
self.app.defaults['global_hud'] = True
|
||||
|
||||
self.app.ui.hud_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: lightblue;
|
||||
}
|
||||
""")
|
||||
self.app.inform[str, bool].emit(_("HUD enabled."), False)
|
||||
else:
|
||||
self.hud_enabled = False
|
||||
self.text_hud.remove_artist()
|
||||
self.app.defaults['global_hud'] = False
|
||||
self.app.ui.hud_label.setStyleSheet("")
|
||||
self.app.inform[str, bool].emit(_("HUD disabled."), False)
|
||||
|
||||
self.canvas.draw()
|
||||
|
||||
class Thud(QtCore.QObject):
|
||||
text_changed = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, plotcanvas):
|
||||
super().__init__()
|
||||
|
||||
self.p = plotcanvas
|
||||
units = self.p.app.defaults['units']
|
||||
self._text = 'Dx: %s [%s]\nDy: %s [%s]\n\nX: %s [%s]\nY: %s [%s]' % \
|
||||
('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
|
||||
|
||||
# set font size
|
||||
qsettings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if qsettings.contains("hud_font_size"):
|
||||
# I multiply with 2.5 because this seems to be the difference between the value taken by the VisPy (3D)
|
||||
# and Matplotlib (Legacy2D FlatCAM graphic engine)
|
||||
fsize = int(qsettings.value('hud_font_size', type=int) * 2.5)
|
||||
else:
|
||||
fsize = 20
|
||||
|
||||
self.hud_holder = AnchoredText(self._text, prop=dict(size=fsize), frameon=True, loc='upper left')
|
||||
self.hud_holder.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
|
||||
|
||||
fc_color = self.p.rect_hud_color[:-2]
|
||||
fc_alpha = int(self.p.rect_hud_color[-2:], 16) / 255
|
||||
text_color = self.p.text_hud_color
|
||||
|
||||
self.hud_holder.patch.set_facecolor(fc_color)
|
||||
self.hud_holder.patch.set_alpha(fc_alpha)
|
||||
self.hud_holder.patch.set_edgecolor((0, 0, 0, 0))
|
||||
|
||||
self. hud_holder.txt._text.set_color(color=text_color)
|
||||
self.text_changed.connect(self.on_text_changed)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, val):
|
||||
self.text_changed.emit(val)
|
||||
self._text = val
|
||||
|
||||
def on_text_changed(self, txt):
|
||||
try:
|
||||
txt = txt.replace('\t', ' ')
|
||||
self.hud_holder.txt.set_text(txt)
|
||||
self.p.canvas.draw()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def add_artist(self):
|
||||
if self.hud_holder not in self.p.axes.artists:
|
||||
self.p.axes.add_artist(self.hud_holder)
|
||||
|
||||
def remove_artist(self):
|
||||
if self.hud_holder in self.p.axes.artists:
|
||||
self.p.axes.artists.remove(self.hud_holder)
|
||||
|
||||
def on_toggle_grid_lines(self):
|
||||
state = not self.grid_lines_enabled
|
||||
|
||||
if state:
|
||||
self.grid_lines_enabled = True
|
||||
self.axes.grid(True)
|
||||
try:
|
||||
self.canvas.draw()
|
||||
except IndexError:
|
||||
pass
|
||||
self.app.inform[str, bool].emit(_("Grid enabled."), False)
|
||||
else:
|
||||
self.grid_lines_enabled = False
|
||||
self.axes.grid(False)
|
||||
try:
|
||||
self.canvas.draw()
|
||||
except IndexError:
|
||||
pass
|
||||
self.app.inform[str, bool].emit(_("Grid disabled."), False)
|
||||
|
||||
def draw_workspace(self, workspace_size):
|
||||
"""
|
||||
Draw a rectangular shape on canvas to specify our valid workspace.
|
||||
@@ -329,12 +484,23 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
self.axes.add_line(self.workspace_line)
|
||||
self.canvas.draw()
|
||||
|
||||
self.app.ui.wplace_label.set_value(workspace_size[:3])
|
||||
self.app.ui.wplace_label.setToolTip(workspace_size)
|
||||
self.fcapp.ui.wplace_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
color: black;
|
||||
background-color: lightgreen;
|
||||
}
|
||||
""")
|
||||
|
||||
def delete_workspace(self):
|
||||
try:
|
||||
self.axes.lines.remove(self.workspace_line)
|
||||
self.canvas.draw()
|
||||
except Exception:
|
||||
pass
|
||||
self.fcapp.ui.wplace_label.setStyleSheet("")
|
||||
|
||||
def graph_event_connect(self, event_name, callback):
|
||||
"""
|
||||
@@ -423,7 +589,7 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
|
||||
if self.big_cursor is False:
|
||||
try:
|
||||
x, y = self.app.geo_editor.snap(x_pos, y_pos)
|
||||
x, y = self.snap(x_pos, y_pos)
|
||||
|
||||
# Pointer (snapped)
|
||||
# The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
|
||||
@@ -781,6 +947,7 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
def on_mouse_press(self, event):
|
||||
|
||||
self.is_dragging = True
|
||||
self.mouse_press_pos = (event.x, event.y)
|
||||
|
||||
# Check for middle mouse button press
|
||||
if self.app.defaults["global_pan_button"] == '2':
|
||||
@@ -806,6 +973,10 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
|
||||
mouse_release_pos = (event.x, event.y)
|
||||
delta = 0.05
|
||||
|
||||
if abs(self.distance(self.mouse_press_pos, mouse_release_pos)) < delta:
|
||||
self.is_dragging = False
|
||||
|
||||
# Check for middle mouse button release to complete pan procedure
|
||||
@@ -858,7 +1029,7 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
self.canvas.draw_idle()
|
||||
|
||||
# #### Temporary place-holder for cached update #####
|
||||
self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
# self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
|
||||
if self.app.defaults["global_cursor_color_enabled"] is True:
|
||||
self.draw_cursor(x_pos=x, y_pos=y, color=self.app.cursor_color_3D)
|
||||
@@ -910,6 +1081,59 @@ class PlotCanvasLegacy(QtCore.QObject):
|
||||
|
||||
return width / xpx, height / ypx
|
||||
|
||||
def snap(self, x, y):
|
||||
"""
|
||||
Adjusts coordinates to snap settings.
|
||||
|
||||
:param x: Input coordinate X
|
||||
:param y: Input coordinate Y
|
||||
:return: Snapped (x, y)
|
||||
"""
|
||||
|
||||
snap_x, snap_y = (x, y)
|
||||
snap_distance = np.Inf
|
||||
|
||||
# ### Grid snap
|
||||
if self.app.grid_status():
|
||||
if self.app.defaults["global_gridx"] != 0:
|
||||
try:
|
||||
snap_x_ = round(x / float(self.app.defaults["global_gridx"])) * \
|
||||
float(self.app.defaults["global_gridx"])
|
||||
except TypeError:
|
||||
snap_x_ = x
|
||||
else:
|
||||
snap_x_ = x
|
||||
|
||||
# If the Grid_gap_linked on Grid Toolbar is checked then the snap distance on GridY entry will be ignored
|
||||
# and it will use the snap distance from GridX entry
|
||||
if self.app.ui.grid_gap_link_cb.isChecked():
|
||||
if self.app.defaults["global_gridx"] != 0:
|
||||
try:
|
||||
snap_y_ = round(y / float(self.app.defaults["global_gridx"])) * \
|
||||
float(self.app.defaults["global_gridx"])
|
||||
except TypeError:
|
||||
snap_y_ = y
|
||||
else:
|
||||
snap_y_ = y
|
||||
else:
|
||||
if self.app.defaults["global_gridy"] != 0:
|
||||
try:
|
||||
snap_y_ = round(y / float(self.app.defaults["global_gridy"])) * \
|
||||
float(self.app.defaults["global_gridy"])
|
||||
except TypeError:
|
||||
snap_y_ = y
|
||||
else:
|
||||
snap_y_ = y
|
||||
nearest_grid_distance = self.distance((x, y), (snap_x_, snap_y_))
|
||||
if nearest_grid_distance < snap_distance:
|
||||
snap_x, snap_y = (snap_x_, snap_y_)
|
||||
|
||||
return snap_x, snap_y
|
||||
|
||||
@staticmethod
|
||||
def distance(pt1, pt2):
|
||||
return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
|
||||
|
||||
|
||||
class FakeCursor(QtCore.QObject):
|
||||
"""
|
||||
@@ -13,6 +13,7 @@ import numpy as np
|
||||
|
||||
import vispy.scene as scene
|
||||
from vispy.scene.cameras.base_camera import BaseCamera
|
||||
# from vispy.scene.widgets import Widget as VisPyWidget
|
||||
from vispy.color import Color
|
||||
|
||||
import time
|
||||
@@ -13,7 +13,7 @@ from vispy.color import Color
|
||||
from shapely.geometry import Polygon, LineString, LinearRing
|
||||
import threading
|
||||
import numpy as np
|
||||
from flatcamGUI.VisPyTesselators import GLUTess
|
||||
from AppGUI.VisPyTesselators import GLUTess
|
||||
|
||||
|
||||
class FlatCAMLineVisual(LineVisual):
|
||||
@@ -5,7 +5,7 @@ from defaults import FlatCAMDefaults
|
||||
import logging
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -29,7 +29,7 @@ class PreferencesUIManager:
|
||||
|
||||
:param defaults: a dictionary storage where all the application settings are stored
|
||||
:param data_path: a path to the file where all the preferences are stored for persistence
|
||||
:param ui: reference to the FlatCAMGUI class which constructs the UI
|
||||
:param ui: reference to the MainGUI class which constructs the UI
|
||||
:param inform: a pyqtSignal used to display information's in the StatusBar of the GUI
|
||||
"""
|
||||
|
||||
@@ -43,7 +43,7 @@ class PreferencesUIManager:
|
||||
self.preferences_changed_flag = False
|
||||
|
||||
# when adding entries here read the comments in the method found below named:
|
||||
# def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
|
||||
# def app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
|
||||
self.defaults_form_fields = {
|
||||
# General App
|
||||
"decimals_inch": self.ui.general_defaults_form.general_app_group.precision_inch_entry,
|
||||
@@ -127,12 +127,6 @@ class PreferencesUIManager:
|
||||
"gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry,
|
||||
|
||||
# Gerber Options
|
||||
"gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
|
||||
"gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
|
||||
"gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
|
||||
"gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb,
|
||||
"gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.iso_scope_radio,
|
||||
"gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio,
|
||||
"gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
|
||||
"gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
|
||||
"gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
|
||||
@@ -143,12 +137,6 @@ class PreferencesUIManager:
|
||||
# "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
|
||||
# "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
|
||||
"gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
|
||||
"gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio,
|
||||
"gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner,
|
||||
"gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner,
|
||||
"gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner,
|
||||
"gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio,
|
||||
|
||||
"gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
|
||||
"gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
|
||||
"gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner,
|
||||
@@ -180,6 +168,7 @@ class PreferencesUIManager:
|
||||
# Excellon General
|
||||
"excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
|
||||
"excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
|
||||
"excellon_multicolored": self.ui.excellon_defaults_form.excellon_gen_group.multicolored_cb,
|
||||
"excellon_format_upper_in":
|
||||
self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
|
||||
"excellon_format_lower_in":
|
||||
@@ -271,6 +260,7 @@ class PreferencesUIManager:
|
||||
|
||||
# Geometry General
|
||||
"geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
|
||||
"geometry_multicolored": self.ui.geometry_defaults_form.geometry_gen_group.multicolored_cb,
|
||||
"geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry,
|
||||
"geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry,
|
||||
"geometry_plot_line": self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry,
|
||||
@@ -338,6 +328,28 @@ class PreferencesUIManager:
|
||||
"cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp,
|
||||
"cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry,
|
||||
|
||||
# Isolation Routing Tool
|
||||
"tools_iso_tooldia": self.ui.tools_defaults_form.tools_iso_group.tool_dia_entry,
|
||||
"tools_iso_order": self.ui.tools_defaults_form.tools_iso_group.order_radio,
|
||||
"tools_iso_tool_type": self.ui.tools_defaults_form.tools_iso_group.tool_type_radio,
|
||||
"tools_iso_tool_vtipdia": self.ui.tools_defaults_form.tools_iso_group.tipdia_entry,
|
||||
"tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_entry,
|
||||
"tools_iso_tool_cutz": self.ui.tools_defaults_form.tools_iso_group.cutz_entry,
|
||||
"tools_iso_newdia": self.ui.tools_defaults_form.tools_iso_group.newdia_entry,
|
||||
|
||||
"tools_iso_passes": self.ui.tools_defaults_form.tools_iso_group.passes_entry,
|
||||
"tools_iso_overlap": self.ui.tools_defaults_form.tools_iso_group.overlap_entry,
|
||||
"tools_iso_milling_type": self.ui.tools_defaults_form.tools_iso_group.milling_type_radio,
|
||||
"tools_iso_follow": self.ui.tools_defaults_form.tools_iso_group.follow_cb,
|
||||
"tools_iso_isotype": self.ui.tools_defaults_form.tools_iso_group.iso_type_radio,
|
||||
|
||||
"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_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_area_shape": self.ui.tools_defaults_form.tools_iso_group.area_shape_radio,
|
||||
"tools_iso_plotting": self.ui.tools_defaults_form.tools_iso_group.plotting_radio,
|
||||
|
||||
# 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,
|
||||
@@ -351,13 +363,13 @@ class PreferencesUIManager:
|
||||
"tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
|
||||
"tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo,
|
||||
"tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio,
|
||||
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio,
|
||||
"tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
|
||||
"tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
|
||||
"tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
|
||||
"tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
|
||||
"tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
|
||||
"tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
|
||||
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.plotting_radio,
|
||||
|
||||
# CutOut Tool
|
||||
"tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
|
||||
@@ -467,6 +479,12 @@ class PreferencesUIManager:
|
||||
"tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo,
|
||||
"tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb,
|
||||
|
||||
# Corner Markers Tool
|
||||
|
||||
"tools_corners_thickness": self.ui.tools_defaults_form.tools_corners_group.thick_entry,
|
||||
"tools_corners_length": self.ui.tools_defaults_form.tools_corners_group.l_entry,
|
||||
"tools_corners_margin": self.ui.tools_defaults_form.tools_corners_group.margin_entry,
|
||||
|
||||
# #######################################################################################################
|
||||
# ########################################## TOOLS 2 ####################################################
|
||||
# #######################################################################################################
|
||||
@@ -650,7 +668,7 @@ class PreferencesUIManager:
|
||||
|
||||
def show_preferences_gui(self):
|
||||
"""
|
||||
Called to initialize and show the Preferences GUI
|
||||
Called to initialize and show the Preferences AppGUI
|
||||
|
||||
:return: None
|
||||
"""
|
||||
@@ -913,7 +931,51 @@ class PreferencesUIManager:
|
||||
# make sure we update the self.current_defaults dict used to undo changes to self.defaults
|
||||
self.defaults.current_defaults.update(self.defaults)
|
||||
|
||||
if save_to_file:
|
||||
# deal with theme change
|
||||
theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if theme_settings.contains("theme"):
|
||||
theme = theme_settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
should_restart = False
|
||||
theme_new_val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value()
|
||||
|
||||
ge = self.defaults["global_graphic_engine"]
|
||||
ge_val = self.ui.general_defaults_form.general_app_group.ge_radio.get_value()
|
||||
|
||||
if theme_new_val != theme or ge != ge_val:
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
msgbox.setText(_("Are you sure you want to continue?"))
|
||||
msgbox.setWindowTitle(_("Application restart"))
|
||||
msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Question)
|
||||
|
||||
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
|
||||
msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
|
||||
|
||||
msgbox.setDefaultButton(bt_yes)
|
||||
msgbox.exec_()
|
||||
response = msgbox.clickedButton()
|
||||
|
||||
if theme_new_val != theme:
|
||||
if response == bt_yes:
|
||||
theme_settings.setValue('theme', theme_new_val)
|
||||
|
||||
# This will write the setting to the platform specific storage.
|
||||
del theme_settings
|
||||
|
||||
should_restart = True
|
||||
else:
|
||||
self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
|
||||
else:
|
||||
if response == bt_yes:
|
||||
self.defaults["global_graphic_engine"] = ge_val
|
||||
should_restart = True
|
||||
else:
|
||||
self.ui.general_defaults_form.general_app_group.ge_radio.set_value(ge)
|
||||
|
||||
if save_to_file or should_restart is True:
|
||||
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'))
|
||||
@@ -935,6 +997,10 @@ class PreferencesUIManager:
|
||||
tb_fsize = self.ui.general_defaults_form.general_app_set_group.textbox_font_size_spinner.get_value()
|
||||
settgs.setValue('textbox_font_size', tb_fsize)
|
||||
|
||||
# save the HUD font size
|
||||
hud_fsize = self.ui.general_defaults_form.general_app_set_group.hud_font_size_spinner.get_value()
|
||||
settgs.setValue('hud_font_size', hud_fsize)
|
||||
|
||||
settgs.setValue(
|
||||
'machinist',
|
||||
1 if self.ui.general_defaults_form.general_app_set_group.machinist_cb.get_value() else 0
|
||||
@@ -951,6 +1017,9 @@ class PreferencesUIManager:
|
||||
self.ui.plot_tab_area.closeTab(idx)
|
||||
break
|
||||
|
||||
if should_restart is True:
|
||||
self.ui.app.on_app_restart()
|
||||
|
||||
def on_pref_close_button(self):
|
||||
# Preferences saved, update flag
|
||||
self.preferences_changed_flag = False
|
||||
@@ -1000,6 +1069,7 @@ class PreferencesUIManager:
|
||||
:return: None
|
||||
"""
|
||||
self.defaults.report_usage("save_defaults")
|
||||
log.debug("App.PreferencesUIManager.save_defaults()")
|
||||
|
||||
if data_path is None:
|
||||
data_path = self.data_path
|
||||
@@ -1036,7 +1106,7 @@ class PreferencesUIManager:
|
||||
if self.ui.toolbarfile.isVisible():
|
||||
tb_status += 1
|
||||
|
||||
if self.ui.toolbargeo.isVisible():
|
||||
if self.ui.toolbaredit.isVisible():
|
||||
tb_status += 2
|
||||
|
||||
if self.ui.toolbarview.isVisible():
|
||||
@@ -1054,7 +1124,7 @@ class PreferencesUIManager:
|
||||
if self.ui.grb_edit_toolbar.isVisible():
|
||||
tb_status += 64
|
||||
|
||||
if self.ui.snap_toolbar.isVisible():
|
||||
if self.ui.status_toolbar.isVisible():
|
||||
tb_status += 128
|
||||
|
||||
if self.ui.toolbarshell.isVisible():
|
||||
@@ -1118,6 +1188,7 @@ class PreferencesUIManager:
|
||||
"Do you want to save the Preferences?"))
|
||||
msgbox.setWindowTitle(_("Save Preferences"))
|
||||
msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/save_as.png'))
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Question)
|
||||
|
||||
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
|
||||
msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
|
||||
@@ -1,6 +1,6 @@
|
||||
from flatcamGUI.GUIElements import *
|
||||
from AppGUI.GUIElements import *
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
|
||||
from flatcamGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,10 +1,10 @@
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCTextArea
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCTextArea
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,8 +1,8 @@
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from flatcamGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
|
||||
from flatcamGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
|
||||
from flatcamGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
|
||||
from AppGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
|
||||
from AppGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
|
||||
from AppGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
|
||||
|
||||
|
||||
class CNCJobPreferencesUI(QtWidgets.QWidget):
|
||||
@@ -1,10 +1,10 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -60,7 +60,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
toolchange_xy_label.setToolTip(
|
||||
_("Toolchange X,Y position.")
|
||||
)
|
||||
self.toolchangexy_entry = FCEntry()
|
||||
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid1.addWidget(toolchange_xy_label, 1, 0)
|
||||
grid1.addWidget(self.toolchangexy_entry, 1, 1)
|
||||
@@ -71,7 +71,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
_("Height of the tool just after start.\n"
|
||||
"Delete the value if you don't need this feature.")
|
||||
)
|
||||
self.estartz_entry = FloatEntry()
|
||||
self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
|
||||
|
||||
grid1.addWidget(startzlabel, 2, 0)
|
||||
grid1.addWidget(self.estartz_entry, 2, 1)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,10 +1,10 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -3,10 +3,10 @@ import platform
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -36,22 +36,31 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
|
||||
grid1 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid1)
|
||||
|
||||
# Plot CB
|
||||
self.plot_cb = FCCheckBox(label=_('Plot'))
|
||||
self.plot_cb.setToolTip(
|
||||
"Plot (show) this object."
|
||||
)
|
||||
grid1.addWidget(self.plot_cb, 0, 0)
|
||||
|
||||
# Solid CB
|
||||
self.solid_cb = FCCheckBox(label=_('Solid'))
|
||||
self.solid_cb.setToolTip(
|
||||
"Plot as solid circles."
|
||||
)
|
||||
grid1.addWidget(self.solid_cb, 0, 1)
|
||||
|
||||
# Multicolored CB
|
||||
self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
|
||||
self.multicolored_cb.setToolTip(
|
||||
_("Draw polygons in different colors.")
|
||||
)
|
||||
grid1.addWidget(self.multicolored_cb, 0, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid1.addWidget(separator_line, 1, 0, 1, 2)
|
||||
grid1.addWidget(separator_line, 1, 0, 1, 3)
|
||||
|
||||
grid2 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid2)
|
||||
@@ -341,6 +350,12 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
# Load the defaults values into the Excellon Format and Excellon Zeros fields
|
||||
self.excellon_defaults_button.clicked.connect(self.on_excellon_defaults_button)
|
||||
# Make sure that when the Excellon loading parameters are changed, the change is reflected in the
|
||||
# Export Excellon parameters.
|
||||
self.update_excellon_cb.stateChanged.connect(self.on_update_exc_export)
|
||||
|
||||
# call it once to make sure it is updated at startup
|
||||
self.on_update_exc_export(state=self.app.defaults["excellon_update"])
|
||||
|
||||
def optimization_selection(self):
|
||||
if self.excellon_optimization_radio.get_value() == 'M':
|
||||
@@ -413,3 +428,105 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
|
||||
self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_mm"].set_value('3')
|
||||
self.app.preferencesUiManager.defaults_form_fields["excellon_zeros"].set_value('L')
|
||||
self.app.preferencesUiManager.defaults_form_fields["excellon_units"].set_value('INCH')
|
||||
|
||||
def on_update_exc_export(self, state):
|
||||
"""
|
||||
This is handling the update of Excellon Export parameters based on the ones in the Excellon General but only
|
||||
if the update_excellon_cb checkbox is checked
|
||||
|
||||
:param state: state of the checkbox whose signals is tied to his slot
|
||||
:return:
|
||||
"""
|
||||
if state:
|
||||
# first try to disconnect
|
||||
try:
|
||||
self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# the connect them
|
||||
self.excellon_format_upper_in_entry.returnPressed.connect(self.on_excellon_format_changed)
|
||||
self.excellon_format_lower_in_entry.returnPressed.connect(self.on_excellon_format_changed)
|
||||
self.excellon_format_upper_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
|
||||
self.excellon_format_lower_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
|
||||
self.excellon_zeros_radio.activated_custom.connect(self.on_excellon_zeros_changed)
|
||||
self.excellon_units_radio.activated_custom.connect(self.on_excellon_units_changed)
|
||||
else:
|
||||
# disconnect the signals
|
||||
try:
|
||||
self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def on_excellon_format_changed(self):
|
||||
"""
|
||||
Slot activated when the user changes the Excellon format values in Preferences -> Excellon -> Excellon General
|
||||
:return: None
|
||||
"""
|
||||
if self.excellon_units_radio.get_value().upper() == 'METRIC':
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
|
||||
self.excellon_format_upper_mm_entry.get_value())
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
|
||||
self.excellon_format_lower_mm_entry.get_value())
|
||||
else:
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
|
||||
self.excellon_format_upper_in_entry.get_value())
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
|
||||
self.excellon_format_lower_in_entry.get_value())
|
||||
|
||||
def on_excellon_zeros_changed(self, val):
|
||||
"""
|
||||
Slot activated when the user changes the Excellon zeros values in Preferences -> Excellon -> Excellon General
|
||||
:return: None
|
||||
"""
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.zeros_radio.set_value(val + 'Z')
|
||||
|
||||
def on_excellon_units_changed(self, val):
|
||||
"""
|
||||
Slot activated when the user changes the Excellon unit values in Preferences -> Excellon -> Excellon General
|
||||
:return: None
|
||||
"""
|
||||
self.app.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio.set_value(val)
|
||||
self.on_excellon_format_changed()
|
||||
@@ -1,12 +1,12 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
|
||||
FCComboBox
|
||||
from flatcamGUI.preferences import machinist_setting
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
|
||||
FCComboBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences import machinist_setting
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -198,7 +198,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
|
||||
"If no value is entered then there is no move\n"
|
||||
"on X,Y plane at the end of the job.")
|
||||
)
|
||||
self.endxy_entry = FCEntry()
|
||||
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid2.addWidget(endmove_xy_label, 9, 0)
|
||||
grid2.addWidget(self.endxy_entry, 9, 1)
|
||||
@@ -1,14 +1,14 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
|
||||
from flatcamGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
|
||||
from flatcamGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
|
||||
from flatcamGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
|
||||
from flatcamGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
|
||||
from AppGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
|
||||
from AppGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
|
||||
from AppGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
|
||||
from AppGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
|
||||
from AppGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -31,7 +31,7 @@ class ExcellonPreferencesUI(QtWidgets.QWidget):
|
||||
self.decimals = decimals
|
||||
|
||||
self.excellon_gen_group = ExcellonGenPrefGroupUI(decimals=self.decimals)
|
||||
self.excellon_gen_group.setMinimumWidth(220)
|
||||
self.excellon_gen_group.setMinimumWidth(240)
|
||||
self.excellon_opt_group = ExcellonOptPrefGroupUI(decimals=self.decimals)
|
||||
self.excellon_opt_group.setMinimumWidth(290)
|
||||
self.excellon_exp_group = ExcellonExpPrefGroupUI(decimals=self.decimals)
|
||||
@@ -1,13 +1,13 @@
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
|
||||
FCEntry
|
||||
from flatcamGUI.preferences import settings
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.preferences import settings
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -177,14 +177,6 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
|
||||
{'label': _('Landscape'), 'value': 'l'},
|
||||
], stretch=False)
|
||||
|
||||
self.wks = OptionalInputSection(self.workspace_cb,
|
||||
[
|
||||
self.workspace_type_lbl,
|
||||
self.wk_cb,
|
||||
self.wk_orientation_label,
|
||||
self.wk_orientation_radio
|
||||
])
|
||||
|
||||
grid0.addWidget(self.wk_orientation_label, 8, 0)
|
||||
grid0.addWidget(self.wk_orientation_radio, 8, 1)
|
||||
|
||||
@@ -201,7 +193,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
|
||||
self.notebook_font_size_label = QtWidgets.QLabel('%s:' % _('Notebook'))
|
||||
self.notebook_font_size_label.setToolTip(
|
||||
_("This sets the font size for the elements found in the Notebook.\n"
|
||||
"The notebook is the collapsible area in the left side of the GUI,\n"
|
||||
"The notebook is the collapsible area in the left side of the AppGUI,\n"
|
||||
"and include the Project, Selected and Tool tabs.")
|
||||
)
|
||||
|
||||
@@ -240,7 +232,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
|
||||
# TextBox Font Size
|
||||
self.textbox_font_size_label = QtWidgets.QLabel('%s:' % _('Textbox'))
|
||||
self.textbox_font_size_label.setToolTip(
|
||||
_("This sets the font size for the Textbox GUI\n"
|
||||
_("This sets the font size for the Textbox AppGUI\n"
|
||||
"elements that are used in FlatCAM.")
|
||||
)
|
||||
|
||||
@@ -257,10 +249,29 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(self.textbox_font_size_label, 13, 0)
|
||||
grid0.addWidget(self.textbox_font_size_spinner, 13, 1)
|
||||
|
||||
# HUD Font Size
|
||||
self.hud_font_size_label = QtWidgets.QLabel('%s:' % _('HUD'))
|
||||
self.hud_font_size_label.setToolTip(
|
||||
_("This sets the font size for the Heads Up Display.")
|
||||
)
|
||||
|
||||
self.hud_font_size_spinner = FCSpinner()
|
||||
self.hud_font_size_spinner.set_range(8, 40)
|
||||
self.hud_font_size_spinner.setWrapping(True)
|
||||
|
||||
qsettings = QSettings("Open Source", "FlatCAM")
|
||||
if qsettings.contains("hud_font_size"):
|
||||
self.hud_font_size_spinner.set_value(settings.value('hud_font_size', type=int))
|
||||
else:
|
||||
self.hud_font_size_spinner.set_value(8)
|
||||
|
||||
grid0.addWidget(self.hud_font_size_label, 14, 0)
|
||||
grid0.addWidget(self.hud_font_size_spinner, 14, 1)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 14, 0, 1, 2)
|
||||
grid0.addWidget(separator_line, 16, 0, 1, 2)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# -------------- MOUSE SETTINGS -----------------------------
|
||||
@@ -3,12 +3,12 @@ import sys
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
|
||||
from AppGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
|
||||
FCDoubleSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -380,12 +380,11 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
def on_toggle_shell_from_settings(self, state):
|
||||
"""
|
||||
Toggle shell: if is visible close it, if it is closed then open it
|
||||
Toggle shell ui: if is visible close it, if it is closed then open it
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
self.app.defaults.report_usage("on_toggle_shell_from_settings()")
|
||||
|
||||
if state is True:
|
||||
if not self.app.ui.shell_dock.isVisible():
|
||||
self.app.ui.shell_dock.show()
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -56,13 +56,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
)
|
||||
grid0.addWidget(self.gray_icons_cb, 1, 0, 1, 3)
|
||||
|
||||
self.theme_button = FCButton(_("Apply Theme"))
|
||||
self.theme_button.setToolTip(
|
||||
_("Select a theme for FlatCAM.\n"
|
||||
"It will theme the plot area.\n"
|
||||
"The application will restart after change.")
|
||||
)
|
||||
grid0.addWidget(self.theme_button, 2, 0, 1, 3)
|
||||
# self.theme_button = FCButton(_("Apply Theme"))
|
||||
# self.theme_button.setToolTip(
|
||||
# _("Select a theme for FlatCAM.\n"
|
||||
# "It will theme the plot area.\n"
|
||||
# "The application will restart after change.")
|
||||
# )
|
||||
# grid0.addWidget(self.theme_button, 2, 0, 1, 3)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
@@ -381,8 +381,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
self.theme_button.clicked.connect(self.on_theme_change)
|
||||
|
||||
# #############################################################################
|
||||
# ############################# GUI COLORS SIGNALS ############################
|
||||
# #############################################################################
|
||||
@@ -418,16 +416,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
|
||||
self.layout_combo.activated.connect(self.on_layout)
|
||||
|
||||
def on_theme_change(self):
|
||||
val = self.theme_radio.get_value()
|
||||
qsettings = QSettings("Open Source", "FlatCAM")
|
||||
qsettings.setValue('theme', val)
|
||||
|
||||
# This will write the setting to the platform specific storage.
|
||||
del qsettings
|
||||
|
||||
self.app.on_app_restart()
|
||||
|
||||
@staticmethod
|
||||
def handle_style(style):
|
||||
# set current style
|
||||
@@ -659,14 +647,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
# first remove the toolbars:
|
||||
try:
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbarfile)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbargeo)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbaredit)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbarview)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbarshell)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbartools)
|
||||
self.app.ui.removeToolBar(self.app.ui.exc_edit_toolbar)
|
||||
self.app.ui.removeToolBar(self.app.ui.geo_edit_toolbar)
|
||||
self.app.ui.removeToolBar(self.app.ui.grb_edit_toolbar)
|
||||
self.app.ui.removeToolBar(self.app.ui.snap_toolbar)
|
||||
self.app.ui.removeToolBar(self.app.ui.toolbarshell)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -677,9 +664,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.app.ui.toolbarfile.setObjectName('File_TB')
|
||||
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarfile)
|
||||
|
||||
self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
|
||||
self.app.ui.toolbargeo.setObjectName('Edit_TB')
|
||||
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbargeo)
|
||||
self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
|
||||
self.app.ui.toolbaredit.setObjectName('Edit_TB')
|
||||
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbaredit)
|
||||
|
||||
self.app.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
|
||||
self.app.ui.toolbarshell.setObjectName('Shell_TB')
|
||||
@@ -709,22 +696,15 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.app.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
|
||||
self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.exc_edit_toolbar)
|
||||
|
||||
self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
|
||||
self.app.ui.snap_toolbar.setObjectName('Snap_TB')
|
||||
self.app.ui.snap_toolbar.setMaximumHeight(30)
|
||||
self.app.ui.splitter_left.addWidget(self.app.ui.snap_toolbar)
|
||||
|
||||
self.app.ui.corner_snap_btn.setVisible(True)
|
||||
self.app.ui.snap_magnet.setVisible(True)
|
||||
else:
|
||||
# ## TOOLBAR INSTALLATION # ##
|
||||
self.app.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
|
||||
self.app.ui.toolbarfile.setObjectName('File_TB')
|
||||
self.app.ui.addToolBar(self.app.ui.toolbarfile)
|
||||
|
||||
self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
|
||||
self.app.ui.toolbargeo.setObjectName('Edit_TB')
|
||||
self.app.ui.addToolBar(self.app.ui.toolbargeo)
|
||||
self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
|
||||
self.app.ui.toolbaredit.setObjectName('Edit_TB')
|
||||
self.app.ui.addToolBar(self.app.ui.toolbaredit)
|
||||
|
||||
self.app.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
|
||||
self.app.ui.toolbarview.setObjectName('View_TB')
|
||||
@@ -755,18 +735,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.app.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
|
||||
self.app.ui.addToolBar(self.app.ui.grb_edit_toolbar)
|
||||
|
||||
self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
|
||||
self.app.ui.snap_toolbar.setObjectName('Snap_TB')
|
||||
# self.app.ui.snap_toolbar.setMaximumHeight(30)
|
||||
self.app.ui.addToolBar(self.app.ui.snap_toolbar)
|
||||
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
|
||||
if current_layout == 'minimal':
|
||||
self.app.ui.toolbarview.setVisible(False)
|
||||
self.app.ui.toolbarshell.setVisible(False)
|
||||
self.app.ui.snap_toolbar.setVisible(False)
|
||||
self.app.ui.geo_edit_toolbar.setVisible(False)
|
||||
self.app.ui.grb_edit_toolbar.setVisible(False)
|
||||
self.app.ui.exc_edit_toolbar.setVisible(False)
|
||||
@@ -779,7 +750,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||
self.app.connect_toolbar_signals()
|
||||
|
||||
self.app.ui.grid_snap_btn.setChecked(True)
|
||||
self.app.ui.on_grid_snap_triggered(state=True)
|
||||
|
||||
self.app.ui.corner_snap_btn.setVisible(False)
|
||||
self.app.ui.snap_magnet.setVisible(False)
|
||||
|
||||
self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
|
||||
self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))
|
||||
@@ -1,12 +1,12 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
|
||||
from flatcamGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
|
||||
from flatcamGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
|
||||
from AppGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
|
||||
from AppGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
|
||||
from AppGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,12 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
|
||||
NumericalEvalEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -46,8 +47,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
toolchange_xy_label.setToolTip(
|
||||
_("Toolchange X,Y position.")
|
||||
)
|
||||
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid1.addWidget(toolchange_xy_label, 1, 0)
|
||||
self.toolchangexy_entry = FCEntry()
|
||||
grid1.addWidget(self.toolchangexy_entry, 1, 1)
|
||||
|
||||
# Start move Z
|
||||
@@ -56,8 +58,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
_("Height of the tool just after starting the work.\n"
|
||||
"Delete the value if you don't need this feature.")
|
||||
)
|
||||
self.gstartz_entry = NumericalEvalEntry(border_color='#0069A9')
|
||||
|
||||
grid1.addWidget(startzlabel, 2, 0)
|
||||
self.gstartz_entry = FloatEntry()
|
||||
grid1.addWidget(self.gstartz_entry, 2, 1)
|
||||
|
||||
# Feedrate rapids
|
||||
@@ -186,6 +189,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
grid1.addWidget(segy_label, 11, 0)
|
||||
grid1.addWidget(self.segy_entry, 11, 1)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid1.addWidget(separator_line, 12, 0, 1, 2)
|
||||
|
||||
# -----------------------------
|
||||
# --- Area Exclusion ----------
|
||||
# -----------------------------
|
||||
@@ -195,10 +203,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
"Those parameters are available only for\n"
|
||||
"Advanced App. Level.")
|
||||
)
|
||||
grid1.addWidget(self.adv_label, 12, 0, 1, 2)
|
||||
grid1.addWidget(self.adv_label, 13, 0, 1, 2)
|
||||
|
||||
# Exclusion Area CB
|
||||
self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
|
||||
self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
|
||||
self.exclusion_cb.setToolTip(
|
||||
_(
|
||||
"Include exclusion areas.\n"
|
||||
@@ -206,7 +214,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
"is forbidden."
|
||||
)
|
||||
)
|
||||
grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
|
||||
grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
|
||||
|
||||
# Area Selection shape
|
||||
self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
|
||||
@@ -217,8 +225,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
|
||||
{'label': _("Polygon"), 'value': 'polygon'}])
|
||||
|
||||
grid1.addWidget(self.area_shape_label, 14, 0)
|
||||
grid1.addWidget(self.area_shape_radio, 14, 1)
|
||||
grid1.addWidget(self.area_shape_label, 15, 0)
|
||||
grid1.addWidget(self.area_shape_radio, 15, 1)
|
||||
|
||||
# Chose Strategy
|
||||
self.strategy_label = FCLabel('%s:' % _("Strategy"))
|
||||
@@ -229,8 +237,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
|
||||
{'label': _('Around'), 'value': 'around'}])
|
||||
|
||||
grid1.addWidget(self.strategy_label, 15, 0)
|
||||
grid1.addWidget(self.strategy_radio, 15, 1)
|
||||
grid1.addWidget(self.strategy_label, 16, 0)
|
||||
grid1.addWidget(self.strategy_radio, 16, 1)
|
||||
|
||||
# Over Z
|
||||
self.over_z_label = FCLabel('%s:' % _("Over Z"))
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -31,12 +31,22 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
|
||||
self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
|
||||
self.layout.addWidget(self.plot_options_label)
|
||||
|
||||
plot_hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout((plot_hlay))
|
||||
|
||||
# Plot CB
|
||||
self.plot_cb = FCCheckBox(label=_('Plot'))
|
||||
self.plot_cb.setToolTip(
|
||||
_("Plot (show) this object.")
|
||||
)
|
||||
self.layout.addWidget(self.plot_cb)
|
||||
plot_hlay.addWidget(self.plot_cb)
|
||||
|
||||
# Multicolored CB
|
||||
self.multicolored_cb = FCCheckBox(label=_('M-Color'))
|
||||
self.multicolored_cb.setToolTip(
|
||||
_("Draw polygons in different colors.")
|
||||
)
|
||||
plot_hlay.addWidget(self.multicolored_cb)
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
@@ -1,12 +1,13 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
|
||||
from flatcamGUI.preferences import machinist_setting
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
|
||||
NumericalEvalTupleEntry
|
||||
from AppGUI.preferences import machinist_setting
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -176,7 +177,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
|
||||
"If no value is entered then there is no move\n"
|
||||
"on X,Y plane at the end of the job.")
|
||||
)
|
||||
self.endxy_entry = FCEntry()
|
||||
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid1.addWidget(endmove_xy_label, 7, 0)
|
||||
grid1.addWidget(self.endxy_entry, 7, 1)
|
||||
@@ -1,13 +1,13 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
|
||||
from flatcamGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
|
||||
from flatcamGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
|
||||
from flatcamGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
|
||||
from AppGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
|
||||
from AppGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
|
||||
from AppGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
|
||||
from AppGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -63,85 +63,6 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 2, 0, 1, 2)
|
||||
|
||||
# Tool Type
|
||||
self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
|
||||
self.tool_type_label.setToolTip(
|
||||
_("Choose which tool to use for Gerber isolation:\n"
|
||||
"'Circular' or 'V-shape'.\n"
|
||||
"When the 'V-shape' is selected then the tool\n"
|
||||
"diameter will depend on the chosen cut depth.")
|
||||
)
|
||||
self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
|
||||
{'label': 'V-Shape', 'value': 'v'}])
|
||||
|
||||
grid0.addWidget(self.tool_type_label, 3, 0)
|
||||
grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
|
||||
|
||||
# Tip Dia
|
||||
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
|
||||
self.tipdialabel.setToolTip(
|
||||
_("The tip diameter for V-Shape Tool")
|
||||
)
|
||||
self.tipdia_spinner = FCDoubleSpinner()
|
||||
self.tipdia_spinner.set_precision(self.decimals)
|
||||
self.tipdia_spinner.set_range(-99.9999, 99.9999)
|
||||
self.tipdia_spinner.setSingleStep(0.1)
|
||||
self.tipdia_spinner.setWrapping(True)
|
||||
grid0.addWidget(self.tipdialabel, 4, 0)
|
||||
grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
|
||||
|
||||
# Tip Angle
|
||||
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
|
||||
self.tipanglelabel.setToolTip(
|
||||
_("The tip angle for V-Shape Tool.\n"
|
||||
"In degree.")
|
||||
)
|
||||
self.tipangle_spinner = FCSpinner()
|
||||
self.tipangle_spinner.set_range(1, 180)
|
||||
self.tipangle_spinner.set_step(5)
|
||||
self.tipangle_spinner.setWrapping(True)
|
||||
grid0.addWidget(self.tipanglelabel, 5, 0)
|
||||
grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
|
||||
|
||||
# Cut Z
|
||||
self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
|
||||
self.cutzlabel.setToolTip(
|
||||
_("Cutting depth (negative)\n"
|
||||
"below the copper surface.")
|
||||
)
|
||||
self.cutz_spinner = FCDoubleSpinner()
|
||||
self.cutz_spinner.set_precision(self.decimals)
|
||||
self.cutz_spinner.set_range(-99.9999, 0.0000)
|
||||
self.cutz_spinner.setSingleStep(0.1)
|
||||
self.cutz_spinner.setWrapping(True)
|
||||
|
||||
grid0.addWidget(self.cutzlabel, 6, 0)
|
||||
grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
|
||||
|
||||
# Isolation Type
|
||||
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
|
||||
self.iso_type_label.setToolTip(
|
||||
_("Choose how the isolation will be executed:\n"
|
||||
"- 'Full' -> complete isolation of polygons\n"
|
||||
"- 'Ext' -> will isolate only on the outside\n"
|
||||
"- 'Int' -> will isolate only on the inside\n"
|
||||
"'Exterior' isolation is almost always possible\n"
|
||||
"(with the right tool) but 'Interior'\n"
|
||||
"isolation can be done only when there is an opening\n"
|
||||
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
|
||||
)
|
||||
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
|
||||
{'label': _('Exterior'), 'value': 'ext'},
|
||||
{'label': _('Interior'), 'value': 'int'}])
|
||||
|
||||
grid0.addWidget(self.iso_type_label, 7, 0,)
|
||||
grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 8, 0, 1, 2)
|
||||
|
||||
# Buffering Type
|
||||
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
|
||||
buffering_label.setToolTip(
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -109,8 +109,9 @@ class GerberEditorPrefGroupUI(OptionsGroupUI):
|
||||
"The value of the diameter has to use the dot decimals separator.\n"
|
||||
"Valid values: 0.3, 1.0")
|
||||
)
|
||||
self.adddim_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid0.addWidget(self.adddim_label, 5, 0)
|
||||
self.adddim_entry = FCEntry()
|
||||
grid0.addWidget(self.adddim_entry, 5, 1)
|
||||
|
||||
self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -34,26 +34,26 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
# Plot CB
|
||||
self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
|
||||
self.plot_options_label.setToolTip(
|
||||
_("Plot (show) this object.")
|
||||
)
|
||||
grid0.addWidget(self.plot_cb, 0, 0)
|
||||
|
||||
# Solid CB
|
||||
self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
|
||||
self.solid_cb.setToolTip(
|
||||
_("Solid color polygons.")
|
||||
)
|
||||
grid0.addWidget(self.solid_cb, 0, 0)
|
||||
grid0.addWidget(self.solid_cb, 0, 1)
|
||||
|
||||
# Multicolored CB
|
||||
self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
|
||||
self.multicolored_cb.setToolTip(
|
||||
_("Draw polygons in different colors.")
|
||||
)
|
||||
grid0.addWidget(self.multicolored_cb, 0, 1)
|
||||
|
||||
# Plot CB
|
||||
self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
|
||||
self.plot_options_label.setToolTip(
|
||||
_("Plot (show) this object.")
|
||||
)
|
||||
grid0.addWidget(self.plot_cb, 0, 2)
|
||||
grid0.addWidget(self.multicolored_cb, 0, 2)
|
||||
|
||||
# Number of circle steps for circular aperture linear approximation
|
||||
self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
|
||||
100
AppGUI/preferences/gerber/GerberOptPrefGroupUI.py
Normal file
100
AppGUI/preferences/gerber/GerberOptPrefGroupUI.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox, FCComboBox
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("machinist"):
|
||||
machinist_setting = settings.value('machinist', type=int)
|
||||
else:
|
||||
machinist_setting = 0
|
||||
|
||||
|
||||
class GerberOptPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, decimals=4, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "Gerber Options Preferences", parent=parent)
|
||||
super(GerberOptPrefGroupUI, self).__init__(self, parent=parent)
|
||||
|
||||
self.decimals = decimals
|
||||
|
||||
self.setTitle(str(_("Gerber Options")))
|
||||
|
||||
# ## Clear non-copper regions
|
||||
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
|
||||
self.clearcopper_label.setToolTip(
|
||||
_("Create polygons covering the\n"
|
||||
"areas without copper on the PCB.\n"
|
||||
"Equivalent to the inverse of this\n"
|
||||
"object. Can be used to remove all\n"
|
||||
"copper from a specified region.")
|
||||
)
|
||||
self.layout.addWidget(self.clearcopper_label)
|
||||
|
||||
grid1 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid1)
|
||||
|
||||
# Margin
|
||||
bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
|
||||
bmlabel.setToolTip(
|
||||
_("Specify the edge of the PCB\n"
|
||||
"by drawing a box around all\n"
|
||||
"objects with this minimum\n"
|
||||
"distance.")
|
||||
)
|
||||
grid1.addWidget(bmlabel, 0, 0)
|
||||
self.noncopper_margin_entry = FCDoubleSpinner()
|
||||
self.noncopper_margin_entry.set_precision(self.decimals)
|
||||
self.noncopper_margin_entry.setSingleStep(0.1)
|
||||
self.noncopper_margin_entry.set_range(-9999, 9999)
|
||||
grid1.addWidget(self.noncopper_margin_entry, 0, 1)
|
||||
|
||||
# Rounded corners
|
||||
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
|
||||
self.noncopper_rounded_cb.setToolTip(
|
||||
_("Resulting geometry will have rounded corners.")
|
||||
)
|
||||
grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid1.addWidget(separator_line, 2, 0, 1, 2)
|
||||
|
||||
# ## Bounding box
|
||||
self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
|
||||
self.layout.addWidget(self.boundingbox_label)
|
||||
|
||||
grid2 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid2)
|
||||
|
||||
bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
|
||||
bbmargin.setToolTip(
|
||||
_("Distance of the edges of the box\n"
|
||||
"to the nearest polygon.")
|
||||
)
|
||||
self.bbmargin_entry = FCDoubleSpinner()
|
||||
self.bbmargin_entry.set_precision(self.decimals)
|
||||
self.bbmargin_entry.setSingleStep(0.1)
|
||||
self.bbmargin_entry.set_range(-9999, 9999)
|
||||
|
||||
grid2.addWidget(bbmargin, 0, 0)
|
||||
grid2.addWidget(self.bbmargin_entry, 0, 1)
|
||||
|
||||
self.bbrounded_cb = FCCheckBox(label='%s' % _("Rounded Geo"))
|
||||
self.bbrounded_cb.setToolTip(
|
||||
_("If the bounding box is \n"
|
||||
"to have rounded corners\n"
|
||||
"their radius is equal to\n"
|
||||
"the margin.")
|
||||
)
|
||||
grid2.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
|
||||
self.layout.addStretch()
|
||||
@@ -1,14 +1,14 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
|
||||
from flatcamGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
|
||||
from flatcamGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
|
||||
from flatcamGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
|
||||
from flatcamGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
|
||||
from AppGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
|
||||
from AppGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
|
||||
from AppGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
|
||||
from AppGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
|
||||
from AppGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -44,6 +44,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
|
||||
self.vlay = QtWidgets.QVBoxLayout()
|
||||
self.vlay.addWidget(self.gerber_opt_group)
|
||||
self.vlay.addWidget(self.gerber_exp_group)
|
||||
self.vlay.addStretch()
|
||||
|
||||
self.layout.addWidget(self.gerber_gen_group)
|
||||
self.layout.addLayout(self.vlay)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -116,7 +116,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
|
||||
"(x, y) point will be used,")
|
||||
)
|
||||
|
||||
self.toolchange_xy_entry = FCEntry()
|
||||
self.toolchange_xy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid_lay.addWidget(toolchangexy_lbl, 7, 0)
|
||||
grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,18 +1,18 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -205,3 +205,67 @@ class Tools2QRCodePrefGroupUI(OptionsGroupUI):
|
||||
grid_lay.addWidget(self.sel_limit_label, 11, 0)
|
||||
grid_lay.addWidget(self.sel_limit_entry, 11, 1)
|
||||
# self.layout.addStretch()
|
||||
|
||||
# QRCode Tool
|
||||
self.fill_color_entry.editingFinished.connect(self.on_qrcode_fill_color_entry)
|
||||
self.fill_color_button.clicked.connect(self.on_qrcode_fill_color_button)
|
||||
self.back_color_entry.editingFinished.connect(self.on_qrcode_back_color_entry)
|
||||
self.back_color_button.clicked.connect(self.on_qrcode_back_color_button)
|
||||
|
||||
def on_qrcode_fill_color_entry(self):
|
||||
self.app.defaults['tools_qrcode_fill_color'] = self.fill_color_entry.get_value()
|
||||
self.fill_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(self.defaults['tools_qrcode_fill_color'])
|
||||
)
|
||||
|
||||
def on_qrcode_fill_color_button(self):
|
||||
current_color = QtGui.QColor(self.app.defaults['tools_qrcode_fill_color'])
|
||||
|
||||
c_dialog = QtWidgets.QColorDialog()
|
||||
fill_color = c_dialog.getColor(initial=current_color)
|
||||
|
||||
if fill_color.isValid() is False:
|
||||
return
|
||||
|
||||
# if new color is different then mark that the Preferences are changed
|
||||
if fill_color != current_color:
|
||||
self.app.preferencesUiManager.on_preferences_edited()
|
||||
|
||||
self.fill_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(fill_color.name())
|
||||
)
|
||||
|
||||
new_val_sel = str(fill_color.name())
|
||||
self.fill_color_entry.set_value(new_val_sel)
|
||||
self.app.defaults['tools_qrcode_fill_color'] = new_val_sel
|
||||
|
||||
def on_qrcode_back_color_entry(self):
|
||||
self.app.defaults['tools_qrcode_back_color'] = self.back_color_entry.get_value()
|
||||
self.back_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(self.defaults['tools_qrcode_back_color'])
|
||||
)
|
||||
|
||||
def on_qrcode_back_color_button(self):
|
||||
current_color = QtGui.QColor(self.app.defaults['tools_qrcode_back_color'])
|
||||
|
||||
c_dialog = QtWidgets.QColorDialog()
|
||||
back_color = c_dialog.getColor(initial=current_color)
|
||||
|
||||
if back_color.isValid() is False:
|
||||
return
|
||||
|
||||
# if new color is different then mark that the Preferences are changed
|
||||
if back_color != current_color:
|
||||
self.app.preferencesUiManager.on_preferences_edited()
|
||||
|
||||
self.back_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(back_color.name())
|
||||
)
|
||||
|
||||
new_val_sel = str(back_color.name())
|
||||
self.back_color_entry.set_value(new_val_sel)
|
||||
self.app.defaults['tools_qrcode_back_color'] = new_val_sel
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox, FCDoubleSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
81
AppGUI/preferences/tools/ToolsCornersPrefGroupUI.py
Normal file
81
AppGUI/preferences/tools/ToolsCornersPrefGroupUI.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from AppGUI.GUIElements import FCDoubleSpinner
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("machinist"):
|
||||
machinist_setting = settings.value('machinist', type=int)
|
||||
else:
|
||||
machinist_setting = 0
|
||||
|
||||
|
||||
class ToolsCornersPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, decimals=4, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent)
|
||||
super(ToolsCornersPrefGroupUI, self).__init__(self, parent=parent)
|
||||
|
||||
self.setTitle(str(_("Corner Markers Options")))
|
||||
self.decimals = decimals
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
grid0.setColumnStretch(0, 0)
|
||||
grid0.setColumnStretch(1, 1)
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
|
||||
self.param_label.setToolTip(
|
||||
_("Parameters used for this tool.")
|
||||
)
|
||||
grid0.addWidget(self.param_label, 0, 0, 1, 2)
|
||||
|
||||
# Thickness #
|
||||
self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
|
||||
self.thick_label.setToolTip(
|
||||
_("The thickness of the line that makes the corner marker.")
|
||||
)
|
||||
self.thick_entry = FCDoubleSpinner()
|
||||
self.thick_entry.set_range(0.0000, 9.9999)
|
||||
self.thick_entry.set_precision(self.decimals)
|
||||
self.thick_entry.setWrapping(True)
|
||||
self.thick_entry.setSingleStep(10 ** -self.decimals)
|
||||
|
||||
grid0.addWidget(self.thick_label, 1, 0)
|
||||
grid0.addWidget(self.thick_entry, 1, 1)
|
||||
|
||||
# Length #
|
||||
self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
|
||||
self.l_label.setToolTip(
|
||||
_("The length of the line that makes the corner marker.")
|
||||
)
|
||||
self.l_entry = FCDoubleSpinner()
|
||||
self.l_entry.set_range(-9999.9999, 9999.9999)
|
||||
self.l_entry.set_precision(self.decimals)
|
||||
self.l_entry.setSingleStep(10 ** -self.decimals)
|
||||
|
||||
# Margin #
|
||||
self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
|
||||
self.margin_label.setToolTip(
|
||||
_("Bounding box margin.")
|
||||
)
|
||||
self.margin_entry = FCDoubleSpinner()
|
||||
self.margin_entry.set_range(-9999.9999, 9999.9999)
|
||||
self.margin_entry.set_precision(self.decimals)
|
||||
self.margin_entry.setSingleStep(0.1)
|
||||
|
||||
grid0.addWidget(self.margin_label, 2, 0)
|
||||
grid0.addWidget(self.margin_entry, 2, 1)
|
||||
|
||||
grid0.addWidget(self.l_label, 4, 0)
|
||||
grid0.addWidget(self.l_entry, 4, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
@@ -1,12 +1,12 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
|
||||
from flatcamGUI.preferences import machinist_setting
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
|
||||
from AppGUI.preferences import machinist_setting
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import Qt, QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -314,3 +314,35 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(self.pagesize_combo, 17, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
# Film Tool
|
||||
self.film_color_entry.editingFinished.connect(self.on_film_color_entry)
|
||||
self.film_color_button.clicked.connect(self.on_film_color_button)
|
||||
|
||||
def on_film_color_entry(self):
|
||||
self.app.defaults['tools_film_color'] = self.film_color_entry.get_value()
|
||||
self.film_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(self.defaults['tools_film_color'])
|
||||
)
|
||||
|
||||
def on_film_color_button(self):
|
||||
current_color = QtGui.QColor(self.app.defaults['tools_film_color'])
|
||||
|
||||
c_dialog = QtWidgets.QColorDialog()
|
||||
film_color = c_dialog.getColor(initial=current_color)
|
||||
|
||||
if film_color.isValid() is False:
|
||||
return
|
||||
|
||||
# if new color is different then mark that the Preferences are changed
|
||||
if film_color != current_color:
|
||||
self.app.preferencesUiManager.on_preferences_edited()
|
||||
|
||||
self.film_color_button.setStyleSheet(
|
||||
"background-color:%s;"
|
||||
"border-color: dimgray" % str(film_color.name())
|
||||
)
|
||||
new_val_sel = str(film_color.name())
|
||||
self.film_color_entry.set_value(new_val_sel)
|
||||
self.app.defaults['tools_film_color'] = new_val_sel
|
||||
319
AppGUI/preferences/tools/ToolsISOPrefGroupUI.py
Normal file
319
AppGUI/preferences/tools/ToolsISOPrefGroupUI.py
Normal file
@@ -0,0 +1,319 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("machinist"):
|
||||
machinist_setting = settings.value('machinist', type=int)
|
||||
else:
|
||||
machinist_setting = 0
|
||||
|
||||
|
||||
class ToolsISOPrefGroupUI(OptionsGroupUI):
|
||||
def __init__(self, decimals=4, parent=None):
|
||||
super(ToolsISOPrefGroupUI, self).__init__(self, parent=parent)
|
||||
|
||||
self.setTitle(str(_("Isolation Tool Options")))
|
||||
self.decimals = decimals
|
||||
|
||||
# ## Clear non-copper regions
|
||||
self.iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
|
||||
self.iso_label.setToolTip(
|
||||
_("Create a Geometry object with\n"
|
||||
"toolpaths to cut around polygons.")
|
||||
)
|
||||
self.layout.addWidget(self.iso_label)
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
# Tool Dias
|
||||
isotdlabel = QtWidgets.QLabel('<b><font color="green">%s:</font></b>' % _('Tools Dia'))
|
||||
isotdlabel.setToolTip(
|
||||
_("Diameters of the tools, separated by comma.\n"
|
||||
"The value of the diameter has to use the dot decimals separator.\n"
|
||||
"Valid values: 0.3, 1.0")
|
||||
)
|
||||
self.tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
|
||||
|
||||
grid0.addWidget(isotdlabel, 0, 0)
|
||||
grid0.addWidget(self.tool_dia_entry, 0, 1, 1, 2)
|
||||
|
||||
# Tool order Radio Button
|
||||
self.order_label = QtWidgets.QLabel('%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"
|
||||
"'Reverse' --> means that the tools will ordered from big to small\n\n"
|
||||
"WARNING: using rest machining will automatically set the order\n"
|
||||
"in reverse and disable this control."))
|
||||
|
||||
self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
|
||||
{'label': _('Forward'), 'value': 'fwd'},
|
||||
{'label': _('Reverse'), 'value': 'rev'}])
|
||||
|
||||
grid0.addWidget(self.order_label, 1, 0)
|
||||
grid0.addWidget(self.order_radio, 1, 1, 1, 2)
|
||||
|
||||
# Tool Type Radio Button
|
||||
self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
|
||||
self.tool_type_label.setToolTip(
|
||||
_("Default tool type:\n"
|
||||
"- 'V-shape'\n"
|
||||
"- Circular")
|
||||
)
|
||||
|
||||
self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
|
||||
{'label': _('Circular'), 'value': 'C1'}])
|
||||
self.tool_type_radio.setToolTip(
|
||||
_("Default tool type:\n"
|
||||
"- 'V-shape'\n"
|
||||
"- Circular")
|
||||
)
|
||||
|
||||
grid0.addWidget(self.tool_type_label, 2, 0)
|
||||
grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
|
||||
|
||||
# Tip Dia
|
||||
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
|
||||
self.tipdialabel.setToolTip(
|
||||
_("The tip diameter for V-Shape Tool"))
|
||||
self.tipdia_entry = FCDoubleSpinner()
|
||||
self.tipdia_entry.set_precision(self.decimals)
|
||||
self.tipdia_entry.set_range(0, 1000)
|
||||
self.tipdia_entry.setSingleStep(0.1)
|
||||
|
||||
grid0.addWidget(self.tipdialabel, 3, 0)
|
||||
grid0.addWidget(self.tipdia_entry, 3, 1, 1, 2)
|
||||
|
||||
# Tip Angle
|
||||
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
|
||||
self.tipanglelabel.setToolTip(
|
||||
_("The tip angle for V-Shape Tool.\n"
|
||||
"In degrees."))
|
||||
self.tipangle_entry = FCDoubleSpinner()
|
||||
self.tipangle_entry.set_precision(self.decimals)
|
||||
self.tipangle_entry.set_range(1, 180)
|
||||
self.tipangle_entry.setSingleStep(5)
|
||||
self.tipangle_entry.setWrapping(True)
|
||||
|
||||
grid0.addWidget(self.tipanglelabel, 4, 0)
|
||||
grid0.addWidget(self.tipangle_entry, 4, 1, 1, 2)
|
||||
|
||||
# Cut Z entry
|
||||
cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
|
||||
cutzlabel.setToolTip(
|
||||
_("Depth of cut into material. Negative value.\n"
|
||||
"In FlatCAM units.")
|
||||
)
|
||||
self.cutz_entry = FCDoubleSpinner()
|
||||
self.cutz_entry.set_precision(self.decimals)
|
||||
self.cutz_entry.set_range(-9999.9999, 0.0000)
|
||||
self.cutz_entry.setSingleStep(0.1)
|
||||
|
||||
self.cutz_entry.setToolTip(
|
||||
_("Depth of cut into material. Negative value.\n"
|
||||
"In FlatCAM units.")
|
||||
)
|
||||
|
||||
grid0.addWidget(cutzlabel, 5, 0)
|
||||
grid0.addWidget(self.cutz_entry, 5, 1, 1, 2)
|
||||
|
||||
# New Diameter
|
||||
self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
|
||||
self.newdialabel.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.")
|
||||
)
|
||||
self.newdia_entry = FCDoubleSpinner()
|
||||
self.newdia_entry.set_precision(self.decimals)
|
||||
self.newdia_entry.set_range(0.0001, 9999.9999)
|
||||
self.newdia_entry.setSingleStep(0.1)
|
||||
|
||||
grid0.addWidget(self.newdialabel, 6, 0)
|
||||
grid0.addWidget(self.newdia_entry, 6, 1, 1, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 7, 0, 1, 3)
|
||||
|
||||
# Passes
|
||||
passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
|
||||
passlabel.setToolTip(
|
||||
_("Width of the isolation gap in\n"
|
||||
"number (integer) of tool widths.")
|
||||
)
|
||||
self.passes_entry = FCSpinner()
|
||||
self.passes_entry.set_range(1, 999)
|
||||
self.passes_entry.setObjectName("i_passes")
|
||||
|
||||
grid0.addWidget(passlabel, 8, 0)
|
||||
grid0.addWidget(self.passes_entry, 8, 1, 1, 2)
|
||||
|
||||
# Overlap Entry
|
||||
overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
|
||||
overlabel.setToolTip(
|
||||
_("How much (percentage) of the tool width to overlap each tool pass.")
|
||||
)
|
||||
self.overlap_entry = FCDoubleSpinner(suffix='%')
|
||||
self.overlap_entry.set_precision(self.decimals)
|
||||
self.overlap_entry.setWrapping(True)
|
||||
self.overlap_entry.set_range(0.0000, 99.9999)
|
||||
self.overlap_entry.setSingleStep(0.1)
|
||||
self.overlap_entry.setObjectName("i_overlap")
|
||||
|
||||
grid0.addWidget(overlabel, 9, 0)
|
||||
grid0.addWidget(self.overlap_entry, 9, 1, 1, 2)
|
||||
|
||||
# Milling Type Radio Button
|
||||
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
|
||||
self.milling_type_label.setToolTip(
|
||||
_("Milling type when the selected tool is of type: 'iso_op':\n"
|
||||
"- climb / best for precision milling and to reduce tool usage\n"
|
||||
"- conventional / useful when there is no backlash compensation")
|
||||
)
|
||||
|
||||
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
|
||||
{'label': _('Conventional'), 'value': 'cv'}])
|
||||
self.milling_type_radio.setToolTip(
|
||||
_("Milling type when the selected tool is of type: 'iso_op':\n"
|
||||
"- climb / best for precision milling and to reduce tool usage\n"
|
||||
"- conventional / useful when there is no backlash compensation")
|
||||
)
|
||||
|
||||
grid0.addWidget(self.milling_type_label, 10, 0)
|
||||
grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
|
||||
|
||||
# Follow
|
||||
self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
|
||||
self.follow_label.setToolTip(
|
||||
_("Generate a 'Follow' geometry.\n"
|
||||
"This means that it will cut through\n"
|
||||
"the middle of the trace.")
|
||||
)
|
||||
|
||||
self.follow_cb = FCCheckBox()
|
||||
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
|
||||
"This means that it will cut through\n"
|
||||
"the middle of the trace."))
|
||||
self.follow_cb.setObjectName("i_follow")
|
||||
|
||||
grid0.addWidget(self.follow_label, 11, 0)
|
||||
grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
|
||||
|
||||
# Isolation Type
|
||||
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
|
||||
self.iso_type_label.setToolTip(
|
||||
_("Choose how the isolation will be executed:\n"
|
||||
"- 'Full' -> complete isolation of polygons\n"
|
||||
"- 'Ext' -> will isolate only on the outside\n"
|
||||
"- 'Int' -> will isolate only on the inside\n"
|
||||
"'Exterior' isolation is almost always possible\n"
|
||||
"(with the right tool) but 'Interior'\n"
|
||||
"isolation can be done only when there is an opening\n"
|
||||
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
|
||||
)
|
||||
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
|
||||
{'label': _('Ext'), 'value': 'ext'},
|
||||
{'label': _('Int'), 'value': 'int'}])
|
||||
self.iso_type_radio.setObjectName("i_type")
|
||||
|
||||
grid0.addWidget(self.iso_type_label, 12, 0)
|
||||
grid0.addWidget(self.iso_type_radio, 12, 1, 1, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 13, 0, 1, 3)
|
||||
|
||||
# Rest machining CheckBox
|
||||
self.rest_cb = FCCheckBox('%s' % _("Rest"))
|
||||
self.rest_cb.setObjectName("i_rest_machining")
|
||||
self.rest_cb.setToolTip(
|
||||
_("If checked, use 'rest machining'.\n"
|
||||
"Basically it will isolate outside PCB features,\n"
|
||||
"using the biggest tool and continue with the next tools,\n"
|
||||
"from bigger to smaller, to isolate the copper features that\n"
|
||||
"could not be cleared by previous tool, until there is\n"
|
||||
"no more copper features to isolate or there are no more tools.\n"
|
||||
"If not checked, use the standard algorithm.")
|
||||
)
|
||||
|
||||
grid0.addWidget(self.rest_cb, 17, 0)
|
||||
|
||||
# Combine All Passes
|
||||
self.combine_passes_cb = FCCheckBox(label=_('Combine'))
|
||||
self.combine_passes_cb.setToolTip(
|
||||
_("Combine all passes into one object")
|
||||
)
|
||||
self.combine_passes_cb.setObjectName("i_combine")
|
||||
|
||||
grid0.addWidget(self.combine_passes_cb, 17, 1)
|
||||
|
||||
# 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")
|
||||
grid0.addWidget(self.except_cb, 17, 2)
|
||||
|
||||
# Isolation Scope
|
||||
self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
|
||||
self.select_label.setToolTip(
|
||||
_("Isolation scope. Choose what to isolate:\n"
|
||||
"- 'All' -> Isolate all the polygons in the object\n"
|
||||
"- 'Selection' -> Isolate a selection of polygons.\n"
|
||||
"- 'Reference Object' - will process the area specified by another object.")
|
||||
)
|
||||
self.select_combo = FCComboBox()
|
||||
self.select_combo.addItems(
|
||||
[_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
|
||||
)
|
||||
self.select_combo.setObjectName("i_selection")
|
||||
|
||||
grid0.addWidget(self.select_label, 20, 0)
|
||||
grid0.addWidget(self.select_combo, 20, 1, 1, 2)
|
||||
|
||||
# Area Shape
|
||||
self.area_shape_label = QtWidgets.QLabel('%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, 21, 0)
|
||||
grid0.addWidget(self.area_shape_radio, 21, 1, 1, 2)
|
||||
|
||||
separator_line = QtWidgets.QFrame()
|
||||
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 22, 0, 1, 3)
|
||||
|
||||
# ## Plotting type
|
||||
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
|
||||
{"label": _("Progressive"), "value": "progressive"}])
|
||||
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
|
||||
plotting_label.setToolTip(
|
||||
_("- 'Normal' - normal plotting, done at the end of the job\n"
|
||||
"- 'Progressive' - each shape is plotted after it is generated")
|
||||
)
|
||||
grid0.addWidget(plotting_label, 23, 0)
|
||||
grid0.addWidget(self.plotting_radio, 23, 1, 1, 2)
|
||||
|
||||
self.layout.addStretch()
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -45,7 +45,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
|
||||
"Valid values: 0.3, 1.0")
|
||||
)
|
||||
grid0.addWidget(ncctdlabel, 0, 0)
|
||||
self.ncc_tool_dia_entry = FCEntry(border_color='#0069A9')
|
||||
self.ncc_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
self.ncc_tool_dia_entry.setPlaceholderText(_("Comma separated values"))
|
||||
grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
|
||||
|
||||
@@ -227,7 +227,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
|
||||
# ], orientation='vertical', stretch=False)
|
||||
self.ncc_method_combo = FCComboBox()
|
||||
self.ncc_method_combo.addItems(
|
||||
[_("Standard"), _("Seed"), _("Lines")]
|
||||
[_("Standard"), _("Seed"), _("Lines"), _("Combo")]
|
||||
)
|
||||
|
||||
grid0.addWidget(methodlabel, 12, 0)
|
||||
@@ -285,7 +285,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(separator_line, 16, 0, 1, 2)
|
||||
|
||||
# Rest machining CheckBox
|
||||
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
|
||||
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
|
||||
self.ncc_rest_cb.setToolTip(
|
||||
_("If checked, use 'rest machining'.\n"
|
||||
"Basically it will clear copper outside PCB features,\n"
|
||||
@@ -336,14 +336,14 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(separator_line, 20, 0, 1, 2)
|
||||
|
||||
# ## Plotting type
|
||||
self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
|
||||
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
|
||||
{"label": _("Progressive"), "value": "progressive"}])
|
||||
plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
|
||||
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
|
||||
plotting_label.setToolTip(
|
||||
_("- 'Normal' - normal plotting, done at the end of the NCC job\n"
|
||||
"- 'Progressive' - after each shape is generated it will be plotted.")
|
||||
_("- 'Normal' - normal plotting, done at the end of the job\n"
|
||||
"- 'Progressive' - each shape is plotted after it is generated")
|
||||
)
|
||||
grid0.addWidget(plotting_label, 21, 0)
|
||||
grid0.addWidget(self.ncc_plotting_radio, 21, 1)
|
||||
grid0.addWidget(self.plotting_radio, 21, 1)
|
||||
|
||||
self.layout.addStretch()
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -53,7 +53,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
|
||||
)
|
||||
grid0.addWidget(ptdlabel, 0, 0)
|
||||
|
||||
self.painttooldia_entry = FCEntry(border_color='#0069A9')
|
||||
self.painttooldia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
self.painttooldia_entry.setPlaceholderText(_("Comma separated values"))
|
||||
|
||||
grid0.addWidget(self.painttooldia_entry, 0, 1)
|
||||
@@ -241,8 +241,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
|
||||
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(separator_line, 13, 0, 1, 2)
|
||||
|
||||
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
|
||||
self.rest_cb.setObjectName(_("Rest Machining"))
|
||||
self.rest_cb = FCCheckBox('%s' % _("Rest"))
|
||||
self.rest_cb.setObjectName(_("Rest"))
|
||||
self.rest_cb.setToolTip(
|
||||
_("If checked, use 'rest machining'.\n"
|
||||
"Basically it will clear copper outside PCB features,\n"
|
||||
@@ -277,7 +277,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
|
||||
# )
|
||||
self.selectmethod_combo = FCComboBox()
|
||||
self.selectmethod_combo.addItems(
|
||||
[_("Polygon Selection"), _("Area Selection"), _("All Polygons"), _("Reference Object")]
|
||||
[_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
|
||||
)
|
||||
|
||||
grid0.addWidget(selectlabel, 15, 0)
|
||||
@@ -302,10 +302,10 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
|
||||
# ## Plotting type
|
||||
self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
|
||||
{"label": _("Progressive"), "value": "progressive"}])
|
||||
plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
|
||||
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
|
||||
plotting_label.setToolTip(
|
||||
_("- 'Normal' - normal plotting, done at the end of the Paint job\n"
|
||||
"- 'Progressive' - after each shape is generated it will be plotted.")
|
||||
_("- 'Normal' - normal plotting, done at the end of the job\n"
|
||||
"- 'Progressive' - each shape is plotted after it is generated")
|
||||
)
|
||||
grid0.addWidget(plotting_label, 20, 0)
|
||||
grid0.addWidget(self.paint_plotting_radio, 20, 1)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,19 +1,22 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
|
||||
from flatcamGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsCornersPrefGroupUI import ToolsCornersPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
|
||||
from AppGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
|
||||
|
||||
from AppGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
|
||||
from AppGUI.preferences.tools.ToolsISOPrefGroupUI import ToolsISOPrefGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -35,6 +38,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
|
||||
self.setLayout(self.layout)
|
||||
self.decimals = decimals
|
||||
|
||||
self.tools_iso_group = ToolsISOPrefGroupUI(decimals=self.decimals)
|
||||
self.tools_iso_group.setMinimumWidth(220)
|
||||
|
||||
self.tools_ncc_group = ToolsNCCPrefGroupUI(decimals=self.decimals)
|
||||
self.tools_ncc_group.setMinimumWidth(220)
|
||||
|
||||
@@ -62,6 +68,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
|
||||
self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI(decimals=self.decimals)
|
||||
self.tools_solderpaste_group.setMinimumWidth(200)
|
||||
|
||||
self.tools_corners_group = ToolsCornersPrefGroupUI(decimals=self.decimals)
|
||||
self.tools_corners_group.setMinimumWidth(200)
|
||||
|
||||
self.tools_sub_group = ToolsSubPrefGroupUI(decimals=self.decimals)
|
||||
self.tools_sub_group.setMinimumWidth(200)
|
||||
|
||||
@@ -71,7 +80,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
|
||||
|
||||
self.vlay1 = QtWidgets.QVBoxLayout()
|
||||
self.vlay1.addWidget(self.tools_paint_group)
|
||||
self.vlay1.addWidget(self.tools_panelize_group)
|
||||
self.vlay1.addWidget(self.tools_iso_group)
|
||||
|
||||
self.vlay2 = QtWidgets.QVBoxLayout()
|
||||
self.vlay2.addWidget(self.tools_transform_group)
|
||||
@@ -84,6 +93,8 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
|
||||
|
||||
self.vlay4 = QtWidgets.QVBoxLayout()
|
||||
self.vlay4.addWidget(self.tools_solderpaste_group)
|
||||
self.vlay4.addWidget(self.tools_corners_group)
|
||||
self.vlay4.addWidget(self.tools_panelize_group)
|
||||
|
||||
self.layout.addLayout(self.vlay)
|
||||
self.layout.addLayout(self.vlay1)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCEntry, FCDoubleSpinner, FCSpinner, FCComboBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -45,7 +45,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
|
||||
"The value of the diameter has to use the dot decimals separator.\n"
|
||||
"Valid values: 0.3, 1.0")
|
||||
)
|
||||
self.nozzle_tool_dia_entry = FCEntry()
|
||||
self.nozzle_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid0.addWidget(nozzletdlabel, 0, 0)
|
||||
grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
|
||||
@@ -130,7 +130,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
|
||||
grid0.addWidget(self.z_toolchange_entry, 6, 1)
|
||||
|
||||
# X,Y Toolchange location
|
||||
self.xy_toolchange_entry = FCEntry()
|
||||
self.xy_toolchange_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
|
||||
self.xy_toolchange_label.setToolTip(
|
||||
_("The X,Y location for tool (nozzle) change.\n"
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCCheckBox
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCCheckBox
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, EvalEntry2
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -191,7 +191,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
|
||||
"The 'x' in (x, y) will be used when using Flip on X and\n"
|
||||
"the 'y' in (x, y) will be used when using Flip on Y and")
|
||||
)
|
||||
self.flip_ref_entry = EvalEntry2("(0, 0)")
|
||||
self.flip_ref_entry = NumericalEvalTupleEntry(border_color='#0069A9')
|
||||
|
||||
grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
|
||||
grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,11 +1,11 @@
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,9 +1,9 @@
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from flatcamGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
|
||||
from flatcamGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
|
||||
from flatcamGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
|
||||
from flatcamGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
|
||||
from AppGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
|
||||
from AppGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
|
||||
from AppGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
|
||||
from AppGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
|
||||
|
||||
|
||||
class UtilPreferencesUI(QtWidgets.QWidget):
|
||||
394
AppObjects/AppObject.py
Normal file
394
AppObjects/AppObject.py
Normal file
@@ -0,0 +1,394 @@
|
||||
# ###########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
# Modified by Marius Stanciu (2020) #
|
||||
# ###########################################################
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from AppObjects.ObjectCollection import *
|
||||
from AppObjects.FlatCAMCNCJob import CNCJobObject
|
||||
from AppObjects.FlatCAMDocument import DocumentObject
|
||||
from AppObjects.FlatCAMExcellon import ExcellonObject
|
||||
from AppObjects.FlatCAMGeometry import GeometryObject
|
||||
from AppObjects.FlatCAMGerber import GerberObject
|
||||
from AppObjects.FlatCAMScript import ScriptObject
|
||||
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# FlatCAM Translation
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class AppObject(QtCore.QObject):
|
||||
|
||||
# Emitted by app_obj.new_object() and passes the new object as argument, plot flag.
|
||||
# on_object_created() adds the object to the collection, plots on appropriate flag
|
||||
# and emits app_obj.new_object_available.
|
||||
object_created = QtCore.pyqtSignal(object, bool, bool)
|
||||
|
||||
# Emitted when a object has been changed (like scaled, mirrored)
|
||||
object_changed = QtCore.pyqtSignal(object)
|
||||
|
||||
# Emitted after object has been plotted.
|
||||
# Calls 'on_zoom_fit' method to fit object in scene view in main thread to prevent drawing glitches.
|
||||
object_plotted = QtCore.pyqtSignal(object)
|
||||
|
||||
plots_updated = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, app):
|
||||
super(AppObject, self).__init__()
|
||||
self.app = app
|
||||
self.inform = app.inform
|
||||
|
||||
# signals that are emitted when object state changes
|
||||
self.object_created.connect(self.on_object_created)
|
||||
self.object_changed.connect(self.on_object_changed)
|
||||
self.object_plotted.connect(self.on_object_plotted)
|
||||
self.plots_updated.connect(self.app.on_plots_updated)
|
||||
|
||||
def new_object(self, kind, name, initialize, plot=True, autoselected=True):
|
||||
"""
|
||||
Creates a new specialized FlatCAMObj and attaches it to the application,
|
||||
this is, updates the GUI accordingly, any other records and plots it.
|
||||
This method is thread-safe.
|
||||
|
||||
Notes:
|
||||
* If the name is in use, the self.collection will modify it
|
||||
when appending it to the collection. There is no need to handle
|
||||
name conflicts here.
|
||||
|
||||
:param kind: The kind of object to create. One of 'gerber', 'excellon', 'cncjob' and 'geometry'.
|
||||
:type kind: str
|
||||
:param name: Name for the object.
|
||||
:type name: str
|
||||
:param initialize: Function to run after creation of the object but before it is attached to the application.
|
||||
The function is called with 2 parameters: the new object and the App instance.
|
||||
:type initialize: function
|
||||
:param plot: If to plot the resulting object
|
||||
:param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
|
||||
self.collection
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
log.debug("AppObject.new_object()")
|
||||
obj_plot = plot
|
||||
obj_autoselected = autoselected
|
||||
|
||||
t0 = time.time() # Debug
|
||||
|
||||
# ## Create object
|
||||
classdict = {
|
||||
"gerber": GerberObject,
|
||||
"excellon": ExcellonObject,
|
||||
"cncjob": CNCJobObject,
|
||||
"geometry": GeometryObject,
|
||||
"script": ScriptObject,
|
||||
"document": DocumentObject
|
||||
}
|
||||
|
||||
log.debug("Calling object constructor...")
|
||||
|
||||
# Object creation/instantiation
|
||||
obj = classdict[kind](name)
|
||||
|
||||
obj.units = self.app.options["units"]
|
||||
|
||||
# IMPORTANT
|
||||
# The key names in defaults and options dictionary's are not random:
|
||||
# they have to have in name first the type of the object (geometry, excellon, cncjob and gerber) or how it's
|
||||
# called here, the 'kind' followed by an underline. Above the App default values from self.defaults are
|
||||
# copied to self.options. After that, below, depending on the type of
|
||||
# object that is created, it will strip the name of the object and the underline (if the original key was
|
||||
# let's say "excellon_toolchange", it will strip the excellon_) and to the obj.options the key will become
|
||||
# "toolchange"
|
||||
|
||||
for option in self.app.options:
|
||||
if option.find(kind + "_") == 0:
|
||||
oname = option[len(kind) + 1:]
|
||||
obj.options[oname] = self.app.options[option]
|
||||
|
||||
obj.isHovering = False
|
||||
obj.notHovering = True
|
||||
|
||||
# Initialize as per user request
|
||||
# User must take care to implement initialize
|
||||
# in a thread-safe way as is is likely that we
|
||||
# have been invoked in a separate thread.
|
||||
t1 = time.time()
|
||||
log.debug("%f seconds before initialize()." % (t1 - t0))
|
||||
try:
|
||||
return_value = initialize(obj, self.app)
|
||||
except Exception as e:
|
||||
msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
|
||||
msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e))
|
||||
msg += traceback.format_exc()
|
||||
self.app.inform.emit(msg)
|
||||
return "fail"
|
||||
|
||||
t2 = time.time()
|
||||
log.debug("%f seconds executing initialize()." % (t2 - t1))
|
||||
|
||||
if return_value == 'fail':
|
||||
log.debug("Object (%s) parsing and/or geometry creation failed." % kind)
|
||||
return "fail"
|
||||
|
||||
# Check units and convert if necessary
|
||||
# This condition CAN be true because initialize() can change obj.units
|
||||
if self.app.options["units"].upper() != obj.units.upper():
|
||||
self.app.inform.emit('%s: %s' % (_("Converting units to "), self.app.options["units"]))
|
||||
obj.convert_units(self.app.options["units"])
|
||||
t3 = time.time()
|
||||
log.debug("%f seconds converting units." % (t3 - t2))
|
||||
|
||||
# Create the bounding box for the object and then add the results to the obj.options
|
||||
# But not for Scripts or for Documents
|
||||
if kind != 'document' and kind != 'script':
|
||||
try:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
obj.options['xmin'] = xmin
|
||||
obj.options['ymin'] = ymin
|
||||
obj.options['xmax'] = xmax
|
||||
obj.options['ymax'] = ymax
|
||||
except Exception as e:
|
||||
log.warning("AppObject.new_object() -> The object has no bounds properties. %s" % str(e))
|
||||
return "fail"
|
||||
|
||||
try:
|
||||
if kind == 'excellon':
|
||||
obj.fill_color = self.app.defaults["excellon_plot_fill"]
|
||||
obj.outline_color = self.app.defaults["excellon_plot_line"]
|
||||
|
||||
if kind == 'gerber':
|
||||
obj.fill_color = self.app.defaults["gerber_plot_fill"]
|
||||
obj.outline_color = self.app.defaults["gerber_plot_line"]
|
||||
except Exception as e:
|
||||
log.warning("AppObject.new_object() -> setting colors error. %s" % str(e))
|
||||
|
||||
# update the KeyWords list with the name of the file
|
||||
self.app.myKeywords.append(obj.options['name'])
|
||||
|
||||
log.debug("Moving new object back to main thread.")
|
||||
|
||||
# Move the object to the main thread and let the app know that it is available.
|
||||
obj.moveToThread(self.app.main_thread)
|
||||
self.object_created.emit(obj, obj_plot, obj_autoselected)
|
||||
|
||||
return obj
|
||||
|
||||
def new_excellon_object(self):
|
||||
"""
|
||||
Creates a new, blank Excellon object.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
self.new_object('excellon', 'new_exc', lambda x, y: None, plot=False)
|
||||
|
||||
def new_geometry_object(self):
|
||||
"""
|
||||
Creates a new, blank and single-tool Geometry object.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
def initialize(obj, app):
|
||||
obj.multitool = False
|
||||
|
||||
self.new_object('geometry', 'new_geo', initialize, plot=False)
|
||||
|
||||
def new_gerber_object(self):
|
||||
"""
|
||||
Creates a new, blank Gerber object.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
def initialize(grb_obj, app):
|
||||
grb_obj.multitool = False
|
||||
grb_obj.source_file = []
|
||||
grb_obj.multigeo = False
|
||||
grb_obj.follow = False
|
||||
grb_obj.apertures = {}
|
||||
grb_obj.solid_geometry = []
|
||||
|
||||
try:
|
||||
grb_obj.options['xmin'] = 0
|
||||
grb_obj.options['ymin'] = 0
|
||||
grb_obj.options['xmax'] = 0
|
||||
grb_obj.options['ymax'] = 0
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.new_object('gerber', 'new_grb', initialize, plot=False)
|
||||
|
||||
def new_script_object(self):
|
||||
"""
|
||||
Creates a new, blank TCL Script object.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
|
||||
# "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
|
||||
# "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
|
||||
# "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
|
||||
# "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
|
||||
# "ListSys, MillDrills,\n" \
|
||||
# "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
|
||||
# "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
|
||||
# "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
|
||||
# "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
|
||||
# "# SubtractRectangle, Version, WriteGCode\n"
|
||||
|
||||
new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
|
||||
'# %s:\n' % _('TCL Tutorial is here') + \
|
||||
'# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
|
||||
'# %s:\n' % _("FlatCAM commands list")
|
||||
new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
|
||||
"(displayed in Tcl Shell).")
|
||||
|
||||
def initialize(obj, app):
|
||||
obj.source_file = deepcopy(new_source_file)
|
||||
|
||||
outname = 'new_script'
|
||||
self.new_object('script', outname, initialize, plot=False)
|
||||
|
||||
def new_document_object(self):
|
||||
"""
|
||||
Creates a new, blank Document object.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
def initialize(obj, app):
|
||||
obj.source_file = ""
|
||||
|
||||
self.new_object('document', 'new_document', initialize, plot=False)
|
||||
|
||||
def on_object_created(self, obj, plot, auto_select):
|
||||
"""
|
||||
Event callback for object creation.
|
||||
It will add the new object to the collection. After that it will plot the object in a threaded way
|
||||
|
||||
:param obj: The newly created FlatCAM object.
|
||||
:param plot: if the newly create object t obe plotted
|
||||
:param auto_select: if the newly created object to be autoselected after creation
|
||||
:return: None
|
||||
"""
|
||||
t0 = time.time() # DEBUG
|
||||
log.debug("on_object_created()")
|
||||
|
||||
# The Collection might change the name if there is a collision
|
||||
self.app.collection.append(obj)
|
||||
|
||||
# after adding the object to the collection always update the list of objects that are in the collection
|
||||
self.app.all_objects_list = self.app.collection.get_list()
|
||||
|
||||
# self.app.inform.emit('[selected] %s created & selected: %s' %
|
||||
# (str(obj.kind).capitalize(), str(obj.options['name'])))
|
||||
if obj.kind == 'gerber':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='green',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
elif obj.kind == 'excellon':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='brown',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
elif obj.kind == 'cncjob':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='blue',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
elif obj.kind == 'geometry':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='red',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
elif obj.kind == 'script':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='orange',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
elif obj.kind == 'document':
|
||||
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
|
||||
kind=obj.kind.capitalize(),
|
||||
color='darkCyan',
|
||||
name=str(obj.options['name']), tx=_("created/selected"))
|
||||
)
|
||||
|
||||
# update the SHELL auto-completer model with the name of the new object
|
||||
self.app.shell._edit.set_model_data(self.app.myKeywords)
|
||||
|
||||
if auto_select:
|
||||
# select the just opened object but deselect the previous ones
|
||||
self.app.collection.set_all_inactive()
|
||||
self.app.collection.set_active(obj.options["name"])
|
||||
else:
|
||||
self.app.collection.set_all_inactive()
|
||||
|
||||
# here it is done the object plotting
|
||||
def task(t_obj):
|
||||
with self.app.proc_container.new(_("Plotting")):
|
||||
if t_obj.kind == 'cncjob':
|
||||
t_obj.plot(kind=self.app.defaults["cncjob_plot_kind"])
|
||||
else:
|
||||
t_obj.plot()
|
||||
|
||||
t1 = time.time() # DEBUG
|
||||
log.debug("%f seconds adding object and plotting." % (t1 - t0))
|
||||
self.object_plotted.emit(t_obj)
|
||||
|
||||
# Send to worker
|
||||
# self.worker.add_task(worker_task, [self])
|
||||
if plot is True:
|
||||
self.app.worker_task.emit({'fcn': task, 'params': [obj]})
|
||||
|
||||
def on_object_changed(self, obj):
|
||||
"""
|
||||
Called whenever the geometry of the object was changed in some way.
|
||||
This require the update of it's bounding values so it can be the selected on canvas.
|
||||
Update the bounding box data from obj.options
|
||||
|
||||
:param obj: the object that was changed
|
||||
:return: None
|
||||
"""
|
||||
|
||||
try:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
except TypeError:
|
||||
return
|
||||
obj.options['xmin'] = xmin
|
||||
obj.options['ymin'] = ymin
|
||||
obj.options['xmax'] = xmax
|
||||
obj.options['ymax'] = ymax
|
||||
|
||||
log.debug("Object changed, updating the bounding box data on self.options")
|
||||
# delete the old selection shape
|
||||
self.app.delete_selection_shape()
|
||||
self.app.should_we_save = True
|
||||
|
||||
def on_object_plotted(self):
|
||||
"""
|
||||
Callback called whenever the plotted object needs to be fit into the viewport (canvas)
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.app.on_zoom_fit()
|
||||
@@ -14,8 +14,8 @@ from copy import deepcopy
|
||||
from io import StringIO
|
||||
from datetime import datetime
|
||||
|
||||
from flatcamEditors.FlatCAMTextEditor import TextEditor
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
from AppEditors.FlatCAMTextEditor import TextEditor
|
||||
from AppObjects.FlatCAMObj import *
|
||||
|
||||
from camlib import CNCjob
|
||||
|
||||
@@ -24,7 +24,7 @@ import sys
|
||||
import math
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -506,10 +506,10 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
||||
filename, _f = FCFileSaveDialog.get_saved_filename(
|
||||
caption=_("Export Machine Code ..."),
|
||||
directory=dir_file_to_save,
|
||||
filter=_filter_
|
||||
ext_filter=_filter_
|
||||
)
|
||||
except TypeError:
|
||||
filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
|
||||
filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), ext_filter=_filter_)
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
@@ -564,7 +564,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
||||
|
||||
# delete the absolute and relative position and messages in the infobar
|
||||
self.app.ui.position_label.setText("")
|
||||
self.app.ui.rel_position_label.setText("")
|
||||
# self.app.ui.rel_position_label.setText("")
|
||||
|
||||
# first clear previous text in text editor (if any)
|
||||
self.gcode_editor_tab.code_editor.clear()
|
||||
@@ -989,7 +989,6 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
||||
for key in self.cnc_tools:
|
||||
ppg = self.cnc_tools[key]['data']['ppname_g']
|
||||
if 'toolchange_custom' not in str(ppg).lower():
|
||||
print(ppg)
|
||||
if self.ui.toolchange_cb.get_value():
|
||||
self.ui.toolchange_cb.set_value(False)
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
@@ -1107,7 +1106,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
||||
except ValueError:
|
||||
# we may have a tuple with only one element and a comma
|
||||
dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
|
||||
self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
|
||||
self.plot2(tooldia=dia_plot, obj=self, visible=visible, kind=kind)
|
||||
else:
|
||||
# multiple tools usage
|
||||
if self.cnc_tools:
|
||||
@@ -1117,10 +1116,14 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
||||
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
|
||||
|
||||
# TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
|
||||
# I do this so the travel lines thickness will reflect the tool diameter
|
||||
# may work only for objects created within the app and not Gcode imported from elsewhere for which we
|
||||
# don't know the origin
|
||||
if self.origin_kind == "excellon":
|
||||
if self.exc_cnc_tools:
|
||||
for tooldia_key in self.exc_cnc_tools:
|
||||
tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
|
||||
# gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
|
||||
# gcode_parsed = self.exc_cnc_tools[tooldia_key]['gcode_parsed']
|
||||
gcode_parsed = self.gcode_parsed
|
||||
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
# File modified by: Marius Stanciu #
|
||||
# ##########################################################
|
||||
|
||||
from flatcamEditors.FlatCAMTextEditor import TextEditor
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
from AppEditors.FlatCAMTextEditor import TextEditor
|
||||
from AppObjects.FlatCAMObj import *
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -284,6 +284,27 @@ class DocumentObject(FlatCAMObj):
|
||||
self.ui.sel_color_entry.set_value(new_val)
|
||||
self.app.defaults['document_sel_color'] = new_val
|
||||
|
||||
def mirror(self, axis, point):
|
||||
pass
|
||||
|
||||
def offset(self, vect):
|
||||
pass
|
||||
|
||||
def rotate(self, angle, point):
|
||||
pass
|
||||
|
||||
def scale(self, xfactor, yfactor=None, point=None):
|
||||
pass
|
||||
|
||||
def skew(self, angle_x, angle_y, point):
|
||||
pass
|
||||
|
||||
def buffer(self, distance, join, factor=None):
|
||||
pass
|
||||
|
||||
def bounds(self, flatten=False):
|
||||
return None, None, None, None
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Returns a representation of the object as a dictionary.
|
||||
@@ -15,13 +15,14 @@ from shapely.geometry import Point, LineString
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from flatcamParsers.ParseExcellon import Excellon
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
from AppParsers.ParseExcellon import Excellon
|
||||
from AppObjects.FlatCAMObj import *
|
||||
|
||||
import itertools
|
||||
import numpy as np
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -31,7 +32,7 @@ if '_' not in builtins.__dict__:
|
||||
|
||||
class ExcellonObject(FlatCAMObj, Excellon):
|
||||
"""
|
||||
Represents Excellon/Drill code.
|
||||
Represents Excellon/Drill code. An object stored in the FlatCAM objects collection (a dict)
|
||||
"""
|
||||
|
||||
ui_type = ExcellonObjectUI
|
||||
@@ -50,6 +51,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.options.update({
|
||||
"plot": True,
|
||||
"solid": False,
|
||||
"multicolored": False,
|
||||
|
||||
"operation": "drill",
|
||||
"milling_type": "drills",
|
||||
@@ -125,6 +127,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.outline_color = self.app.defaults['excellon_plot_line']
|
||||
self.alpha_level = 'bf'
|
||||
|
||||
# store here the state of the exclusion checkbox state to be restored after building the UI
|
||||
# TODO add this in the sel.app.defaults dict and in Preferences
|
||||
self.exclusion_area_cb_is_checked = False
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
@@ -143,7 +149,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
If only one object is in exc_list parameter then this function will copy that object in the exc_final
|
||||
|
||||
:param exc_list: List or one object of ExcellonObject Objects to join.
|
||||
:type exc_list: list
|
||||
:param exc_final: Destination ExcellonObject object.
|
||||
:type exc_final: class
|
||||
:return: None
|
||||
"""
|
||||
|
||||
@@ -312,8 +320,23 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
exc_final.create_geometry()
|
||||
|
||||
def build_ui(self):
|
||||
"""
|
||||
Will (re)build the Excellon UI updating it (the tool table)
|
||||
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
FlatCAMObj.build_ui(self)
|
||||
|
||||
# Area Exception - exclusion shape added signal
|
||||
# first disconnect it from any other object
|
||||
try:
|
||||
self.app.exc_areas.e_shape_modified.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
# then connect it to the current build_ui() method
|
||||
self.app.exc_areas.e_shape_modified.connect(self.update_exclusion_table)
|
||||
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
|
||||
for row in range(self.ui.tools_table.rowCount()):
|
||||
@@ -514,6 +537,58 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
"<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
|
||||
)
|
||||
|
||||
# Build Exclusion Areas section
|
||||
e_len = len(self.app.exc_areas.exclusion_areas_storage)
|
||||
self.ui.exclusion_table.setRowCount(e_len)
|
||||
|
||||
area_id = 0
|
||||
|
||||
for area in range(e_len):
|
||||
area_id += 1
|
||||
|
||||
area_dict = self.app.exc_areas.exclusion_areas_storage[area]
|
||||
|
||||
area_id_item = QtWidgets.QTableWidgetItem('%d' % int(area_id))
|
||||
area_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 0, area_id_item) # Area id
|
||||
|
||||
object_item = QtWidgets.QTableWidgetItem('%s' % area_dict["obj_type"])
|
||||
object_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 1, object_item) # Origin Object
|
||||
|
||||
strategy_item = QtWidgets.QTableWidgetItem('%s' % area_dict["strategy"])
|
||||
strategy_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 2, strategy_item) # Strategy
|
||||
|
||||
overz_item = QtWidgets.QTableWidgetItem('%s' % area_dict["overz"])
|
||||
overz_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 3, overz_item) # Over Z
|
||||
|
||||
self.ui.exclusion_table.resizeColumnsToContents()
|
||||
self.ui.exclusion_table.resizeRowsToContents()
|
||||
|
||||
area_vheader = self.ui.exclusion_table.verticalHeader()
|
||||
area_vheader.hide()
|
||||
self.ui.exclusion_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
area_hheader = self.ui.exclusion_table.horizontalHeader()
|
||||
area_hheader.setMinimumSectionSize(10)
|
||||
area_hheader.setDefaultSectionSize(70)
|
||||
|
||||
area_hheader.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
||||
area_hheader.resizeSection(0, 20)
|
||||
area_hheader.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
|
||||
area_hheader.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||
area_hheader.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
# area_hheader.setStretchLastSection(True)
|
||||
self.ui.exclusion_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
self.ui.exclusion_table.setColumnWidth(0, 20)
|
||||
|
||||
self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight())
|
||||
self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight())
|
||||
|
||||
self.ui_connect()
|
||||
|
||||
def set_ui(self, ui):
|
||||
@@ -534,6 +609,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.form_fields.update({
|
||||
"plot": self.ui.plot_cb,
|
||||
"solid": self.ui.solid_cb,
|
||||
"multicolored": self.ui.multicolored_cb,
|
||||
|
||||
"operation": self.ui.operation_radio,
|
||||
"milling_type": self.ui.milling_type_radio,
|
||||
@@ -567,7 +643,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
"ppname_g": self.ui.pp_geo_name_cb,
|
||||
"z_pdepth": self.ui.pdepth_entry,
|
||||
"feedrate_probe": self.ui.feedrate_probe_entry,
|
||||
# "gcode_type": self.ui.excellon_gcode_type_radio
|
||||
# "gcode_type": self.ui.excellon_gcode_type_radio,
|
||||
"area_exclusion": self.ui.exclusion_cb,
|
||||
"area_shape": self.ui.area_shape_radio,
|
||||
"area_strategy": self.ui.strategy_radio,
|
||||
"area_overz": self.ui.over_z_entry,
|
||||
})
|
||||
|
||||
self.name2option = {
|
||||
@@ -628,12 +708,24 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
|
||||
assert isinstance(self.ui, ExcellonObjectUI), \
|
||||
"Expected a ExcellonObjectUI, got %s" % type(self.ui)
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
|
||||
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
|
||||
|
||||
self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
|
||||
self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
|
||||
self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
|
||||
|
||||
# Exclusion areas signals
|
||||
self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.exclusion_table_toggle_all)
|
||||
self.ui.exclusion_table.lost_focus.connect(self.clear_selection)
|
||||
self.ui.exclusion_table.itemClicked.connect(self.draw_sel_shape)
|
||||
self.ui.add_area_button.clicked.connect(self.on_add_area_click)
|
||||
self.ui.delete_area_button.clicked.connect(self.on_clear_area_click)
|
||||
self.ui.delete_sel_area_button.clicked.connect(self.on_delete_sel_areas)
|
||||
self.ui.strategy_radio.activated_custom.connect(self.on_strategy)
|
||||
|
||||
self.on_operation_type(val='drill')
|
||||
self.ui.operation_radio.activated_custom.connect(self.on_operation_type)
|
||||
|
||||
@@ -650,6 +742,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.ui.operation_radio.setEnabled(False)
|
||||
|
||||
def ui_connect(self):
|
||||
"""
|
||||
Will connect all signals in the Excellon UI that needs to be connected
|
||||
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
# selective plotting
|
||||
for row in range(self.ui.tools_table.rowCount() - 2):
|
||||
@@ -672,6 +770,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
current_widget.returnPressed.connect(self.form_to_storage)
|
||||
|
||||
def ui_disconnect(self):
|
||||
"""
|
||||
Will disconnect all signals in the Excellon UI that needs to be disconnected
|
||||
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
# selective plotting
|
||||
for row in range(self.ui.tools_table.rowCount()):
|
||||
try:
|
||||
@@ -714,6 +818,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
pass
|
||||
|
||||
def on_row_selection_change(self):
|
||||
"""
|
||||
Called when the user clicks on a row in Tools Table
|
||||
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
self.ui_disconnect()
|
||||
|
||||
sel_rows = []
|
||||
@@ -764,6 +874,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.ui_connect()
|
||||
|
||||
def storage_to_form(self, dict_storage):
|
||||
"""
|
||||
Will update the GUI with data from the "storage" in this case the dict self.tools
|
||||
|
||||
:param dict_storage: A dictionary holding the data relevant for gnerating Gcode from Excellon
|
||||
:type dict_storage: dict
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
for form_key in self.form_fields:
|
||||
for storage_key in dict_storage:
|
||||
if form_key == storage_key and form_key not in \
|
||||
@@ -775,6 +893,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
pass
|
||||
|
||||
def form_to_storage(self):
|
||||
"""
|
||||
Will update the 'storage' attribute which is the dict self.tools with data collected from GUI
|
||||
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
if self.ui.tools_table.rowCount() == 0:
|
||||
# there is no tool in tool table so we can't save the GUI elements values to storage
|
||||
return
|
||||
@@ -803,6 +927,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.ui_connect()
|
||||
|
||||
def on_operation_type(self, val):
|
||||
"""
|
||||
Called by a RadioSet activated_custom signal
|
||||
|
||||
:param val: Parameter passes by the signal that called this method
|
||||
:type val: str
|
||||
:return: None
|
||||
:rtype:
|
||||
"""
|
||||
if val == 'mill':
|
||||
self.ui.mill_type_label.show()
|
||||
self.ui.milling_type_radio.show()
|
||||
@@ -831,7 +963,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
def get_selected_tools_list(self):
|
||||
"""
|
||||
Returns the keys to the self.tools dictionary corresponding
|
||||
to the selections on the tool list in the GUI.
|
||||
to the selections on the tool list in the AppGUI.
|
||||
|
||||
:return: List of tools.
|
||||
:rtype: list
|
||||
@@ -872,7 +1004,21 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
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
|
||||
:return: has_slots and Excellon_code
|
||||
|
||||
:param whole: Integer part digits
|
||||
:type whole: int
|
||||
:param fract: Fractional part digits
|
||||
:type fract: int
|
||||
:param e_zeros: Excellon zeros suppression: LZ or TZ
|
||||
:type e_zeros: str
|
||||
:param form: Excellon format: 'dec',
|
||||
:type form: str
|
||||
:param factor: Conversion factor
|
||||
:type factor: float
|
||||
:param slot_type: How to treat slots: "routing" or "drilling"
|
||||
:type slot_type: str
|
||||
:return: A tuple: (has_slots, Excellon_code) -> (bool, str)
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
excellon_code = ''
|
||||
@@ -1039,11 +1185,23 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
|
||||
def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
|
||||
"""
|
||||
Will generate an Geometry Object allowing to cut a drill hole instead of drilling it.
|
||||
|
||||
Note: This method is a good template for generic operations as
|
||||
it takes it's options from parameters or otherwise from the
|
||||
object's options and returns a (success, msg) tuple as feedback
|
||||
for shell operations.
|
||||
|
||||
:param tools: A list of tools where the drills are to be milled or a string: "all"
|
||||
:type tools:
|
||||
:param outname: the name of the resulting Geometry object
|
||||
:type outname: str
|
||||
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
|
||||
:type tooldia: float
|
||||
:param plot: if to plot the resulting object
|
||||
:type plot: bool
|
||||
:param use_thread: if to use threading for creation of the Geometry object
|
||||
:type use_thread: bool
|
||||
:return: Success/failure condition tuple (bool, str).
|
||||
:rtype: tuple
|
||||
"""
|
||||
@@ -1088,6 +1246,15 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
return False, "Error: Milling tool is larger than hole."
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
"""
|
||||
|
||||
:param geo_obj: New object
|
||||
:type geo_obj: GeometryObject
|
||||
:param app_obj: App
|
||||
:type app_obj: FlatCAMApp.App
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
|
||||
|
||||
# ## Add properties to the object
|
||||
@@ -1100,7 +1267,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.solid_geometry = []
|
||||
|
||||
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
|
||||
@@ -1115,9 +1282,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
else:
|
||||
geo_obj.solid_geometry.append(
|
||||
Point(hole['point']).buffer(buffer_value).exterior)
|
||||
|
||||
if use_thread:
|
||||
def geo_thread(app_obj):
|
||||
app_obj.new_object("geometry", outname, geo_init, plot=plot)
|
||||
def geo_thread(a_obj):
|
||||
a_obj.app_obj.new_object("geometry", outname, geo_init, plot=plot)
|
||||
|
||||
# Create a promise with the new name
|
||||
self.app.collection.promise(outname)
|
||||
@@ -1125,17 +1293,29 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
# Send to worker
|
||||
self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
|
||||
else:
|
||||
self.app.new_object("geometry", outname, geo_init, plot=plot)
|
||||
self.app.app_obj.new_object("geometry", outname, geo_init, plot=plot)
|
||||
|
||||
return True, ""
|
||||
|
||||
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
|
||||
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
|
||||
"""
|
||||
Will generate an Geometry Object allowing to cut/mill a slot hole.
|
||||
|
||||
Note: This method is a good template for generic operations as
|
||||
it takes it's options from parameters or otherwise from the
|
||||
object's options and returns a (success, msg) tuple as feedback
|
||||
for shell operations.
|
||||
|
||||
:param tools: A list of tools where the drills are to be milled or a string: "all"
|
||||
:type tools:
|
||||
:param outname: the name of the resulting Geometry object
|
||||
:type outname: str
|
||||
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
|
||||
:type tooldia: float
|
||||
:param plot: if to plot the resulting object
|
||||
:type plot: bool
|
||||
:param use_thread: if to use threading for creation of the Geometry object
|
||||
:type use_thread: bool
|
||||
:return: Success/failure condition tuple (bool, str).
|
||||
:rtype: tuple
|
||||
"""
|
||||
@@ -1178,7 +1358,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
return False, "Error: Milling tool is larger than hole."
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
assert geo_obj.kind == 'geometry' "Initializer expected a GeometryObject, got %s" % type(geo_obj)
|
||||
assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
|
||||
|
||||
# ## Add properties to the object
|
||||
|
||||
@@ -1190,7 +1370,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.solid_geometry = []
|
||||
|
||||
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
|
||||
@@ -1220,8 +1400,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
geo_obj.solid_geometry.append(poly)
|
||||
|
||||
if use_thread:
|
||||
def geo_thread(app_obj):
|
||||
app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
|
||||
def geo_thread(a_obj):
|
||||
a_obj.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
|
||||
|
||||
# Create a promise with the new name
|
||||
self.app.collection.promise(outname)
|
||||
@@ -1229,7 +1409,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
# Send to worker
|
||||
self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
|
||||
else:
|
||||
self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot)
|
||||
self.app.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
|
||||
|
||||
return True, ""
|
||||
|
||||
@@ -1237,13 +1417,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
self.app.defaults.report_usage("excellon_on_create_milling_drills button")
|
||||
self.read_form()
|
||||
|
||||
self.generate_milling_drills(use_thread=False)
|
||||
self.generate_milling_drills(use_thread=False, plot=True)
|
||||
|
||||
def on_generate_milling_slots_button_click(self, *args):
|
||||
self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
|
||||
self.read_form()
|
||||
|
||||
self.generate_milling_slots(use_thread=False)
|
||||
self.generate_milling_slots(use_thread=False, plot=True)
|
||||
|
||||
def on_pp_changed(self):
|
||||
current_pp = self.ui.pp_excellon_name_cb.get_value()
|
||||
@@ -1363,7 +1543,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
job_name = self.options["name"] + "_cnc"
|
||||
pp_excellon_name = self.options["ppname_e"]
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
# Object initialization function for app.app_obj.new_object()
|
||||
def job_init(job_obj, app_obj):
|
||||
assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
|
||||
|
||||
@@ -1426,9 +1606,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
job_obj.create_geometry()
|
||||
|
||||
# To be run in separate thread
|
||||
def job_thread(app_obj):
|
||||
def job_thread(a_obj):
|
||||
with self.app.proc_container.new(_("Generating CNC Code")):
|
||||
app_obj.new_object("cncjob", job_name, job_init)
|
||||
a_obj.app_obj.new_object("cncjob", job_name, job_init)
|
||||
|
||||
# Create promise for the new name.
|
||||
self.app.collection.promise(job_name)
|
||||
@@ -1466,12 +1646,122 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
# self.options['startz'] = float(self.options['startz']) * factor
|
||||
# self.options['endz'] = float(self.options['endz']) * factor
|
||||
|
||||
def on_add_area_click(self):
|
||||
shape_button = self.ui.area_shape_radio
|
||||
overz_button = self.ui.over_z_entry
|
||||
strategy_radio = self.ui.strategy_radio
|
||||
cnc_button = self.ui.generate_cnc_button
|
||||
solid_geo = self.solid_geometry
|
||||
obj_type = self.kind
|
||||
|
||||
self.app.exc_areas.on_add_area_click(
|
||||
shape_button=shape_button, overz_button=overz_button, cnc_button=cnc_button, strategy_radio=strategy_radio,
|
||||
solid_geo=solid_geo, obj_type=obj_type)
|
||||
|
||||
def on_clear_area_click(self):
|
||||
if not self.app.exc_areas.exclusion_areas_storage:
|
||||
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. There are no exclusion areas to delete."))
|
||||
return
|
||||
|
||||
self.app.exc_areas.on_clear_area_click()
|
||||
self.app.exc_areas.e_shape_modified.emit()
|
||||
|
||||
def on_delete_sel_areas(self):
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
# so the duplicate rows will not be added
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
if not sel_rows:
|
||||
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. Nothing is selected."))
|
||||
return
|
||||
|
||||
self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows))
|
||||
self.app.exc_areas.e_shape_modified.emit()
|
||||
|
||||
def draw_sel_shape(self):
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
self.delete_sel_shape()
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
|
||||
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
|
||||
else:
|
||||
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.4 * 255)))[2:]
|
||||
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
|
||||
|
||||
for row in sel_rows:
|
||||
sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape']
|
||||
self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0,
|
||||
tolerance=None)
|
||||
if self.app.is_legacy is True:
|
||||
self.app.move_tool.sel_shapes.redraw()
|
||||
|
||||
def clear_selection(self):
|
||||
self.app.delete_selection_shape()
|
||||
# self.ui.exclusion_table.clearSelection()
|
||||
|
||||
def delete_sel_shape(self):
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
def update_exclusion_table(self):
|
||||
self.exclusion_area_cb_is_checked = True if self.ui.exclusion_cb.isChecked() else False
|
||||
|
||||
self.build_ui()
|
||||
self.ui.exclusion_cb.set_value(self.exclusion_area_cb_is_checked)
|
||||
|
||||
def on_strategy(self, val):
|
||||
if val == 'around':
|
||||
self.ui.over_z_label.setDisabled(True)
|
||||
self.ui.over_z_entry.setDisabled(True)
|
||||
else:
|
||||
self.ui.over_z_label.setDisabled(False)
|
||||
self.ui.over_z_entry.setDisabled(False)
|
||||
|
||||
def exclusion_table_toggle_all(self):
|
||||
"""
|
||||
will toggle the selection of all rows in Exclusion Areas table
|
||||
|
||||
:return:
|
||||
"""
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
if sel_rows:
|
||||
self.ui.exclusion_table.clearSelection()
|
||||
self.delete_sel_shape()
|
||||
else:
|
||||
self.ui.exclusion_table.selectAll()
|
||||
self.draw_sel_shape()
|
||||
|
||||
def on_solid_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
return
|
||||
self.read_form_item('solid')
|
||||
self.plot()
|
||||
|
||||
def on_multicolored_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
return
|
||||
self.read_form_item('multicolored')
|
||||
self.plot()
|
||||
|
||||
def on_plot_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
return
|
||||
@@ -1545,6 +1835,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
if not FlatCAMObj.plot(self):
|
||||
return
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
def random_color():
|
||||
r_color = np.random.rand(4)
|
||||
r_color[3] = 1
|
||||
return r_color
|
||||
else:
|
||||
def random_color():
|
||||
while True:
|
||||
r_color = np.random.rand(4)
|
||||
r_color[3] = 1
|
||||
|
||||
new_color = '#'
|
||||
for idx in range(len(r_color)):
|
||||
new_color += '%x' % int(r_color[idx] * 255)
|
||||
# do it until a valid color is generated
|
||||
# a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
|
||||
# for a total of 9 chars
|
||||
if len(new_color) == 9:
|
||||
break
|
||||
return new_color
|
||||
|
||||
# try:
|
||||
# # Plot Excellon (All polygons?)
|
||||
# if self.options["solid"]:
|
||||
@@ -1576,12 +1887,26 @@ class ExcellonObject(FlatCAMObj, Excellon):
|
||||
try:
|
||||
# Plot Excellon (All polygons?)
|
||||
if self.options["solid"]:
|
||||
for geo in self.solid_geometry:
|
||||
# for geo in self.solid_geometry:
|
||||
# self.add_shape(shape=geo,
|
||||
# color=self.outline_color,
|
||||
# face_color=random_color() if self.options['multicolored'] else self.fill_color,
|
||||
# visible=visible,
|
||||
# layer=2)
|
||||
|
||||
# plot polygons for each tool separately
|
||||
for tool in self.tools:
|
||||
# set the color here so we have one color for each tool
|
||||
geo_color = random_color()
|
||||
|
||||
# tool is a dict also
|
||||
for geo in self.tools[tool]["solid_geometry"]:
|
||||
self.add_shape(shape=geo,
|
||||
color=self.outline_color,
|
||||
face_color=self.fill_color,
|
||||
color=geo_color if self.options['multicolored'] else self.outline_color,
|
||||
face_color=geo_color if self.options['multicolored'] else self.fill_color,
|
||||
visible=visible,
|
||||
layer=2)
|
||||
|
||||
else:
|
||||
for geo in self.solid_geometry:
|
||||
self.add_shape(shape=geo.exterior, color='red', visible=visible)
|
||||
@@ -15,8 +15,7 @@ import shapely.affinity as affinity
|
||||
|
||||
from camlib import Geometry
|
||||
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
import FlatCAMTool
|
||||
from AppObjects.FlatCAMObj import *
|
||||
|
||||
import ezdxf
|
||||
import math
|
||||
@@ -25,7 +24,7 @@ from copy import deepcopy
|
||||
import traceback
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -53,6 +52,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
self.options.update({
|
||||
"plot": True,
|
||||
"multicolored": False,
|
||||
"cutz": -0.002,
|
||||
"vtipdia": 0.1,
|
||||
"vtipangle": 30,
|
||||
@@ -151,17 +151,9 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
self.param_fields = {}
|
||||
|
||||
# Event signals disconnect id holders
|
||||
self.mr = None
|
||||
self.mm = None
|
||||
self.kp = None
|
||||
|
||||
# variables to be used in area exclusion
|
||||
self.cursor_pos = (0, 0)
|
||||
self.exclusion_areas_list = []
|
||||
self.first_click = False
|
||||
self.points = []
|
||||
self.poly_drawn = False
|
||||
# store here the state of the exclusion checkbox state to be restored after building the UI
|
||||
# TODO add this in the sel.app.defaults dict and in Preferences
|
||||
self.exclusion_area_cb_is_checked = False
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
@@ -172,6 +164,15 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui_disconnect()
|
||||
FlatCAMObj.build_ui(self)
|
||||
|
||||
# Area Exception - exclusion shape added signal
|
||||
# first disconnect it from any other object
|
||||
try:
|
||||
self.app.exc_areas.e_shape_modified.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
# then connect it to the current build_ui() method
|
||||
self.app.exc_areas.e_shape_modified.connect(self.update_exclusion_table)
|
||||
|
||||
self.units = self.app.defaults['units']
|
||||
|
||||
tool_idx = 0
|
||||
@@ -192,7 +193,6 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# For INCH the decimals should be no more than 3. There are no tools under 10mils.
|
||||
|
||||
dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia'])))
|
||||
|
||||
dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
offset_item = FCComboBox()
|
||||
@@ -305,6 +305,63 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
self.set_tool_offset_visibility(selected_row)
|
||||
|
||||
# -----------------------------
|
||||
# Build Exclusion Areas section
|
||||
# -----------------------------
|
||||
e_len = len(self.app.exc_areas.exclusion_areas_storage)
|
||||
self.ui.exclusion_table.setRowCount(e_len)
|
||||
|
||||
area_id = 0
|
||||
|
||||
for area in range(e_len):
|
||||
area_id += 1
|
||||
|
||||
area_dict = self.app.exc_areas.exclusion_areas_storage[area]
|
||||
|
||||
area_id_item = QtWidgets.QTableWidgetItem('%d' % int(area_id))
|
||||
area_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 0, area_id_item) # Area id
|
||||
|
||||
object_item = QtWidgets.QTableWidgetItem('%s' % area_dict["obj_type"])
|
||||
object_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 1, object_item) # Origin Object
|
||||
|
||||
strategy_item = QtWidgets.QTableWidgetItem('%s' % area_dict["strategy"])
|
||||
strategy_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 2, strategy_item) # Strategy
|
||||
|
||||
overz_item = QtWidgets.QTableWidgetItem('%s' % area_dict["overz"])
|
||||
overz_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.ui.exclusion_table.setItem(area, 3, overz_item) # Over Z
|
||||
|
||||
self.ui.exclusion_table.resizeColumnsToContents()
|
||||
self.ui.exclusion_table.resizeRowsToContents()
|
||||
|
||||
area_vheader = self.ui.exclusion_table.verticalHeader()
|
||||
area_vheader.hide()
|
||||
self.ui.exclusion_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
area_hheader = self.ui.exclusion_table.horizontalHeader()
|
||||
area_hheader.setMinimumSectionSize(10)
|
||||
area_hheader.setDefaultSectionSize(70)
|
||||
|
||||
area_hheader.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
||||
area_hheader.resizeSection(0, 20)
|
||||
area_hheader.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
|
||||
area_hheader.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||
area_hheader.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
# area_hheader.setStretchLastSection(True)
|
||||
self.ui.exclusion_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
self.ui.exclusion_table.setColumnWidth(0, 20)
|
||||
|
||||
self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight())
|
||||
self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight())
|
||||
|
||||
# End Build Exclusion Areas
|
||||
# -----------------------------
|
||||
|
||||
# HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
|
||||
# done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
|
||||
self.ui.name_entry.set_value(self.options['name'])
|
||||
@@ -340,6 +397,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
self.form_fields.update({
|
||||
"plot": self.ui.plot_cb,
|
||||
"multicolored": self.ui.multicolored_cb,
|
||||
"cutz": self.ui.cutz_entry,
|
||||
"vtipdia": self.ui.tipdia_entry,
|
||||
"vtipangle": self.ui.tipangle_entry,
|
||||
@@ -363,7 +421,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
"endxy": self.ui.endxy_entry,
|
||||
"cnctooldia": self.ui.addtool_entry,
|
||||
"area_exclusion": self.ui.exclusion_cb,
|
||||
"area_shape":self.ui.area_shape_radio,
|
||||
"area_shape": self.ui.area_shape_radio,
|
||||
"area_strategy": self.ui.strategy_radio,
|
||||
"area_overz": self.ui.over_z_entry,
|
||||
})
|
||||
@@ -535,6 +593,8 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
|
||||
|
||||
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))
|
||||
@@ -547,8 +607,14 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
|
||||
self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed)
|
||||
|
||||
# Exclusion areas signals
|
||||
self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.exclusion_table_toggle_all)
|
||||
self.ui.exclusion_table.lost_focus.connect(self.clear_selection)
|
||||
self.ui.exclusion_table.itemClicked.connect(self.draw_sel_shape)
|
||||
self.ui.add_area_button.clicked.connect(self.on_add_area_click)
|
||||
self.ui.delete_area_button.clicked.connect(self.on_clear_area_click)
|
||||
self.ui.delete_sel_area_button.clicked.connect(self.on_delete_sel_areas)
|
||||
self.ui.strategy_radio.activated_custom.connect(self.on_strategy)
|
||||
|
||||
def on_cut_z_changed(self):
|
||||
self.old_cutz = self.ui.cutz_entry.get_value()
|
||||
@@ -595,8 +661,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui.tool_offset_entry.get_value().replace(',', '.')
|
||||
)
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
def ui_connect(self):
|
||||
@@ -604,6 +669,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# changes in geometry UI
|
||||
for i in self.param_fields:
|
||||
current_widget = self.param_fields[i]
|
||||
|
||||
if isinstance(current_widget, FCCheckBox):
|
||||
current_widget.stateChanged.connect(self.gui_form_to_storage)
|
||||
elif isinstance(current_widget, FCComboBox):
|
||||
@@ -874,7 +940,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui_connect()
|
||||
self.build_ui()
|
||||
|
||||
# if there is no tool left in the Tools Table, enable the parameters GUI
|
||||
# if there is no tool left in the Tools Table, enable the parameters AppGUI
|
||||
if self.ui.geo_tools_table.rowCount() != 0:
|
||||
self.ui.geo_param_frame.setDisabled(False)
|
||||
|
||||
@@ -950,7 +1016,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui_connect()
|
||||
self.build_ui()
|
||||
|
||||
# if there is no tool left in the Tools Table, enable the parameters GUI
|
||||
# if there is no tool left in the Tools Table, enable the parameters AppGUI
|
||||
if self.ui.geo_tools_table.rowCount() != 0:
|
||||
self.ui.geo_param_frame.setDisabled(False)
|
||||
|
||||
@@ -1131,7 +1197,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
obj_active.options['xmax'] = 0
|
||||
obj_active.options['ymax'] = 0
|
||||
|
||||
# if there is no tool left in the Tools Table, disable the parameters GUI
|
||||
# if there is no tool left in the Tools Table, disable the parameters AppGUI
|
||||
if self.ui.geo_tools_table.rowCount() == 0:
|
||||
self.ui.geo_param_frame.setDisabled(True)
|
||||
|
||||
@@ -1149,8 +1215,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
"- 'V-tip Angle' -> angle at the tip of the tool\n"
|
||||
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
|
||||
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
|
||||
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
|
||||
)
|
||||
"NB: a value of zero means that Tool Dia = 'V-tip Dia'")
|
||||
)
|
||||
self.ui.cutz_entry.setToolTip(
|
||||
_("Disabled because the tool is V-shape.\n"
|
||||
@@ -1159,8 +1224,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
"- 'V-tip Angle' -> angle at the tip of the tool\n"
|
||||
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
|
||||
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
|
||||
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
|
||||
)
|
||||
"NB: a value of zero means that Tool Dia = 'V-tip Dia'")
|
||||
)
|
||||
|
||||
self.update_cutz()
|
||||
@@ -1172,8 +1236,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui.cutz_entry.setDisabled(False)
|
||||
self.ui.cutzlabel.setToolTip(
|
||||
_("Cutting depth (negative)\n"
|
||||
"below the copper surface."
|
||||
)
|
||||
"below the copper surface.")
|
||||
)
|
||||
self.ui.cutz_entry.setToolTip('')
|
||||
|
||||
@@ -1328,16 +1391,18 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui_connect()
|
||||
|
||||
def gui_form_to_storage(self):
|
||||
self.ui_disconnect()
|
||||
|
||||
if self.ui.geo_tools_table.rowCount() == 0:
|
||||
# there is no tool in tool table so we can't save the GUI elements values to storage
|
||||
log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.")
|
||||
return
|
||||
|
||||
self.ui_disconnect()
|
||||
widget_changed = self.sender()
|
||||
try:
|
||||
widget_idx = self.ui.grid3.indexOf(widget_changed)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
log.debug("GeometryObject.gui_form_to_storage() -- wdg index -> %s" % str(e))
|
||||
return
|
||||
|
||||
# those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
|
||||
@@ -1690,15 +1755,11 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
The actual work is done by the target CNCJobObject object's
|
||||
`generate_from_geometry_2()` method.
|
||||
|
||||
:param outname:
|
||||
:param tools_dict: a dictionary that holds the whole data needed to create the Gcode
|
||||
(including the solid_geometry)
|
||||
|
||||
:param tools_in_use: the tools that are used, needed by some preprocessors
|
||||
:type list of lists, each list in the list is made out of row elements of tools table from GUI
|
||||
|
||||
:param outname:
|
||||
:param tools_dict:
|
||||
:param tools_in_use:
|
||||
:type tools_in_use list of lists, each list in the list is made out of row elements of tools table from AppGUI
|
||||
:param segx: number of segments on the X axis, for auto-levelling
|
||||
:param segy: number of segments on the Y axis, for auto-levelling
|
||||
:param plot: if True the generated object will be plotted; if False will not be plotted
|
||||
@@ -1728,7 +1789,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.app.inform.emit(msg)
|
||||
return
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
# Object initialization function for app.app_obj.new_object()
|
||||
# RUNNING ON SEPARATE THREAD!
|
||||
def job_init_single_geometry(job_obj, app_obj):
|
||||
log.debug("Creating a CNCJob out of a single-geometry")
|
||||
@@ -1742,7 +1803,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# count the tools
|
||||
tool_cnt = 0
|
||||
|
||||
dia_cnc_dict = {}
|
||||
# dia_cnc_dict = {}
|
||||
|
||||
# this turn on the FlatCAMCNCJob plot for multiple tools
|
||||
job_obj.multitool = True
|
||||
@@ -1868,7 +1929,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
})
|
||||
dia_cnc_dict.clear()
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
# Object initialization function for app.app_obj.new_object()
|
||||
# RUNNING ON SEPARATE THREAD!
|
||||
def job_init_multi_geometry(job_obj, app_obj):
|
||||
log.debug("Creating a CNCJob out of a multi-geometry")
|
||||
@@ -1882,7 +1943,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# count the tools
|
||||
tool_cnt = 0
|
||||
|
||||
dia_cnc_dict = {}
|
||||
# dia_cnc_dict = {}
|
||||
|
||||
# this turn on the FlatCAMCNCJob plot for multiple tools
|
||||
job_obj.multitool = True
|
||||
@@ -2022,15 +2083,15 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
if use_thread:
|
||||
# To be run in separate thread
|
||||
def job_thread(app_obj):
|
||||
def job_thread(a_obj):
|
||||
if self.multigeo is False:
|
||||
with self.app.proc_container.new(_("Generating CNC Code")):
|
||||
if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
|
||||
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
|
||||
if a_obj.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
|
||||
a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
|
||||
else:
|
||||
with self.app.proc_container.new(_("Generating CNC Code")):
|
||||
if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
|
||||
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
|
||||
if a_obj.app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
|
||||
a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
|
||||
|
||||
# Create a promise with the name
|
||||
self.app.collection.promise(outname)
|
||||
@@ -2038,25 +2099,17 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
else:
|
||||
if self.solid_geometry:
|
||||
self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
|
||||
self.app.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
|
||||
else:
|
||||
self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
|
||||
self.app.app_obj.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
|
||||
|
||||
def generatecncjob(
|
||||
self, outname=None,
|
||||
dia=None, offset=None,
|
||||
z_cut=None, z_move=None,
|
||||
feedrate=None, feedrate_z=None, feedrate_rapid=None,
|
||||
spindlespeed=None, dwell=None, dwelltime=None,
|
||||
multidepth=None, depthperpass=None,
|
||||
toolchange=None, toolchangez=None, toolchangexy=None,
|
||||
extracut=None, extracut_length=None, startz=None, endz=None,
|
||||
pp=None,
|
||||
segx=None, segy=None,
|
||||
use_thread=True,
|
||||
plot=True):
|
||||
def generatecncjob(self, outname=None, dia=None, offset=None, z_cut=None, z_move=None,
|
||||
feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None, dwell=None, dwelltime=None,
|
||||
multidepth=None, dpp=None, toolchange=None, toolchangez=None, toolchangexy=None,
|
||||
extracut=None, extracut_length=None, startz=None, endz=None, endxy=None, pp=None, segx=None, segy=None,
|
||||
use_thread=True, plot=True):
|
||||
"""
|
||||
Only used for TCL Command.
|
||||
Only used by the TCL Command Cncjob.
|
||||
Creates a CNCJob out of this Geometry object. The actual
|
||||
work is done by the target camlib.CNCjob
|
||||
`generate_from_geometry_2()` method.
|
||||
@@ -2073,14 +2126,17 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
:param dwell:
|
||||
:param dwelltime:
|
||||
:param multidepth:
|
||||
:param depthperpass:
|
||||
:param dpp: Depth for each pass when multidepth parameter is True
|
||||
:param toolchange:
|
||||
:param toolchangez:
|
||||
:param toolchangexy:
|
||||
:param toolchangexy: A sequence ox X,Y coordinates: a 2-length tuple or a string.
|
||||
Coordinates in X,Y plane for the Toolchange event
|
||||
:param extracut:
|
||||
:param extracut_length:
|
||||
:param startz:
|
||||
:param endz:
|
||||
:param endxy: A sequence ox X,Y coordinates: a 2-length tuple or a string.
|
||||
Coordinates in X, Y plane for the last move after ending the job.
|
||||
:param pp: Name of the preprocessor
|
||||
:param segx:
|
||||
:param segy:
|
||||
@@ -2100,7 +2156,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
|
||||
|
||||
multidepth = multidepth if multidepth is not None else self.options["multidepth"]
|
||||
depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
|
||||
depthperpass = dpp if dpp is not None else float(self.options["depthperpass"])
|
||||
|
||||
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
|
||||
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
|
||||
@@ -2110,10 +2166,21 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
startz = startz if startz is not None else self.options["startz"]
|
||||
endz = endz if endz is not None else float(self.options["endz"])
|
||||
endxy = self.options["endxy"]
|
||||
|
||||
endxy = endxy if endxy else self.options["endxy"]
|
||||
if isinstance(endxy, str):
|
||||
endxy = re.sub('[()\[\]]', '', endxy)
|
||||
if endxy and endxy != '':
|
||||
endxy = [float(eval(a)) for a in endxy.split(",")]
|
||||
|
||||
toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
|
||||
|
||||
toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
|
||||
if isinstance(toolchangexy, str):
|
||||
toolchangexy = re.sub('[()\[\]]', '', toolchangexy)
|
||||
if toolchangexy and toolchangexy != '':
|
||||
toolchangexy = [float(eval(a)) for a in toolchangexy.split(",")]
|
||||
|
||||
toolchange = toolchange if toolchange else self.options["toolchange"]
|
||||
|
||||
offset = offset if offset else 0.0
|
||||
@@ -2125,7 +2192,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
ppname_g = pp if pp else self.options["ppname_g"]
|
||||
|
||||
# Object initialization function for app.new_object()
|
||||
# Object initialization function for app.app_obj.new_object()
|
||||
# RUNNING ON SEPARATE THREAD!
|
||||
def job_init(job_obj, app_obj):
|
||||
assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
|
||||
@@ -2174,7 +2241,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# To be run in separate thread
|
||||
def job_thread(app_obj):
|
||||
with self.app.proc_container.new(_("Generating CNC Code")):
|
||||
app_obj.new_object("cncjob", outname, job_init, plot=plot)
|
||||
app_obj.app_obj.new_object("cncjob", outname, job_init, plot=plot)
|
||||
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
|
||||
|
||||
# Create a promise with the name
|
||||
@@ -2182,7 +2249,7 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
# Send to worker
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
else:
|
||||
self.app.new_object("cncjob", outname, job_init, plot=plot)
|
||||
self.app.app_obj.new_object("cncjob", outname, job_init, plot=plot)
|
||||
|
||||
# def on_plot_cb_click(self, *args):
|
||||
# if self.muted_ui:
|
||||
@@ -2469,6 +2536,110 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
|
||||
return factor
|
||||
|
||||
def on_add_area_click(self):
|
||||
shape_button = self.ui.area_shape_radio
|
||||
overz_button = self.ui.over_z_entry
|
||||
strategy_radio = self.ui.strategy_radio
|
||||
cnc_button = self.ui.generate_cnc_button
|
||||
solid_geo = self.solid_geometry
|
||||
obj_type = self.kind
|
||||
|
||||
self.app.exc_areas.on_add_area_click(
|
||||
shape_button=shape_button, overz_button=overz_button, cnc_button=cnc_button, strategy_radio=strategy_radio,
|
||||
solid_geo=solid_geo, obj_type=obj_type)
|
||||
|
||||
def on_clear_area_click(self):
|
||||
if not self.app.exc_areas.exclusion_areas_storage:
|
||||
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. There are no exclusion areas to delete."))
|
||||
return
|
||||
|
||||
self.app.exc_areas.on_clear_area_click()
|
||||
self.app.exc_areas.e_shape_modified.emit()
|
||||
|
||||
def on_delete_sel_areas(self):
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
# so the duplicate rows will not be added
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
if not sel_rows:
|
||||
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. Nothing is selected."))
|
||||
return
|
||||
|
||||
self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows))
|
||||
self.app.exc_areas.e_shape_modified.emit()
|
||||
|
||||
def draw_sel_shape(self):
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
self.delete_sel_shape()
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
|
||||
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
|
||||
else:
|
||||
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.4 * 255)))[2:]
|
||||
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
|
||||
|
||||
for row in sel_rows:
|
||||
sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape']
|
||||
self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0,
|
||||
tolerance=None)
|
||||
if self.app.is_legacy is True:
|
||||
self.app.move_tool.sel_shapes.redraw()
|
||||
|
||||
def clear_selection(self):
|
||||
self.app.delete_selection_shape()
|
||||
# self.ui.exclusion_table.clearSelection()
|
||||
|
||||
def delete_sel_shape(self):
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
def update_exclusion_table(self):
|
||||
self.exclusion_area_cb_is_checked = True if self.ui.exclusion_cb.isChecked() else False
|
||||
|
||||
self.build_ui()
|
||||
self.ui.exclusion_cb.set_value(self.exclusion_area_cb_is_checked)
|
||||
|
||||
def on_strategy(self, val):
|
||||
if val == 'around':
|
||||
self.ui.over_z_label.setDisabled(True)
|
||||
self.ui.over_z_entry.setDisabled(True)
|
||||
else:
|
||||
self.ui.over_z_label.setDisabled(False)
|
||||
self.ui.over_z_entry.setDisabled(False)
|
||||
|
||||
def exclusion_table_toggle_all(self):
|
||||
"""
|
||||
will toggle the selection of all rows in Exclusion Areas table
|
||||
|
||||
:return:
|
||||
"""
|
||||
sel_model = self.ui.exclusion_table.selectionModel()
|
||||
sel_indexes = sel_model.selectedIndexes()
|
||||
|
||||
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
|
||||
sel_rows = set()
|
||||
for idx in sel_indexes:
|
||||
sel_rows.add(idx.row())
|
||||
|
||||
if sel_rows:
|
||||
self.ui.exclusion_table.clearSelection()
|
||||
self.delete_sel_shape()
|
||||
else:
|
||||
self.ui.exclusion_table.selectAll()
|
||||
self.draw_sel_shape()
|
||||
|
||||
def plot_element(self, element, color=None, visible=None):
|
||||
|
||||
if color is None:
|
||||
@@ -2498,6 +2669,27 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
if not FlatCAMObj.plot(self):
|
||||
return
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
def random_color():
|
||||
r_color = np.random.rand(4)
|
||||
r_color[3] = 1
|
||||
return r_color
|
||||
else:
|
||||
def random_color():
|
||||
while True:
|
||||
r_color = np.random.rand(4)
|
||||
r_color[3] = 1
|
||||
|
||||
new_color = '#'
|
||||
for idx in range(len(r_color)):
|
||||
new_color += '%x' % int(r_color[idx] * 255)
|
||||
# do it until a valid color is generated
|
||||
# a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
|
||||
# for a total of 9 chars
|
||||
if len(new_color) == 9:
|
||||
break
|
||||
return new_color
|
||||
|
||||
try:
|
||||
# plot solid geometries found as members of self.tools attribute dict
|
||||
# for MultiGeo
|
||||
@@ -2505,7 +2697,8 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
for tooluid_key in self.tools:
|
||||
solid_geometry = self.tools[tooluid_key]['solid_geometry']
|
||||
self.plot_element(solid_geometry, visible=visible,
|
||||
color=self.app.defaults["geometry_plot_line"])
|
||||
color=random_color() if self.options['multicolored']
|
||||
else self.app.defaults["geometry_plot_line"])
|
||||
else:
|
||||
# plot solid geometry that may be an direct attribute of the geometry object
|
||||
# for SingleGeo
|
||||
@@ -2573,218 +2766,11 @@ class GeometryObject(FlatCAMObj, Geometry):
|
||||
self.ui.plot_cb.setChecked(True)
|
||||
self.ui_connect()
|
||||
|
||||
def on_add_area_click(self):
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
|
||||
self.app.call_source = 'geometry'
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mp)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mm)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
|
||||
|
||||
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
|
||||
self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
|
||||
# self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
|
||||
|
||||
# To be called after clicking on the plot.
|
||||
def on_mouse_release(self, event):
|
||||
if self.app.is_legacy is False:
|
||||
event_pos = event.pos
|
||||
# event_is_dragging = event.is_dragging
|
||||
right_button = 2
|
||||
else:
|
||||
event_pos = (event.xdata, event.ydata)
|
||||
# event_is_dragging = self.app.plotcanvas.is_dragging
|
||||
right_button = 3
|
||||
|
||||
event_pos = self.app.plotcanvas.translate_coords(event_pos)
|
||||
if self.app.grid_status():
|
||||
curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
|
||||
else:
|
||||
curr_pos = (event_pos[0], event_pos[1])
|
||||
|
||||
x1, y1 = curr_pos[0], curr_pos[1]
|
||||
|
||||
shape_type = self.ui.area_shape_radio.get_value()
|
||||
|
||||
# do clear area only for left mouse clicks
|
||||
if event.button == 1:
|
||||
if shape_type == "square":
|
||||
if self.first_click is False:
|
||||
self.first_click = True
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
|
||||
|
||||
self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
|
||||
if self.app.grid_status():
|
||||
self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
|
||||
else:
|
||||
self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
|
||||
|
||||
pt1 = (x0, y0)
|
||||
pt2 = (x1, y0)
|
||||
pt3 = (x1, y1)
|
||||
pt4 = (x0, y1)
|
||||
|
||||
new_rectangle = Polygon([pt1, pt2, pt3, pt4])
|
||||
self.exclusion_areas_list.append(new_rectangle)
|
||||
|
||||
# add a temporary shape on canvas
|
||||
FlatCAMTool.FlatCAMTool.draw_tool_selection_shape(self, old_coords=(x0, y0), coords=(x1, y1))
|
||||
|
||||
self.first_click = False
|
||||
def on_multicolored_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
return
|
||||
else:
|
||||
self.points.append((x1, y1))
|
||||
|
||||
if len(self.points) > 1:
|
||||
self.poly_drawn = True
|
||||
self.app.inform.emit(_("Click on next Point or click right mouse button to complete ..."))
|
||||
|
||||
return ""
|
||||
elif event.button == right_button and self.mouse_is_dragging is False:
|
||||
|
||||
shape_type = self.ui.area_shape_radio.get_value()
|
||||
|
||||
if shape_type == "square":
|
||||
self.first_click = False
|
||||
else:
|
||||
# if we finish to add a polygon
|
||||
if self.poly_drawn is True:
|
||||
try:
|
||||
# try to add the point where we last clicked if it is not already in the self.points
|
||||
last_pt = (x1, y1)
|
||||
if last_pt != self.points[-1]:
|
||||
self.points.append(last_pt)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# we need to add a Polygon and a Polygon can be made only from at least 3 points
|
||||
if len(self.points) > 2:
|
||||
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
|
||||
pol = Polygon(self.points)
|
||||
# do not add invalid polygons even if they are drawn by utility geometry
|
||||
if pol.is_valid:
|
||||
self.exclusion_areas_list.append(pol)
|
||||
FlatCAMTool.FlatCAMTool.draw_selection_shape_polygon(self, points=self.points)
|
||||
self.app.inform.emit(
|
||||
_("Zone added. Click to start adding next zone or right click to finish."))
|
||||
|
||||
self.points = []
|
||||
self.poly_drawn = False
|
||||
return
|
||||
|
||||
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self)
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
|
||||
# self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.mr)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.mm)
|
||||
# self.app.plotcanvas.graph_event_disconnect(self.kp)
|
||||
|
||||
self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
|
||||
self.app.on_mouse_click_over_plot)
|
||||
self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
|
||||
self.app.on_mouse_move_over_plot)
|
||||
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
|
||||
self.app.on_mouse_click_release_over_plot)
|
||||
|
||||
self.app.call_source = 'app'
|
||||
|
||||
if len(self.exclusion_areas_list) == 0:
|
||||
return
|
||||
|
||||
def area_disconnect(self):
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.mr)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.mm)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.kp)
|
||||
|
||||
self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
|
||||
self.app.on_mouse_click_over_plot)
|
||||
self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
|
||||
self.app.on_mouse_move_over_plot)
|
||||
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
|
||||
self.app.on_mouse_click_release_over_plot)
|
||||
self.points = []
|
||||
self.poly_drawn = False
|
||||
self.exclusion_areas_list = []
|
||||
|
||||
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
|
||||
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self)
|
||||
|
||||
self.app.call_source = "app"
|
||||
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted."))
|
||||
|
||||
# called on mouse move
|
||||
def on_mouse_move(self, event):
|
||||
shape_type = self.ui.area_shape_radio.get_value()
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
event_pos = event.pos
|
||||
event_is_dragging = event.is_dragging
|
||||
# right_button = 2
|
||||
else:
|
||||
event_pos = (event.xdata, event.ydata)
|
||||
event_is_dragging = self.app.plotcanvas.is_dragging
|
||||
# right_button = 3
|
||||
|
||||
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
|
||||
|
||||
# detect mouse dragging motion
|
||||
if event_is_dragging is True:
|
||||
self.mouse_is_dragging = True
|
||||
else:
|
||||
self.mouse_is_dragging = False
|
||||
|
||||
# update the cursor position
|
||||
if self.app.grid_status():
|
||||
# Update cursor
|
||||
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
|
||||
|
||||
self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
|
||||
symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
edge_width=self.app.defaults["global_cursor_width"],
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
# update the positions on status bar
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
|
||||
if self.cursor_pos is None:
|
||||
self.cursor_pos = (0, 0)
|
||||
|
||||
self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
|
||||
self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (self.app.dx, self.app.dy))
|
||||
|
||||
# draw the utility geometry
|
||||
if shape_type == "square":
|
||||
if self.first_click:
|
||||
self.app.delete_selection_shape()
|
||||
self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
|
||||
coords=(curr_pos[0], curr_pos[1]))
|
||||
else:
|
||||
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
|
||||
FlatCAMTool.FlatCAMTool.draw_moving_selection_shape_poly(
|
||||
self, points=self.points, data=(curr_pos[0], curr_pos[1]))
|
||||
|
||||
def on_clear_area_click(self):
|
||||
self.exclusion_areas_list = []
|
||||
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
|
||||
self.app.delete_selection_shape()
|
||||
self.read_form_item('multicolored')
|
||||
self.plot()
|
||||
|
||||
@staticmethod
|
||||
def merge(geo_list, geo_final, multigeo=None):
|
||||
@@ -14,15 +14,15 @@
|
||||
from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
|
||||
from shapely.ops import cascaded_union
|
||||
|
||||
from flatcamParsers.ParseGerber import Gerber
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
from AppParsers.ParseGerber import Gerber
|
||||
from AppObjects.FlatCAMObj import *
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
from copy import deepcopy
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
"plot": True,
|
||||
"multicolored": False,
|
||||
"solid": False,
|
||||
"tool_type": 'circular',
|
||||
"vtipdia": 0.1,
|
||||
"vtipangle": 30,
|
||||
"vcutz": -0.05,
|
||||
"isotooldia": 0.016,
|
||||
"isopasses": 1,
|
||||
"isooverlap": 15,
|
||||
"milling_type": "cl",
|
||||
"combine_passes": True,
|
||||
"noncoppermargin": 0.0,
|
||||
"noncopperrounded": False,
|
||||
"bboxmargin": 0.0,
|
||||
"bboxrounded": False,
|
||||
"aperture_display": False,
|
||||
"follow": False,
|
||||
"iso_scope": 'all',
|
||||
"iso_type": 'full'
|
||||
})
|
||||
|
||||
# type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
|
||||
@@ -197,88 +186,44 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
"plot": self.ui.plot_cb,
|
||||
"multicolored": self.ui.multicolored_cb,
|
||||
"solid": self.ui.solid_cb,
|
||||
"tool_type": self.ui.tool_type_radio,
|
||||
"vtipdia": self.ui.tipdia_spinner,
|
||||
"vtipangle": self.ui.tipangle_spinner,
|
||||
"vcutz": self.ui.cutz_spinner,
|
||||
"isotooldia": self.ui.iso_tool_dia_entry,
|
||||
"isopasses": self.ui.iso_width_entry,
|
||||
"isooverlap": self.ui.iso_overlap_entry,
|
||||
"milling_type": self.ui.milling_type_radio,
|
||||
"combine_passes": self.ui.combine_passes_cb,
|
||||
"noncoppermargin": self.ui.noncopper_margin_entry,
|
||||
"noncopperrounded": self.ui.noncopper_rounded_cb,
|
||||
"bboxmargin": self.ui.bbmargin_entry,
|
||||
"bboxrounded": self.ui.bbrounded_cb,
|
||||
"aperture_display": self.ui.aperture_table_visibility_cb,
|
||||
"follow": self.ui.follow_cb,
|
||||
"iso_scope": self.ui.iso_scope_radio,
|
||||
"iso_type": self.ui.iso_type_radio
|
||||
"follow": self.ui.follow_cb
|
||||
})
|
||||
|
||||
# Fill form fields only on object create
|
||||
self.to_form()
|
||||
|
||||
assert isinstance(self.ui, GerberObjectUI)
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
|
||||
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
|
||||
self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
|
||||
|
||||
# Tools
|
||||
self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
|
||||
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
|
||||
self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
|
||||
|
||||
self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
|
||||
self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
|
||||
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
|
||||
self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
|
||||
|
||||
# set the model for the Area Exception comboboxes
|
||||
self.ui.obj_combo.setModel(self.app.collection)
|
||||
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.ui.obj_combo.is_last = True
|
||||
self.ui.obj_combo.obj_type = {
|
||||
_("Gerber"): "Gerber", _("Geometry"): "Geometry"
|
||||
}[self.ui.type_obj_combo.get_value()]
|
||||
self.on_type_obj_index_changed()
|
||||
|
||||
self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
|
||||
self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
|
||||
# establish visibility for the GUI elements found in the slot function
|
||||
self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
if self.app.defaults["global_app_level"] == 'b':
|
||||
self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
|
||||
self.options['tool_type'] = 'circular'
|
||||
|
||||
self.ui.tool_type_label.hide()
|
||||
self.ui.tool_type_radio.hide()
|
||||
|
||||
# override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
|
||||
self.ui.tool_type_radio.set_value('circular')
|
||||
|
||||
self.ui.tipdialabel.hide()
|
||||
self.ui.tipdia_spinner.hide()
|
||||
self.ui.tipanglelabel.hide()
|
||||
self.ui.tipangle_spinner.hide()
|
||||
self.ui.cutzlabel.hide()
|
||||
self.ui.cutz_spinner.hide()
|
||||
|
||||
self.ui.apertures_table_label.hide()
|
||||
self.ui.aperture_table_visibility_cb.hide()
|
||||
self.ui.milling_type_label.hide()
|
||||
self.ui.milling_type_radio.hide()
|
||||
self.ui.iso_type_label.hide()
|
||||
self.ui.iso_type_radio.hide()
|
||||
|
||||
self.ui.follow_cb.hide()
|
||||
self.ui.except_cb.setChecked(False)
|
||||
self.ui.except_cb.hide()
|
||||
|
||||
else:
|
||||
self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
|
||||
self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
|
||||
self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
|
||||
self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
|
||||
|
||||
if self.app.defaults["gerber_buffering"] == 'no':
|
||||
self.ui.create_buffer_button.show()
|
||||
@@ -296,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
self.build_ui()
|
||||
self.units_found = self.app.defaults['units']
|
||||
|
||||
def on_calculate_tooldia(self):
|
||||
try:
|
||||
tdia = float(self.ui.tipdia_spinner.get_value())
|
||||
except Exception:
|
||||
return
|
||||
try:
|
||||
dang = float(self.ui.tipangle_spinner.get_value())
|
||||
except Exception:
|
||||
return
|
||||
try:
|
||||
cutz = float(self.ui.cutz_spinner.get_value())
|
||||
except Exception:
|
||||
return
|
||||
|
||||
cutz *= -1
|
||||
if cutz < 0:
|
||||
cutz *= -1
|
||||
|
||||
half_tip_angle = dang / 2
|
||||
|
||||
tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
|
||||
self.ui.iso_tool_dia_entry.set_value(tool_diameter)
|
||||
|
||||
def on_type_obj_index_changed(self):
|
||||
val = self.ui.type_obj_combo.get_value()
|
||||
obj_type = {"Gerber": 0, "Geometry": 2}[val]
|
||||
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.ui.obj_combo.setCurrentIndex(0)
|
||||
self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
|
||||
|
||||
def on_tool_type_change(self, state):
|
||||
if state == 'circular':
|
||||
self.ui.tipdialabel.hide()
|
||||
self.ui.tipdia_spinner.hide()
|
||||
self.ui.tipanglelabel.hide()
|
||||
self.ui.tipangle_spinner.hide()
|
||||
self.ui.cutzlabel.hide()
|
||||
self.ui.cutz_spinner.hide()
|
||||
self.ui.iso_tool_dia_entry.setDisabled(False)
|
||||
# update the value in the self.iso_tool_dia_entry once this is selected
|
||||
self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
|
||||
else:
|
||||
self.ui.tipdialabel.show()
|
||||
self.ui.tipdia_spinner.show()
|
||||
self.ui.tipanglelabel.show()
|
||||
self.ui.tipangle_spinner.show()
|
||||
self.ui.cutzlabel.show()
|
||||
self.ui.cutz_spinner.show()
|
||||
self.ui.iso_tool_dia_entry.setDisabled(True)
|
||||
# update the value in the self.iso_tool_dia_entry once this is selected
|
||||
self.on_calculate_tooldia()
|
||||
|
||||
def build_ui(self):
|
||||
FlatCAMObj.build_ui(self)
|
||||
|
||||
@@ -530,7 +423,7 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
return "fail"
|
||||
geo_obj.solid_geometry = non_copper
|
||||
|
||||
self.app.new_object("geometry", name, geo_init)
|
||||
self.app.app_obj.new_object("geometry", name, geo_init)
|
||||
|
||||
def on_generatebb_button_click(self, *args):
|
||||
self.app.defaults.report_usage("gerber_on_generatebb_button")
|
||||
@@ -556,523 +449,7 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
return "fail"
|
||||
geo_obj.solid_geometry = bounding_box
|
||||
|
||||
self.app.new_object("geometry", name, geo_init)
|
||||
|
||||
def on_iso_button_click(self, *args):
|
||||
|
||||
obj = self.app.collection.get_active()
|
||||
|
||||
self.iso_type = 2
|
||||
if self.ui.iso_type_radio.get_value() == 'ext':
|
||||
self.iso_type = 0
|
||||
if self.ui.iso_type_radio.get_value() == 'int':
|
||||
self.iso_type = 1
|
||||
|
||||
def worker_task(iso_obj, app_obj):
|
||||
with self.app.proc_container.new(_("Isolating...")):
|
||||
if self.ui.follow_cb.get_value() is True:
|
||||
iso_obj.follow_geo()
|
||||
# in the end toggle the visibility of the origin object so we can see the generated Geometry
|
||||
iso_obj.ui.plot_cb.toggle()
|
||||
else:
|
||||
app_obj.defaults.report_usage("gerber_on_iso_button")
|
||||
self.read_form()
|
||||
|
||||
iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
|
||||
self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
|
||||
|
||||
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
|
||||
|
||||
def follow_geo(self, outname=None):
|
||||
"""
|
||||
Creates a geometry object "following" the gerber paths.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# default_name = self.options["name"] + "_follow"
|
||||
# follow_name = outname or default_name
|
||||
|
||||
if outname is None:
|
||||
follow_name = self.options["name"] + "_follow"
|
||||
else:
|
||||
follow_name = outname
|
||||
|
||||
def follow_init(follow_obj, app):
|
||||
# Propagate options
|
||||
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
follow_obj.solid_geometry = self.follow_geometry
|
||||
|
||||
# TODO: Do something if this is None. Offer changing name?
|
||||
try:
|
||||
self.app.new_object("geometry", follow_name, follow_init)
|
||||
except Exception as e:
|
||||
return "Operation failed: %s" % str(e)
|
||||
|
||||
def isolate_handler(self, iso_type, iso_scope):
|
||||
|
||||
if iso_scope == 'all':
|
||||
self.isolate(iso_type=iso_type)
|
||||
else:
|
||||
# disengage the grid snapping since it may be hard to click on polygons with grid snapping on
|
||||
if self.app.ui.grid_snap_btn.isChecked():
|
||||
self.grid_status_memory = True
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
else:
|
||||
self.grid_status_memory = False
|
||||
|
||||
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
|
||||
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
|
||||
|
||||
def on_mouse_click_release(self, event):
|
||||
if self.app.is_legacy is False:
|
||||
event_pos = event.pos
|
||||
right_button = 2
|
||||
self.app.event_is_dragging = self.app.event_is_dragging
|
||||
else:
|
||||
event_pos = (event.xdata, event.ydata)
|
||||
right_button = 3
|
||||
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
|
||||
|
||||
try:
|
||||
x = float(event_pos[0])
|
||||
y = float(event_pos[1])
|
||||
except TypeError:
|
||||
return
|
||||
|
||||
event_pos = (x, y)
|
||||
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
|
||||
if self.app.grid_status():
|
||||
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
|
||||
else:
|
||||
curr_pos = (curr_pos[0], curr_pos[1])
|
||||
|
||||
if event.button == 1:
|
||||
clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
|
||||
|
||||
if self.app.selection_type is not None:
|
||||
self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
|
||||
self.app.selection_type = None
|
||||
elif clicked_poly:
|
||||
if clicked_poly not in self.poly_dict.values():
|
||||
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
|
||||
color=self.app.defaults['global_sel_draw_color'] + 'AF',
|
||||
face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
|
||||
visible=True)
|
||||
self.poly_dict[shape_id] = clicked_poly
|
||||
self.app.inform.emit(
|
||||
'%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
|
||||
_("Click to add next polygon or right click to start isolation."))
|
||||
)
|
||||
else:
|
||||
try:
|
||||
for k, v in list(self.poly_dict.items()):
|
||||
if v == clicked_poly:
|
||||
self.app.tool_shapes.remove(k)
|
||||
self.poly_dict.pop(k)
|
||||
break
|
||||
except TypeError:
|
||||
return
|
||||
self.app.inform.emit(
|
||||
'%s. %s' % (_("Removed polygon"),
|
||||
_("Click to add/remove next polygon or right click to start isolation."))
|
||||
)
|
||||
|
||||
self.app.tool_shapes.redraw()
|
||||
else:
|
||||
self.app.inform.emit(_("No polygon detected under click position."))
|
||||
elif event.button == right_button and self.app.event_is_dragging is False:
|
||||
# restore the Grid snapping if it was active before
|
||||
if self.grid_status_memory is True:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.mr)
|
||||
|
||||
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
|
||||
self.app.on_mouse_click_release_over_plot)
|
||||
|
||||
self.app.tool_shapes.clear(update=True)
|
||||
|
||||
if self.poly_dict:
|
||||
poly_list = deepcopy(list(self.poly_dict.values()))
|
||||
self.isolate(iso_type=self.iso_type, geometry=poly_list)
|
||||
self.poly_dict.clear()
|
||||
else:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
|
||||
|
||||
def selection_area_handler(self, start_pos, end_pos, sel_type):
|
||||
"""
|
||||
:param start_pos: mouse position when the selection LMB click was done
|
||||
:param end_pos: mouse position when the left mouse button is released
|
||||
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
|
||||
:return:
|
||||
"""
|
||||
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
|
||||
|
||||
# delete previous selection shape
|
||||
self.app.delete_selection_shape()
|
||||
|
||||
added_poly_count = 0
|
||||
try:
|
||||
for geo in self.solid_geometry:
|
||||
if geo not in self.poly_dict.values():
|
||||
if sel_type is True:
|
||||
if geo.within(poly_selection):
|
||||
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
|
||||
shape=geo,
|
||||
color=self.app.defaults['global_sel_draw_color'] + 'AF',
|
||||
face_color=self.app.defaults[
|
||||
'global_sel_draw_color'] + 'AF',
|
||||
visible=True)
|
||||
self.poly_dict[shape_id] = geo
|
||||
added_poly_count += 1
|
||||
else:
|
||||
if poly_selection.intersects(geo):
|
||||
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
|
||||
shape=geo,
|
||||
color=self.app.defaults['global_sel_draw_color'] + 'AF',
|
||||
face_color=self.app.defaults[
|
||||
'global_sel_draw_color'] + 'AF',
|
||||
visible=True)
|
||||
self.poly_dict[shape_id] = geo
|
||||
added_poly_count += 1
|
||||
except TypeError:
|
||||
if self.solid_geometry not in self.poly_dict.values():
|
||||
if sel_type is True:
|
||||
if self.solid_geometry.within(poly_selection):
|
||||
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',
|
||||
face_color=self.app.defaults[
|
||||
'global_sel_draw_color'] + 'AF',
|
||||
visible=True)
|
||||
self.poly_dict[shape_id] = self.solid_geometry
|
||||
added_poly_count += 1
|
||||
else:
|
||||
if poly_selection.intersects(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',
|
||||
face_color=self.app.defaults[
|
||||
'global_sel_draw_color'] + 'AF',
|
||||
visible=True)
|
||||
self.poly_dict[shape_id] = self.solid_geometry
|
||||
added_poly_count += 1
|
||||
|
||||
if added_poly_count > 0:
|
||||
self.app.tool_shapes.redraw()
|
||||
self.app.inform.emit(
|
||||
'%s: %d. %s' % (_("Added polygon"),
|
||||
int(added_poly_count),
|
||||
_("Click to add next polygon or right click to start isolation."))
|
||||
)
|
||||
else:
|
||||
self.app.inform.emit(_("No polygon in selection."))
|
||||
|
||||
def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
|
||||
milling_type=None, follow=None, plot=True):
|
||||
"""
|
||||
Creates an isolation routing geometry object in the project.
|
||||
|
||||
:param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
|
||||
:param geometry: specific geometry to isolate
|
||||
:param dia: Tool diameter
|
||||
:param passes: Number of tool widths to cut
|
||||
:param overlap: Overlap between passes in fraction of tool diameter
|
||||
:param outname: Base name of the output object
|
||||
:param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
|
||||
:param milling_type: type of milling: conventional or climbing
|
||||
:param follow: Boolean: if to generate a 'follow' geometry
|
||||
:param plot: Boolean: if to plot the resulting geometry object
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if geometry is None:
|
||||
work_geo = self.follow_geometry if follow is True else self.solid_geometry
|
||||
else:
|
||||
work_geo = geometry
|
||||
|
||||
if dia is None:
|
||||
dia = float(self.options["isotooldia"])
|
||||
|
||||
if passes is None:
|
||||
passes = int(self.options["isopasses"])
|
||||
|
||||
if overlap is None:
|
||||
overlap = float(self.options["isooverlap"])
|
||||
|
||||
overlap /= 100.0
|
||||
|
||||
combine = self.options["combine_passes"] if combine is None else bool(combine)
|
||||
|
||||
if milling_type is None:
|
||||
milling_type = self.options["milling_type"]
|
||||
|
||||
if iso_type is None:
|
||||
iso_t = 2
|
||||
else:
|
||||
iso_t = iso_type
|
||||
|
||||
base_name = self.options["name"]
|
||||
|
||||
if combine:
|
||||
if outname is None:
|
||||
if self.iso_type == 0:
|
||||
iso_name = base_name + "_ext_iso"
|
||||
elif self.iso_type == 1:
|
||||
iso_name = base_name + "_int_iso"
|
||||
else:
|
||||
iso_name = base_name + "_iso"
|
||||
else:
|
||||
iso_name = outname
|
||||
|
||||
def iso_init(geo_obj, app_obj):
|
||||
# Propagate options
|
||||
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
|
||||
|
||||
geo_obj.solid_geometry = []
|
||||
|
||||
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
|
||||
if self.ui.tool_type_radio.get_value() == 'v':
|
||||
new_cutz = self.ui.cutz_spinner.get_value()
|
||||
new_vtipdia = self.ui.tipdia_spinner.get_value()
|
||||
new_vtipangle = self.ui.tipangle_spinner.get_value()
|
||||
tool_type = 'V'
|
||||
else:
|
||||
new_cutz = self.app.defaults['geometry_cutz']
|
||||
new_vtipdia = self.app.defaults['geometry_vtipdia']
|
||||
new_vtipangle = self.app.defaults['geometry_vtipangle']
|
||||
tool_type = 'C1'
|
||||
|
||||
# store here the default data for Geometry Data
|
||||
default_data = {}
|
||||
default_data.update({
|
||||
"name": iso_name,
|
||||
"plot": self.app.defaults['geometry_plot'],
|
||||
"cutz": new_cutz,
|
||||
"vtipdia": new_vtipdia,
|
||||
"vtipangle": new_vtipangle,
|
||||
"travelz": self.app.defaults['geometry_travelz'],
|
||||
"feedrate": self.app.defaults['geometry_feedrate'],
|
||||
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
|
||||
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
|
||||
"dwell": self.app.defaults['geometry_dwell'],
|
||||
"dwelltime": self.app.defaults['geometry_dwelltime'],
|
||||
"multidepth": self.app.defaults['geometry_multidepth'],
|
||||
"ppname_g": self.app.defaults['geometry_ppname_g'],
|
||||
"depthperpass": self.app.defaults['geometry_depthperpass'],
|
||||
"extracut": self.app.defaults['geometry_extracut'],
|
||||
"extracut_length": self.app.defaults['geometry_extracut_length'],
|
||||
"toolchange": self.app.defaults['geometry_toolchange'],
|
||||
"toolchangez": self.app.defaults['geometry_toolchangez'],
|
||||
"endz": self.app.defaults['geometry_endz'],
|
||||
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
|
||||
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
|
||||
"startz": self.app.defaults['geometry_startz']
|
||||
})
|
||||
|
||||
geo_obj.tools = {}
|
||||
geo_obj.tools['1'] = {}
|
||||
geo_obj.tools.update({
|
||||
'1': {
|
||||
'tooldia': float(self.options["isotooldia"]),
|
||||
'offset': 'Path',
|
||||
'offset_value': 0.0,
|
||||
'type': _('Rough'),
|
||||
'tool_type': tool_type,
|
||||
'data': default_data,
|
||||
'solid_geometry': geo_obj.solid_geometry
|
||||
}
|
||||
})
|
||||
|
||||
for nr_pass in range(passes):
|
||||
iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia)
|
||||
|
||||
# if milling type is climb then the move is counter-clockwise around features
|
||||
mill_dir = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||
follow=follow, nr_passes=nr_pass)
|
||||
|
||||
if geom == 'fail':
|
||||
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
|
||||
return 'fail'
|
||||
geo_obj.solid_geometry.append(geom)
|
||||
|
||||
# update the geometry in the tools
|
||||
geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
|
||||
|
||||
# detect if solid_geometry is empty and this require list flattening which is "heavy"
|
||||
# or just looking in the lists (they are one level depth) and if any is not empty
|
||||
# proceed with object creation, if there are empty and the number of them is the length
|
||||
# of the list then we have an empty solid_geometry which should raise a Custom Exception
|
||||
empty_cnt = 0
|
||||
if not isinstance(geo_obj.solid_geometry, list) and \
|
||||
not isinstance(geo_obj.solid_geometry, MultiPolygon):
|
||||
geo_obj.solid_geometry = [geo_obj.solid_geometry]
|
||||
|
||||
for g in geo_obj.solid_geometry:
|
||||
if g:
|
||||
break
|
||||
else:
|
||||
empty_cnt += 1
|
||||
|
||||
if empty_cnt == len(geo_obj.solid_geometry):
|
||||
raise ValidationError("Empty Geometry", None)
|
||||
else:
|
||||
app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
|
||||
|
||||
# even if combine is checked, one pass is still single-geo
|
||||
geo_obj.multigeo = True if passes > 1 else False
|
||||
|
||||
# ############################################################
|
||||
# ########## AREA SUBTRACTION ################################
|
||||
# ############################################################
|
||||
if self.ui.except_cb.get_value():
|
||||
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
|
||||
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
|
||||
|
||||
# TODO: Do something if this is None. Offer changing name?
|
||||
self.app.new_object("geometry", iso_name, iso_init, plot=plot)
|
||||
else:
|
||||
for i in range(passes):
|
||||
|
||||
offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
|
||||
if passes > 1:
|
||||
if outname is None:
|
||||
if self.iso_type == 0:
|
||||
iso_name = base_name + "_ext_iso" + str(i + 1)
|
||||
elif self.iso_type == 1:
|
||||
iso_name = base_name + "_int_iso" + str(i + 1)
|
||||
else:
|
||||
iso_name = base_name + "_iso" + str(i + 1)
|
||||
else:
|
||||
iso_name = outname
|
||||
else:
|
||||
if outname is None:
|
||||
if self.iso_type == 0:
|
||||
iso_name = base_name + "_ext_iso"
|
||||
elif self.iso_type == 1:
|
||||
iso_name = base_name + "_int_iso"
|
||||
else:
|
||||
iso_name = base_name + "_iso"
|
||||
else:
|
||||
iso_name = outname
|
||||
|
||||
def iso_init(geo_obj, app_obj):
|
||||
# Propagate options
|
||||
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
if self.ui.tool_type_radio.get_value() == 'v':
|
||||
geo_obj.tool_type = 'V'
|
||||
else:
|
||||
geo_obj.tool_type = 'C1'
|
||||
|
||||
# if milling type is climb then the move is counter-clockwise around features
|
||||
mill_dir = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||
follow=follow,
|
||||
nr_passes=i)
|
||||
|
||||
if geom == 'fail':
|
||||
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
|
||||
return 'fail'
|
||||
|
||||
geo_obj.solid_geometry = geom
|
||||
|
||||
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
|
||||
# even if the resulting geometry is not multigeo we add the tools dict which will hold the data
|
||||
# required to be transfered to the Geometry object
|
||||
if self.ui.tool_type_radio.get_value() == 'v':
|
||||
new_cutz = self.ui.cutz_spinner.get_value()
|
||||
new_vtipdia = self.ui.tipdia_spinner.get_value()
|
||||
new_vtipangle = self.ui.tipangle_spinner.get_value()
|
||||
tool_type = 'V'
|
||||
else:
|
||||
new_cutz = self.app.defaults['geometry_cutz']
|
||||
new_vtipdia = self.app.defaults['geometry_vtipdia']
|
||||
new_vtipangle = self.app.defaults['geometry_vtipangle']
|
||||
tool_type = 'C1'
|
||||
|
||||
# store here the default data for Geometry Data
|
||||
default_data = {}
|
||||
default_data.update({
|
||||
"name": iso_name,
|
||||
"plot": self.app.defaults['geometry_plot'],
|
||||
"cutz": new_cutz,
|
||||
"vtipdia": new_vtipdia,
|
||||
"vtipangle": new_vtipangle,
|
||||
"travelz": self.app.defaults['geometry_travelz'],
|
||||
"feedrate": self.app.defaults['geometry_feedrate'],
|
||||
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
|
||||
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
|
||||
"dwell": self.app.defaults['geometry_dwell'],
|
||||
"dwelltime": self.app.defaults['geometry_dwelltime'],
|
||||
"multidepth": self.app.defaults['geometry_multidepth'],
|
||||
"ppname_g": self.app.defaults['geometry_ppname_g'],
|
||||
"depthperpass": self.app.defaults['geometry_depthperpass'],
|
||||
"extracut": self.app.defaults['geometry_extracut'],
|
||||
"extracut_length": self.app.defaults['geometry_extracut_length'],
|
||||
"toolchange": self.app.defaults['geometry_toolchange'],
|
||||
"toolchangez": self.app.defaults['geometry_toolchangez'],
|
||||
"endz": self.app.defaults['geometry_endz'],
|
||||
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
|
||||
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
|
||||
"startz": self.app.defaults['geometry_startz']
|
||||
})
|
||||
|
||||
geo_obj.tools = {}
|
||||
geo_obj.tools['1'] = {}
|
||||
geo_obj.tools.update({
|
||||
'1': {
|
||||
'tooldia': float(self.options["isotooldia"]),
|
||||
'offset': 'Path',
|
||||
'offset_value': 0.0,
|
||||
'type': _('Rough'),
|
||||
'tool_type': tool_type,
|
||||
'data': default_data,
|
||||
'solid_geometry': geo_obj.solid_geometry
|
||||
}
|
||||
})
|
||||
|
||||
# detect if solid_geometry is empty and this require list flattening which is "heavy"
|
||||
# or just looking in the lists (they are one level depth) and if any is not empty
|
||||
# proceed with object creation, if there are empty and the number of them is the length
|
||||
# of the list then we have an empty solid_geometry which should raise a Custom Exception
|
||||
empty_cnt = 0
|
||||
if not isinstance(geo_obj.solid_geometry, list):
|
||||
geo_obj.solid_geometry = [geo_obj.solid_geometry]
|
||||
|
||||
for g in geo_obj.solid_geometry:
|
||||
if g:
|
||||
break
|
||||
else:
|
||||
empty_cnt += 1
|
||||
|
||||
if empty_cnt == len(geo_obj.solid_geometry):
|
||||
raise ValidationError("Empty Geometry", None)
|
||||
else:
|
||||
app_obj.inform.emit('[success] %s: %s' %
|
||||
(_("Isolation geometry created"), geo_obj.options["name"]))
|
||||
geo_obj.multigeo = False
|
||||
|
||||
# ############################################################
|
||||
# ########## AREA SUBTRACTION ################################
|
||||
# ############################################################
|
||||
if self.ui.except_cb.get_value():
|
||||
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
|
||||
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
|
||||
|
||||
# TODO: Do something if this is None. Offer changing name?
|
||||
self.app.new_object("geometry", iso_name, iso_init, plot=plot)
|
||||
self.app.app_obj.new_object("geometry", name, geo_init)
|
||||
|
||||
def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
|
||||
# isolation_geometry produces an envelope that is going on the left of the geometry
|
||||
@@ -1114,64 +491,28 @@ class GerberObject(FlatCAMObj, Gerber):
|
||||
return 'fail'
|
||||
return geom
|
||||
|
||||
def area_subtraction(self, geo, subtractor_geo=None):
|
||||
def follow_geo(self, outname=None):
|
||||
"""
|
||||
Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
|
||||
Creates a geometry object "following" the gerber paths.
|
||||
|
||||
:param geo: target geometry from which to subtract
|
||||
:param subtractor_geo: geometry that acts as subtractor
|
||||
:return:
|
||||
:return: None
|
||||
"""
|
||||
new_geometry = []
|
||||
target_geo = geo
|
||||
|
||||
if subtractor_geo:
|
||||
sub_union = cascaded_union(subtractor_geo)
|
||||
if outname is None:
|
||||
follow_name = self.options["name"] + "_follow"
|
||||
else:
|
||||
name = self.ui.obj_combo.currentText()
|
||||
subtractor_obj = self.app.collection.get_by_name(name)
|
||||
sub_union = cascaded_union(subtractor_obj.solid_geometry)
|
||||
follow_name = outname
|
||||
|
||||
def follow_init(follow_obj, app):
|
||||
# Propagate options
|
||||
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
follow_obj.solid_geometry = self.follow_geometry
|
||||
|
||||
# TODO: Do something if this is None. Offer changing name?
|
||||
try:
|
||||
for geo_elem in target_geo:
|
||||
if isinstance(geo_elem, Polygon):
|
||||
for ring in self.poly2rings(geo_elem):
|
||||
new_geo = ring.difference(sub_union)
|
||||
if new_geo and not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
elif isinstance(geo_elem, MultiPolygon):
|
||||
for poly in geo_elem:
|
||||
for ring in self.poly2rings(poly):
|
||||
new_geo = ring.difference(sub_union)
|
||||
if new_geo and not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
elif isinstance(geo_elem, LineString):
|
||||
new_geo = geo_elem.difference(sub_union)
|
||||
if new_geo:
|
||||
if not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
elif isinstance(geo_elem, MultiLineString):
|
||||
for line_elem in geo_elem:
|
||||
new_geo = line_elem.difference(sub_union)
|
||||
if new_geo and not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
except TypeError:
|
||||
if isinstance(target_geo, Polygon):
|
||||
for ring in self.poly2rings(target_geo):
|
||||
new_geo = ring.difference(sub_union)
|
||||
if new_geo:
|
||||
if not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
elif isinstance(target_geo, LineString):
|
||||
new_geo = target_geo.difference(sub_union)
|
||||
if new_geo and not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
elif isinstance(target_geo, MultiLineString):
|
||||
for line_elem in target_geo:
|
||||
new_geo = line_elem.difference(sub_union)
|
||||
if new_geo and not new_geo.is_empty:
|
||||
new_geometry.append(new_geo)
|
||||
return new_geometry
|
||||
self.app.app_obj.new_object("geometry", follow_name, follow_init)
|
||||
except Exception as e:
|
||||
return "Operation failed: %s" % str(e)
|
||||
|
||||
def on_plot_cb_click(self, *args):
|
||||
if self.muted_ui:
|
||||
@@ -12,15 +12,15 @@
|
||||
|
||||
import inspect # TODO: For debugging only.
|
||||
|
||||
from flatcamGUI.ObjectUI import *
|
||||
from AppGUI.ObjectUI import *
|
||||
|
||||
from FlatCAMCommon import LoudDict
|
||||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
from Common import LoudDict
|
||||
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
|
||||
import sys
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -43,7 +43,7 @@ class ValidationError(Exception):
|
||||
class FlatCAMObj(QtCore.QObject):
|
||||
"""
|
||||
Base type of objects handled in FlatCAM. These become interactive
|
||||
in the GUI, can be plotted, and their options can be modified
|
||||
in the AppGUI, can be plotted, and their options can be modified
|
||||
by the user in their respective forms.
|
||||
"""
|
||||
|
||||
@@ -186,11 +186,9 @@ class FlatCAMObj(QtCore.QObject):
|
||||
|
||||
def build_ui(self):
|
||||
"""
|
||||
Sets up the UI/form for this object. Show the UI
|
||||
in the App.
|
||||
Sets up the UI/form for this object. Show the UI in the App.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
self.muted_ui = True
|
||||
@@ -247,14 +245,14 @@ class FlatCAMObj(QtCore.QObject):
|
||||
self.app.proc_container.update_view_text('')
|
||||
with self.app.proc_container.new('%s...' % _("Plotting")):
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
self.app.app_obj.object_changed.emit(self)
|
||||
|
||||
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
|
||||
|
||||
def on_scale_button_click(self):
|
||||
self.read_form()
|
||||
try:
|
||||
factor = float(eval(self.ui.scale_entry.get_value()))
|
||||
factor = float(self.ui.scale_entry.get_value())
|
||||
except Exception as e:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
|
||||
log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
|
||||
@@ -278,7 +276,7 @@ class FlatCAMObj(QtCore.QObject):
|
||||
self.app.proc_container.update_view_text('')
|
||||
with self.app.proc_container.new('%s...' % _("Plotting")):
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
self.app.app_obj.object_changed.emit(self)
|
||||
|
||||
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
|
||||
|
||||
@@ -294,7 +292,7 @@ class FlatCAMObj(QtCore.QObject):
|
||||
self.app.proc_container.update_view_text('')
|
||||
with self.app.proc_container.new('%s...' % _("Plotting")):
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
self.app.app_obj.object_changed.emit(self)
|
||||
|
||||
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
|
||||
|
||||
@@ -308,8 +306,8 @@ class FlatCAMObj(QtCore.QObject):
|
||||
for option in self.options:
|
||||
try:
|
||||
self.set_form_item(option)
|
||||
except Exception:
|
||||
self.app.log.warning("Unexpected error:", sys.exc_info())
|
||||
except Exception as err:
|
||||
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()), str(err))
|
||||
|
||||
def read_form(self):
|
||||
"""
|
||||
@@ -323,7 +321,7 @@ class FlatCAMObj(QtCore.QObject):
|
||||
try:
|
||||
self.read_form_item(option)
|
||||
except Exception:
|
||||
self.app.log.warning("Unexpected error:", sys.exc_info())
|
||||
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()))
|
||||
|
||||
def set_form_item(self, option):
|
||||
"""
|
||||
@@ -374,7 +372,7 @@ class FlatCAMObj(QtCore.QObject):
|
||||
def plot_task():
|
||||
with self.app.proc_container.new('%s...' % _("Plotting")):
|
||||
self.plot()
|
||||
self.app.object_changed.emit(self)
|
||||
self.app.app_obj.object_changed.emit(self)
|
||||
|
||||
self.app.worker_task.emit({'fcn': plot_task, 'params': []})
|
||||
|
||||
@@ -461,8 +459,13 @@ class FlatCAMObj(QtCore.QObject):
|
||||
def visible(self, value, threaded=True):
|
||||
log.debug("FlatCAMObj.visible()")
|
||||
|
||||
def worker_task(app_obj):
|
||||
self.shapes.visible = value
|
||||
# self.shapes.visible = value # maybe this is slower in VisPy? use enabled property?
|
||||
if self.shapes.visible is True:
|
||||
if value is False:
|
||||
self.shapes.visible = False
|
||||
else:
|
||||
if value is True:
|
||||
self.shapes.visible = True
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
# Not all object types has annotations
|
||||
@@ -471,11 +474,6 @@ class FlatCAMObj(QtCore.QObject):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if threaded is False:
|
||||
worker_task(app_obj=self.app)
|
||||
else:
|
||||
self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
|
||||
|
||||
@property
|
||||
def drawing_tolerance(self):
|
||||
self.units = self.app.defaults['units'].upper()
|
||||
@@ -10,16 +10,16 @@
|
||||
# File modified by: Marius Stanciu #
|
||||
# ##########################################################
|
||||
|
||||
from flatcamEditors.FlatCAMTextEditor import TextEditor
|
||||
from flatcamObjects.FlatCAMObj import *
|
||||
from flatcamGUI.ObjectUI import *
|
||||
from AppEditors.FlatCAMTextEditor import TextEditor
|
||||
from AppObjects.FlatCAMObj import *
|
||||
from AppGUI.ObjectUI import *
|
||||
|
||||
import tkinter as tk
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -183,7 +183,13 @@ class ScriptObject(FlatCAMObj):
|
||||
if self.app.ui.shell_dock.isHidden():
|
||||
self.app.ui.shell_dock.show()
|
||||
|
||||
self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
|
||||
self.app.shell.open_processing() # Disables input box.
|
||||
|
||||
# make sure that the pixmaps are not updated when running this as they will crash
|
||||
# TODO find why the pixmaps load crash when run from this object (perhaps another thread?)
|
||||
self.app.ui.fcinfo.lock_pmaps = True
|
||||
|
||||
self.script_code = self.script_editor_tab.code_editor.toPlainText()
|
||||
|
||||
old_line = ''
|
||||
for tcl_command_line in self.script_code.splitlines():
|
||||
@@ -202,8 +208,6 @@ class ScriptObject(FlatCAMObj):
|
||||
|
||||
# execute the actual Tcl command
|
||||
try:
|
||||
self.app.shell.open_processing() # Disables input box.
|
||||
|
||||
result = self.app.shell.tcl.eval(str(new_command))
|
||||
if result != 'None':
|
||||
self.app.shell.append_output(result + '\n')
|
||||
@@ -220,6 +224,7 @@ class ScriptObject(FlatCAMObj):
|
||||
log.error("Exec command Exception: %s\n" % result)
|
||||
self.app.shell.append_error('ERROR: %s\n '% result)
|
||||
|
||||
self.app.ui.fcinfo.lock_pmaps = False
|
||||
self.app.shell.close_processing()
|
||||
|
||||
def on_autocomplete_changed(self, state):
|
||||
@@ -228,6 +233,27 @@ class ScriptObject(FlatCAMObj):
|
||||
else:
|
||||
self.script_editor_tab.code_editor.completer_enable = False
|
||||
|
||||
def mirror(self, axis, point):
|
||||
pass
|
||||
|
||||
def offset(self, vect):
|
||||
pass
|
||||
|
||||
def rotate(self, angle, point):
|
||||
pass
|
||||
|
||||
def scale(self, xfactor, yfactor=None, point=None):
|
||||
pass
|
||||
|
||||
def skew(self, angle_x, angle_y, point):
|
||||
pass
|
||||
|
||||
def buffer(self, distance, join, factor=None):
|
||||
pass
|
||||
|
||||
def bounds(self, flatten=False):
|
||||
return None, None, None, None
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Returns a representation of the object as a dictionary.
|
||||
@@ -16,13 +16,13 @@ from PyQt5.QtCore import Qt, QSettings
|
||||
from PyQt5.QtGui import QColor
|
||||
# from PyQt5.QtCore import QModelIndex
|
||||
|
||||
from flatcamObjects.FlatCAMObj import FlatCAMObj
|
||||
from flatcamObjects.FlatCAMCNCJob import CNCJobObject
|
||||
from flatcamObjects.FlatCAMDocument import DocumentObject
|
||||
from flatcamObjects.FlatCAMExcellon import ExcellonObject
|
||||
from flatcamObjects.FlatCAMGeometry import GeometryObject
|
||||
from flatcamObjects.FlatCAMGerber import GerberObject
|
||||
from flatcamObjects.FlatCAMScript import ScriptObject
|
||||
from AppObjects.FlatCAMObj import FlatCAMObj
|
||||
from AppObjects.FlatCAMCNCJob import CNCJobObject
|
||||
from AppObjects.FlatCAMDocument import DocumentObject
|
||||
from AppObjects.FlatCAMExcellon import ExcellonObject
|
||||
from AppObjects.FlatCAMGeometry import GeometryObject
|
||||
from AppObjects.FlatCAMGerber import GerberObject
|
||||
from AppObjects.FlatCAMScript import ScriptObject
|
||||
|
||||
import inspect # TODO: Remove
|
||||
|
||||
@@ -32,7 +32,7 @@ from copy import deepcopy
|
||||
from numpy import Inf
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -50,12 +50,13 @@ class KeySensitiveListView(QtWidgets.QTreeView):
|
||||
def __init__(self, app, parent=None):
|
||||
super(KeySensitiveListView, self).__init__(parent)
|
||||
self.setHeaderHidden(True)
|
||||
self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)
|
||||
# self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked) # allow Edit on Tree
|
||||
self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
|
||||
|
||||
# self.setRootIsDecorated(False)
|
||||
# self.setExpandsOnDoubleClick(False)
|
||||
|
||||
# Enable dragging and dropping onto the GUI
|
||||
# Enable dragging and dropping onto the AppGUI
|
||||
self.setAcceptDrops(True)
|
||||
self.filename = ""
|
||||
self.app = app
|
||||
@@ -334,6 +335,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
||||
self.click_modifier = None
|
||||
|
||||
self.update_list_signal.connect(self.on_update_list_signal)
|
||||
self.view.activated.connect(self.on_row_activated)
|
||||
self.item_selected.connect(self.on_row_selected)
|
||||
|
||||
def promise(self, obj_name):
|
||||
log.debug("Object %s has been promised." % obj_name)
|
||||
@@ -382,8 +385,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
||||
|
||||
if type(obj) != GeometryObject:
|
||||
self.app.ui.menuprojectgeneratecnc.setVisible(False)
|
||||
if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject:
|
||||
self.app.ui.menuprojectedit.setVisible(False)
|
||||
# if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject or \
|
||||
# type(obj) != CNCJobObject:
|
||||
# self.app.ui.menuprojectedit.setVisible(False)
|
||||
if type(obj) != GerberObject and type(obj) != ExcellonObject and type(obj) != CNCJobObject:
|
||||
self.app.ui.menuprojectviewsource.setVisible(False)
|
||||
if type(obj) != GerberObject and type(obj) != GeometryObject and type(obj) != ExcellonObject and \
|
||||
@@ -550,7 +554,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
||||
while name in self.get_names():
|
||||
# ## Create a new name
|
||||
# Ends with number?
|
||||
log.debug("new_object(): Object name (%s) exists, changing." % name)
|
||||
log.debug("app_obj.new_object(): Object name (%s) exists, changing." % name)
|
||||
match = re.search(r'(.*[^\d])?(\d+)$', name)
|
||||
if match: # Yes: Increment the number!
|
||||
base = match.group(1) or ''
|
||||
@@ -985,7 +989,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
||||
|
||||
def get_list(self):
|
||||
"""
|
||||
Will return a list of all objects currently opened.
|
||||
Will return a list of all objects currently opened. Except FlatCAMScript and FlatCAMDocuments
|
||||
|
||||
:return:
|
||||
"""
|
||||
@@ -998,3 +1002,199 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
||||
|
||||
def update_view(self):
|
||||
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
|
||||
|
||||
def on_row_activated(self, index):
|
||||
if index.isValid():
|
||||
if index.internalPointer().parent_item != self.root_item:
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
|
||||
self.on_item_activated(index)
|
||||
|
||||
def on_row_selected(self, obj_name):
|
||||
"""
|
||||
This is a special string; when received it will make all Menu -> Objects entries unchecked
|
||||
It mean we clicked outside of the items and deselected all
|
||||
|
||||
:param obj_name:
|
||||
:return:
|
||||
"""
|
||||
if obj_name == 'none':
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
act.setChecked(False)
|
||||
return
|
||||
|
||||
# get the name of the selected objects and add them to a list
|
||||
name_list = []
|
||||
for obj in self.get_selected():
|
||||
name_list.append(obj.options['name'])
|
||||
|
||||
# set all actions as unchecked but the ones selected make them checked
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
act.setChecked(False)
|
||||
if act.text() in name_list:
|
||||
act.setChecked(True)
|
||||
|
||||
def on_collection_updated(self, obj, state, old_name):
|
||||
"""
|
||||
Create a menu from the object loaded in the collection.
|
||||
|
||||
:param obj: object that was changed (added, deleted, renamed)
|
||||
:param state: what was done with the object. Can be: added, deleted, delete_all, renamed
|
||||
:param old_name: the old name of the object before the action that triggered this slot happened
|
||||
:return: None
|
||||
"""
|
||||
icon_files = {
|
||||
"gerber": self.app.resource_location + "/flatcam_icon16.png",
|
||||
"excellon": self.app.resource_location + "/drill16.png",
|
||||
"cncjob": self.app.resource_location + "/cnc16.png",
|
||||
"geometry": self.app.resource_location + "/geometry16.png",
|
||||
"script": self.app.resource_location + "/script_new16.png",
|
||||
"document": self.app.resource_location + "/notes16_1.png"
|
||||
}
|
||||
|
||||
if state == 'append':
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
try:
|
||||
act.triggered.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.app.ui.menuobjects.clear()
|
||||
|
||||
gerber_list = []
|
||||
exc_list = []
|
||||
cncjob_list = []
|
||||
geo_list = []
|
||||
script_list = []
|
||||
doc_list = []
|
||||
|
||||
for name in self.get_names():
|
||||
obj_named = self.get_by_name(name)
|
||||
if obj_named.kind == 'gerber':
|
||||
gerber_list.append(name)
|
||||
elif obj_named.kind == 'excellon':
|
||||
exc_list.append(name)
|
||||
elif obj_named.kind == 'cncjob':
|
||||
cncjob_list.append(name)
|
||||
elif obj_named.kind == 'geometry':
|
||||
geo_list.append(name)
|
||||
elif obj_named.kind == 'script':
|
||||
script_list.append(name)
|
||||
elif obj_named.kind == 'document':
|
||||
doc_list.append(name)
|
||||
|
||||
def add_act(o_name):
|
||||
obj_for_icon = self.get_by_name(o_name)
|
||||
add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
|
||||
add_action.setCheckable(True)
|
||||
add_action.setText(o_name)
|
||||
add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
|
||||
add_action.triggered.connect(
|
||||
lambda: self.set_active(o_name) if add_action.isChecked() is True else
|
||||
self.set_inactive(o_name))
|
||||
self.app.ui.menuobjects.addAction(add_action)
|
||||
|
||||
for name in gerber_list:
|
||||
add_act(name)
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
|
||||
for name in exc_list:
|
||||
add_act(name)
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
|
||||
for name in cncjob_list:
|
||||
add_act(name)
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
|
||||
for name in geo_list:
|
||||
add_act(name)
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
|
||||
for name in script_list:
|
||||
add_act(name)
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
|
||||
for name in doc_list:
|
||||
add_act(name)
|
||||
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/select_all.png'),
|
||||
_('Select All')
|
||||
)
|
||||
self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
|
||||
_('Deselect All')
|
||||
)
|
||||
self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
|
||||
self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
|
||||
|
||||
elif state == 'delete':
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
if act.text() == obj.options['name']:
|
||||
try:
|
||||
act.triggered.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.app.ui.menuobjects.removeAction(act)
|
||||
break
|
||||
elif state == 'rename':
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
if act.text() == old_name:
|
||||
add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
|
||||
add_action.setText(obj.options['name'])
|
||||
add_action.setIcon(QtGui.QIcon(icon_files[obj.kind]))
|
||||
add_action.triggered.connect(
|
||||
lambda: self.set_active(obj.options['name']) if add_action.isChecked() is True else
|
||||
self.set_inactive(obj.options['name']))
|
||||
|
||||
self.app.ui.menuobjects.insertAction(act, add_action)
|
||||
|
||||
try:
|
||||
act.triggered.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.app.ui.menuobjects.removeAction(act)
|
||||
break
|
||||
elif state == 'delete_all':
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
try:
|
||||
act.triggered.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.app.ui.menuobjects.clear()
|
||||
|
||||
self.app.ui.menuobjects.addSeparator()
|
||||
self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/select_all.png'),
|
||||
_('Select All')
|
||||
)
|
||||
self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
|
||||
_('Deselect All')
|
||||
)
|
||||
self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
|
||||
self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
|
||||
|
||||
def on_objects_selection(self, on_off):
|
||||
obj_list = self.get_names()
|
||||
|
||||
if on_off is True:
|
||||
self.set_all_active()
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
try:
|
||||
act.setChecked(True)
|
||||
except Exception:
|
||||
pass
|
||||
if obj_list:
|
||||
self.app.inform[str, bool].emit('[selected] %s' % _("All objects are selected."), False)
|
||||
else:
|
||||
self.set_all_inactive()
|
||||
for act in self.app.ui.menuobjects.actions():
|
||||
try:
|
||||
act.setChecked(False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if obj_list:
|
||||
self.app.inform[str, bool].emit('%s' % _("Objects selection is cleared."), False)
|
||||
else:
|
||||
self.app.inform[str, bool].emit('', False)
|
||||
@@ -12,8 +12,8 @@ import logging
|
||||
|
||||
log = logging.getLogger('base2')
|
||||
|
||||
from flatcamParsers.ParseFont import *
|
||||
from flatcamParsers.ParseDXF_Spline import *
|
||||
from AppParsers.ParseFont import *
|
||||
from AppParsers.ParseDXF_Spline import *
|
||||
|
||||
|
||||
def distance(pt1, pt2):
|
||||
@@ -6,7 +6,7 @@
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
|
||||
from camlib import Geometry
|
||||
from camlib import Geometry, grace
|
||||
|
||||
import shapely.affinity as affinity
|
||||
from shapely.geometry import Point, LineString
|
||||
@@ -17,8 +17,7 @@ import logging
|
||||
import traceback
|
||||
from copy import deepcopy
|
||||
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
from FlatCAMCommon import GracefulException as grace
|
||||
# import AppTranslation as fcTranslate
|
||||
|
||||
import gettext
|
||||
import builtins
|
||||
@@ -967,7 +966,7 @@ class Excellon(Geometry):
|
||||
:return: None
|
||||
"""
|
||||
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry()")
|
||||
self.solid_geometry = []
|
||||
try:
|
||||
# clear the solid_geometry in self.tools
|
||||
@@ -982,7 +981,7 @@ class Excellon(Geometry):
|
||||
_("Excellon.create_geometry() -> a drill location was skipped "
|
||||
"due of not having a tool associated.\n"
|
||||
"Check the resulting GCode."))
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
|
||||
"due of not having a tool associated")
|
||||
continue
|
||||
tooldia = self.tools[drill['tool']]['C']
|
||||
@@ -1006,7 +1005,7 @@ class Excellon(Geometry):
|
||||
self.tools[tool_in_slots]['solid_geometry'].append(poly)
|
||||
self.tools[tool_in_slots]['data'] = deepcopy(self.default_data)
|
||||
except Exception as e:
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> "
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> "
|
||||
"Excellon geometry creation failed due of ERROR: %s" % str(e))
|
||||
return "fail"
|
||||
|
||||
@@ -1018,10 +1017,10 @@ class Excellon(Geometry):
|
||||
:param flatten: No used
|
||||
"""
|
||||
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.bounds()")
|
||||
|
||||
if self.solid_geometry is None or not self.tools:
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon -> solid_geometry is None")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon -> solid_geometry is None")
|
||||
return 0, 0, 0, 0
|
||||
|
||||
def bounds_rec(obj):
|
||||
@@ -1069,7 +1068,7 @@ class Excellon(Geometry):
|
||||
This function first convert to the the units found in the Excellon file but it converts tools that
|
||||
are not there yet so it has no effect other than it signal that the units are the ones in the file.
|
||||
|
||||
On object creation, in new_object(), true conversion is done because this is done at the end of the
|
||||
On object creation, in app_obj.new_object(), true conversion is done because this is done at the end of the
|
||||
Excellon file parsing, the tools are inside and self.tools is really converted from the units found
|
||||
inside the file to the FlatCAM units.
|
||||
|
||||
@@ -1092,7 +1091,7 @@ class Excellon(Geometry):
|
||||
else:
|
||||
log.error("Unsupported units: %s" % str(obj_units))
|
||||
factor = 1.0
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
|
||||
|
||||
self.units = obj_units
|
||||
self.scale(factor, factor)
|
||||
@@ -1118,7 +1117,7 @@ class Excellon(Geometry):
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.scale()")
|
||||
|
||||
if yfactor is None:
|
||||
yfactor = xfactor
|
||||
@@ -1183,7 +1182,7 @@ class Excellon(Geometry):
|
||||
:type vect: tuple
|
||||
:return: None
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.offset()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.offset()")
|
||||
|
||||
dx, dy = vect
|
||||
|
||||
@@ -1243,7 +1242,7 @@ class Excellon(Geometry):
|
||||
:type point: list
|
||||
:return: None
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.mirror()")
|
||||
|
||||
px, py = point
|
||||
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
|
||||
@@ -1309,7 +1308,7 @@ class Excellon(Geometry):
|
||||
See shapely manual for more information:
|
||||
http://toblerity.org/shapely/manual.html#affine-transformations
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.skew()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.skew()")
|
||||
|
||||
if angle_x is None:
|
||||
angle_x = 0.0
|
||||
@@ -1396,7 +1395,7 @@ class Excellon(Geometry):
|
||||
:param point: tuple of coordinates (x, y)
|
||||
:return: None
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.rotate()")
|
||||
|
||||
if angle == 0:
|
||||
return
|
||||
@@ -1479,7 +1478,7 @@ class Excellon(Geometry):
|
||||
:param join: The type of line joint used by the shapely buffer method: round, square, bevel
|
||||
:return: None
|
||||
"""
|
||||
log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
|
||||
log.debug("AppParsers.ParseExcellon.Excellon.buffer()")
|
||||
|
||||
if distance == 0:
|
||||
return
|
||||
@@ -22,7 +22,7 @@ from fontTools import ttLib
|
||||
|
||||
import logging
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
@@ -1,5 +1,5 @@
|
||||
from PyQt5 import QtWidgets
|
||||
from camlib import Geometry, arc, arc_angle, ApertureMacro
|
||||
from camlib import Geometry, arc, arc_angle, ApertureMacro, grace
|
||||
|
||||
import numpy as np
|
||||
import re
|
||||
@@ -14,9 +14,8 @@ import shapely.affinity as affinity
|
||||
from shapely.geometry import box as shply_box, Polygon, LineString, Point, MultiPolygon
|
||||
|
||||
from lxml import etree as ET
|
||||
from flatcamParsers.ParseSVG import svgparselength, getsvggeo
|
||||
from FlatCAMCommon import GracefulException as grace
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
from AppParsers.ParseSVG import svgparselength, getsvggeo
|
||||
import AppTranslation as fcTranslate
|
||||
|
||||
import gettext
|
||||
import builtins
|
||||
@@ -1477,7 +1476,7 @@ class Gerber(Geometry):
|
||||
sol_geo_length = 1
|
||||
|
||||
try:
|
||||
if buff_length == 0 and sol_geo_length in [0, 1]:
|
||||
if buff_length == 0 and sol_geo_length in [0, 1] and self.solid_geometry.area == 0:
|
||||
log.error("Object is not Gerber file or empty. Aborting Object creation.")
|
||||
return 'fail'
|
||||
except TypeError as e:
|
||||
@@ -1763,7 +1762,7 @@ class Gerber(Geometry):
|
||||
:return: None
|
||||
"""
|
||||
|
||||
log.debug("flatcamParsers.ParseGerber.Gerber.import_svg()")
|
||||
log.debug("AppParsers.ParseGerber.Gerber.import_svg()")
|
||||
|
||||
# Parse into list of shapely objects
|
||||
svg_tree = ET.parse(filename)
|
||||
@@ -2389,7 +2388,7 @@ class Gerber(Geometry):
|
||||
geo_p = shply_box(minx, miny, maxx, maxy)
|
||||
new_geo_el['solid'] = geo_p
|
||||
else:
|
||||
log.debug("flatcamParsers.ParseGerber.Gerber.buffer() --> "
|
||||
log.debug("AppParsers.ParseGerber.Gerber.buffer() --> "
|
||||
"ap type not supported")
|
||||
else:
|
||||
new_geo_el['solid'] = geo_el['follow'].buffer(
|
||||
@@ -6,7 +6,7 @@
|
||||
# MIT Licence #
|
||||
# ############################################################
|
||||
|
||||
from camlib import arc, three_point_circle
|
||||
from camlib import arc, three_point_circle, grace
|
||||
|
||||
import numpy as np
|
||||
import re
|
||||
@@ -18,8 +18,7 @@ import sys
|
||||
from shapely.ops import unary_union
|
||||
from shapely.geometry import LineString, Point
|
||||
|
||||
from FlatCAMCommon import GracefulException as grace
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
# import AppTranslation as fcTranslate
|
||||
import gettext
|
||||
import builtins
|
||||
|
||||
@@ -5,50 +5,27 @@
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMCommon import GracefulException as grace
|
||||
from Common import GracefulException as grace
|
||||
|
||||
from shapely.geometry import Point, Polygon, LineString, MultiPolygon
|
||||
from shapely.ops import unary_union
|
||||
from shapely.geometry import Polygon, LineString, MultiPolygon
|
||||
|
||||
from copy import copy, deepcopy
|
||||
import numpy as np
|
||||
|
||||
import zlib
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
||||
class ToolPDF(FlatCAMTool):
|
||||
"""
|
||||
Parse a PDF file.
|
||||
Reference here: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf
|
||||
Return a list of geometries
|
||||
"""
|
||||
toolName = _("PDF Import Tool")
|
||||
class PdfParser(QtCore.QObject):
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
super().__init__()
|
||||
self.app = app
|
||||
self.decimals = self.app.decimals
|
||||
self.step_per_circles = self.app.defaults["gerber_circle_steps"]
|
||||
|
||||
self.stream_re = re.compile(b'.*?FlateDecode.*?stream(.*?)endstream', re.S)
|
||||
|
||||
# detect stroke color change; it means a new object to be created
|
||||
self.stroke_color_re = re.compile(r'^\s*(\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)\s*RG$')
|
||||
|
||||
@@ -112,73 +89,10 @@ class ToolPDF(FlatCAMTool):
|
||||
self.gs['transform'] = []
|
||||
self.gs['line_width'] = [] # each element is a float
|
||||
|
||||
self.pdf_decompressed = {}
|
||||
|
||||
# key = file name and extension
|
||||
# value is a dict to store the parsed content of the PDF
|
||||
self.pdf_parsed = {}
|
||||
|
||||
# QTimer for periodic check
|
||||
self.check_thread = QtCore.QTimer()
|
||||
|
||||
# Every time a parser is started we add a promise; every time a parser finished we remove a promise
|
||||
# when empty we start the layer rendering
|
||||
self.parsing_promises = []
|
||||
|
||||
# conversion factor to INCH
|
||||
self.point_to_unit_factor = 0.01388888888
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.defaults.report_usage("ToolPDF()")
|
||||
|
||||
self.set_tool_ui()
|
||||
self.on_open_pdf_click()
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='Ctrl+Q', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
pass
|
||||
|
||||
def on_open_pdf_click(self):
|
||||
"""
|
||||
File menu callback for opening an PDF file.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
self.app.defaults.report_usage("ToolPDF.on_open_pdf_click()")
|
||||
self.app.log.debug("ToolPDF.on_open_pdf_click()")
|
||||
|
||||
_filter_ = "Adobe PDF Files (*.pdf);;" \
|
||||
"All Files (*.*)"
|
||||
|
||||
try:
|
||||
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"),
|
||||
directory=self.app.get_last_folder(),
|
||||
filter=_filter_)
|
||||
except TypeError:
|
||||
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"), filter=_filter_)
|
||||
|
||||
if len(filenames) == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s.' % _("Open PDF cancelled"))
|
||||
else:
|
||||
# start the parsing timer with a period of 1 second
|
||||
self.periodic_check(1000)
|
||||
|
||||
for filename in filenames:
|
||||
if filename != '':
|
||||
self.app.worker_task.emit({'fcn': self.open_pdf,
|
||||
'params': [filename]})
|
||||
|
||||
def open_pdf(self, filename):
|
||||
short_name = filename.split('/')[-1].split('\\')[-1]
|
||||
self.parsing_promises.append(short_name)
|
||||
self.pdf_parsed[short_name] = {}
|
||||
self.pdf_parsed[short_name]['pdf'] = {}
|
||||
self.pdf_parsed[short_name]['filename'] = filename
|
||||
|
||||
self.pdf_decompressed[short_name] = ''
|
||||
def parse_pdf(self, pdf_content):
|
||||
|
||||
# the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
|
||||
if self.app.defaults['units'].upper() == 'MM':
|
||||
@@ -188,252 +102,6 @@ class ToolPDF(FlatCAMTool):
|
||||
# 1 inch = 72 points => 1 point = 1 / 72 = 0.01388888888 inch
|
||||
self.point_to_unit_factor = 1 / 72
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise grace
|
||||
|
||||
with self.app.proc_container.new(_("Parsing PDF file ...")):
|
||||
with open(filename, "rb") as f:
|
||||
pdf = f.read()
|
||||
|
||||
stream_nr = 0
|
||||
for s in re.findall(self.stream_re, pdf):
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise grace
|
||||
|
||||
stream_nr += 1
|
||||
log.debug(" PDF STREAM: %d\n" % stream_nr)
|
||||
s = s.strip(b'\r\n')
|
||||
try:
|
||||
self.pdf_decompressed[short_name] += (zlib.decompress(s).decode('UTF-8') + '\r\n')
|
||||
except Exception as e:
|
||||
log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
|
||||
|
||||
self.pdf_parsed[short_name]['pdf'] = self.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
|
||||
# we used it, now we delete it
|
||||
self.pdf_decompressed[short_name] = ''
|
||||
|
||||
# removal from list is done in a multithreaded way therefore not always the removal can be done
|
||||
# try to remove until it's done
|
||||
try:
|
||||
while True:
|
||||
self.parsing_promises.remove(short_name)
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
log.debug("ToolPDF.open_pdf() --> %s" % str(e))
|
||||
self.app.inform.emit('[success] %s: %s' % (_("Opened"), str(filename)))
|
||||
|
||||
def layer_rendering_as_excellon(self, filename, ap_dict, layer_nr):
|
||||
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
|
||||
|
||||
# store the points here until reconstitution:
|
||||
# keys are diameters and values are list of (x,y) coords
|
||||
points = {}
|
||||
|
||||
def obj_init(exc_obj, app_obj):
|
||||
clear_geo = [geo_el['clear'] for geo_el in ap_dict['0']['geometry']]
|
||||
|
||||
for geo in clear_geo:
|
||||
xmin, ymin, xmax, ymax = geo.bounds
|
||||
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
|
||||
|
||||
# for drill bits, even in INCH, it's enough 3 decimals
|
||||
correction_factor = 0.974
|
||||
dia = (xmax - xmin) * correction_factor
|
||||
dia = round(dia, 3)
|
||||
if dia in points:
|
||||
points[dia].append(center)
|
||||
else:
|
||||
points[dia] = [center]
|
||||
|
||||
sorted_dia = sorted(points.keys())
|
||||
|
||||
name_tool = 0
|
||||
for dia in sorted_dia:
|
||||
name_tool += 1
|
||||
|
||||
# create tools dictionary
|
||||
spec = {"C": dia, 'solid_geometry': []}
|
||||
exc_obj.tools[str(name_tool)] = spec
|
||||
|
||||
# create drill list of dictionaries
|
||||
for dia_points in points:
|
||||
if dia == dia_points:
|
||||
for pt in points[dia_points]:
|
||||
exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
|
||||
break
|
||||
|
||||
ret = exc_obj.create_geometry()
|
||||
if ret == 'fail':
|
||||
log.debug("Could not create geometry for Excellon object.")
|
||||
return "fail"
|
||||
for tool in exc_obj.tools:
|
||||
if exc_obj.tools[tool]['solid_geometry']:
|
||||
return
|
||||
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
|
||||
(_("No geometry found in file"), outname))
|
||||
return "fail"
|
||||
|
||||
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
|
||||
|
||||
ret_val = self.app.new_object("excellon", outname, obj_init, autoselected=False)
|
||||
if ret_val == 'fail':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_('Open PDF file failed.'))
|
||||
return
|
||||
# Register recent file
|
||||
self.app.file_opened.emit("excellon", filename)
|
||||
# GUI feedback
|
||||
self.app.inform.emit('[success] %s: %s' %
|
||||
(_("Rendered"), outname))
|
||||
|
||||
def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
|
||||
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
|
||||
|
||||
def obj_init(grb_obj):
|
||||
|
||||
grb_obj.apertures = ap_dict
|
||||
|
||||
poly_buff = []
|
||||
follow_buf = []
|
||||
for ap in grb_obj.apertures:
|
||||
for k in grb_obj.apertures[ap]:
|
||||
if k == 'geometry':
|
||||
for geo_el in ap_dict[ap][k]:
|
||||
if 'solid' in geo_el:
|
||||
poly_buff.append(geo_el['solid'])
|
||||
if 'follow' in geo_el:
|
||||
follow_buf.append(geo_el['follow'])
|
||||
poly_buff = unary_union(poly_buff)
|
||||
|
||||
if '0' in grb_obj.apertures:
|
||||
global_clear_geo = []
|
||||
if 'geometry' in grb_obj.apertures['0']:
|
||||
for geo_el in ap_dict['0']['geometry']:
|
||||
if 'clear' in geo_el:
|
||||
global_clear_geo.append(geo_el['clear'])
|
||||
|
||||
if global_clear_geo:
|
||||
solid = []
|
||||
for apid in grb_obj.apertures:
|
||||
if 'geometry' in grb_obj.apertures[apid]:
|
||||
for elem in grb_obj.apertures[apid]['geometry']:
|
||||
if 'solid' in elem:
|
||||
solid_geo = deepcopy(elem['solid'])
|
||||
for clear_geo in global_clear_geo:
|
||||
# Make sure that the clear_geo is within the solid_geo otherwise we loose
|
||||
# the solid_geometry. We want for clear_geometry just to cut into solid_geometry
|
||||
# not to delete it
|
||||
if clear_geo.within(solid_geo):
|
||||
solid_geo = solid_geo.difference(clear_geo)
|
||||
if solid_geo.is_empty:
|
||||
solid_geo = elem['solid']
|
||||
try:
|
||||
for poly in solid_geo:
|
||||
solid.append(poly)
|
||||
except TypeError:
|
||||
solid.append(solid_geo)
|
||||
poly_buff = deepcopy(MultiPolygon(solid))
|
||||
|
||||
follow_buf = unary_union(follow_buf)
|
||||
|
||||
try:
|
||||
poly_buff = poly_buff.buffer(0.0000001)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
poly_buff = poly_buff.buffer(-0.0000001)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
grb_obj.solid_geometry = deepcopy(poly_buff)
|
||||
grb_obj.follow_geometry = deepcopy(follow_buf)
|
||||
|
||||
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
|
||||
|
||||
ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
|
||||
if ret == 'fail':
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_('Open PDF file failed.'))
|
||||
return
|
||||
# Register recent file
|
||||
self.app.file_opened.emit('gerber', filename)
|
||||
# GUI feedback
|
||||
self.app.inform.emit('[success] %s: %s' % (_("Rendered"), outname))
|
||||
|
||||
def periodic_check(self, check_period):
|
||||
"""
|
||||
This function starts an QTimer and it will periodically check if parsing was done
|
||||
|
||||
:param check_period: time at which to check periodically if all plots finished to be plotted
|
||||
:return:
|
||||
"""
|
||||
|
||||
# self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
|
||||
# self.plot_thread.start()
|
||||
log.debug("ToolPDF --> Periodic Check started.")
|
||||
|
||||
try:
|
||||
self.check_thread.stop()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self.check_thread.setInterval(check_period)
|
||||
try:
|
||||
self.check_thread.timeout.disconnect(self.periodic_check_handler)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
self.check_thread.timeout.connect(self.periodic_check_handler)
|
||||
self.check_thread.start(QtCore.QThread.HighPriority)
|
||||
|
||||
def periodic_check_handler(self):
|
||||
"""
|
||||
If the parsing worker finished then start multithreaded rendering
|
||||
:return:
|
||||
"""
|
||||
# log.debug("checking parsing --> %s" % str(self.parsing_promises))
|
||||
|
||||
try:
|
||||
if not self.parsing_promises:
|
||||
self.check_thread.stop()
|
||||
# parsing finished start the layer rendering
|
||||
if self.pdf_parsed:
|
||||
obj_to_delete = []
|
||||
for object_name in self.pdf_parsed:
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise grace
|
||||
|
||||
filename = deepcopy(self.pdf_parsed[object_name]['filename'])
|
||||
pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
|
||||
obj_to_delete.append(object_name)
|
||||
for k in pdf_content:
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise grace
|
||||
|
||||
ap_dict = pdf_content[k]
|
||||
if ap_dict:
|
||||
layer_nr = k
|
||||
if k == 0:
|
||||
self.app.worker_task.emit({'fcn': self.layer_rendering_as_excellon,
|
||||
'params': [filename, ap_dict, layer_nr]})
|
||||
else:
|
||||
self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber,
|
||||
'params': [filename, ap_dict, layer_nr]})
|
||||
# delete the object already processed so it will not be processed again for other objects
|
||||
# that were opened at the same time; like in drag & drop on GUI
|
||||
for obj_name in obj_to_delete:
|
||||
if obj_name in self.pdf_parsed:
|
||||
self.pdf_parsed.pop(obj_name)
|
||||
|
||||
log.debug("ToolPDF --> Periodic check finished.")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
def parse_pdf(self, pdf_content):
|
||||
path = {}
|
||||
path['lines'] = [] # it's a list of lines subpaths
|
||||
path['bezier'] = [] # it's a list of bezier arcs subpaths
|
||||
@@ -503,7 +171,7 @@ class ToolPDF(FlatCAMTool):
|
||||
if match:
|
||||
color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> STROKE Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
|
||||
"parse_pdf() --> STROKE Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
|
||||
(line_nr, color[0], color[1], color[2]))
|
||||
|
||||
if color[0] == old_color[0] and color[1] == old_color[1] and color[2] == old_color[2]:
|
||||
@@ -526,7 +194,7 @@ class ToolPDF(FlatCAMTool):
|
||||
if match:
|
||||
fill_color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> FILL Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
|
||||
"parse_pdf() --> FILL Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
|
||||
(line_nr, fill_color[0], fill_color[1], fill_color[2]))
|
||||
# if the color is white we are seeing 'clear_geometry' that can't be seen. It may be that those
|
||||
# geometries are actually holes from which we can make an Excellon file
|
||||
@@ -545,7 +213,7 @@ class ToolPDF(FlatCAMTool):
|
||||
# sometimes they combine save_to_graphics_stack with the transformation on the same line
|
||||
if match.group(1) == 'q':
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
|
||||
"parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
|
||||
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
|
||||
|
||||
self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
|
||||
@@ -555,7 +223,7 @@ class ToolPDF(FlatCAMTool):
|
||||
if (float(match.group(3)) == 0 and float(match.group(4)) == 0) and \
|
||||
(float(match.group(6)) != 0 or float(match.group(7)) != 0):
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
|
||||
"parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
|
||||
|
||||
offset_geo[0] += float(match.group(6))
|
||||
offset_geo[1] += float(match.group(7))
|
||||
@@ -564,7 +232,7 @@ class ToolPDF(FlatCAMTool):
|
||||
# transformation = SCALING
|
||||
if float(match.group(2)) != 1 and float(match.group(5)) != 1:
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
|
||||
"parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
|
||||
|
||||
scale_geo[0] *= float(match.group(2))
|
||||
scale_geo[1] *= float(match.group(5))
|
||||
@@ -576,7 +244,7 @@ class ToolPDF(FlatCAMTool):
|
||||
match = self.save_gs_re.search(pline)
|
||||
if match:
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
|
||||
"parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
|
||||
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
|
||||
self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
|
||||
self.gs['line_width'].append(deepcopy(size))
|
||||
@@ -590,18 +258,18 @@ class ToolPDF(FlatCAMTool):
|
||||
scale_geo = restored_transform[1]
|
||||
except IndexError:
|
||||
# nothing to remove
|
||||
log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
|
||||
log.debug("parse_pdf() --> Nothing to restore")
|
||||
pass
|
||||
|
||||
try:
|
||||
size = self.gs['line_width'].pop(-1)
|
||||
except IndexError:
|
||||
log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
|
||||
log.debug("parse_pdf() --> Nothing to restore")
|
||||
# nothing to remove
|
||||
pass
|
||||
|
||||
log.debug(
|
||||
"ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> "
|
||||
"parse_pdf() --> Restore from GS found on line: %s --> "
|
||||
"restored_offset=[%f, %f] ||| restored_scale=[%f, %f]" %
|
||||
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
|
||||
# log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
|
||||
@@ -712,9 +380,9 @@ class ToolPDF(FlatCAMTool):
|
||||
width = (float(match.group(3)) + offset_geo[0]) * self.point_to_unit_factor * scale_geo[0]
|
||||
height = (float(match.group(4)) + offset_geo[1]) * self.point_to_unit_factor * scale_geo[1]
|
||||
pt1 = (x, y)
|
||||
pt2 = (x+width, y)
|
||||
pt3 = (x+width, y+height)
|
||||
pt4 = (x, y+height)
|
||||
pt2 = (x + width, y)
|
||||
pt3 = (x + width, y + height)
|
||||
pt4 = (x, y + height)
|
||||
subpath['rectangle'] += [pt1, pt2, pt3, pt4, pt1]
|
||||
current_point = pt1
|
||||
continue
|
||||
@@ -948,7 +616,8 @@ class ToolPDF(FlatCAMTool):
|
||||
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
|
||||
# close the subpath if it was not closed already
|
||||
if close_subpath is False:
|
||||
geo.append(geo[0])
|
||||
new_g = geo[0]
|
||||
geo.append(new_g)
|
||||
try:
|
||||
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
|
||||
path_geo.append(geo_el)
|
||||
@@ -27,7 +27,7 @@ from shapely.geometry import LineString, LinearRing, MultiLineString
|
||||
from shapely.affinity import skew, affine_transform, rotate
|
||||
import numpy as np
|
||||
|
||||
from flatcamParsers.ParseFont import *
|
||||
from AppParsers.ParseFont import *
|
||||
|
||||
log = logging.getLogger('base2')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user