- maintenance_2

This commit is contained in:
Marius Stanciu
2020-06-02 18:29:45 +03:00
committed by Marius
parent 38fa25ed3d
commit f5c84fdc49
255 changed files with 162378 additions and 152106 deletions

394
AppObjects/AppObject.py Normal file
View 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()

1224
AppObjects/FlatCAMCNCJob.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
# ##########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
# ##########################################################
# ##########################################################
# File modified by: Marius Stanciu #
# ##########################################################
from AppEditors.FlatCAMTextEditor import TextEditor
from AppObjects.FlatCAMObj import *
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class DocumentObject(FlatCAMObj):
"""
Represents a Document object.
"""
optionChanged = QtCore.pyqtSignal(str)
ui_type = DocumentObjectUI
def __init__(self, name):
self.decimals = self.app.decimals
log.debug("Creating a Document object...")
FlatCAMObj.__init__(self, name)
self.kind = "document"
self.units = ''
self.ser_attrs = ['options', 'kind', 'source_file']
self.source_file = ''
self.doc_code = ''
self.font_name = None
self.font_italic = None
self.font_bold = None
self.font_underline = None
self.document_editor_tab = None
self._read_only = False
self.units_found = self.app.defaults['units']
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
log.debug("DocumentObject.set_ui()")
assert isinstance(self.ui, DocumentObjectUI), \
"Expected a DocumentObjectUI, got %s" % type(self.ui)
self.units = self.app.defaults['units'].upper()
self.units_found = self.app.defaults['units']
# Fill form fields only on object create
self.to_form()
# Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b':
self.ui.level.setText(_(
'<span style="color:green;"><b>Basic</b></span>'
))
else:
self.ui.level.setText(_(
'<span style="color:red;"><b>Advanced</b></span>'
))
self.document_editor_tab = TextEditor(app=self.app)
stylesheet = """
QTextEdit {selection-background-color:%s;
selection-color:white;
}
""" % self.app.defaults["document_sel_color"]
self.document_editor_tab.code_editor.setStyleSheet(stylesheet)
# first clear previous text in text editor (if any)
self.document_editor_tab.code_editor.clear()
self.document_editor_tab.code_editor.setReadOnly(self._read_only)
self.document_editor_tab.buttonRun.hide()
self.ui.autocomplete_cb.set_value(self.app.defaults['document_autocompleter'])
self.on_autocomplete_changed(state=self.app.defaults['document_autocompleter'])
self.on_tab_size_change(val=self.app.defaults['document_tab_size'])
flt = "FlatCAM Docs (*.FlatDoc);;All Files (*.*)"
# ######################################################################
# ######################## SIGNALS #####################################
# ######################################################################
self.document_editor_tab.buttonOpen.clicked.disconnect()
self.document_editor_tab.buttonOpen.clicked.connect(lambda: self.document_editor_tab.handleOpen(filt=flt))
self.document_editor_tab.buttonSave.clicked.disconnect()
self.document_editor_tab.buttonSave.clicked.connect(lambda: self.document_editor_tab.handleSaveGCode(filt=flt))
self.document_editor_tab.code_editor.textChanged.connect(self.on_text_changed)
self.ui.font_type_cb.currentFontChanged.connect(self.font_family)
self.ui.font_size_cb.activated.connect(self.font_size)
self.ui.font_bold_tb.clicked.connect(self.on_bold_button)
self.ui.font_italic_tb.clicked.connect(self.on_italic_button)
self.ui.font_under_tb.clicked.connect(self.on_underline_button)
self.ui.font_color_entry.editingFinished.connect(self.on_font_color_entry)
self.ui.font_color_button.clicked.connect(self.on_font_color_button)
self.ui.sel_color_entry.editingFinished.connect(self.on_selection_color_entry)
self.ui.sel_color_button.clicked.connect(self.on_selection_color_button)
self.ui.al_left_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignLeft))
self.ui.al_center_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignCenter))
self.ui.al_right_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignRight))
self.ui.al_justify_tb.clicked.connect(
lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignJustify)
)
self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
# #######################################################################
self.ui.font_color_entry.set_value(self.app.defaults['document_font_color'])
self.ui.font_color_button.setStyleSheet(
"background-color:%s" % str(self.app.defaults['document_font_color']))
self.ui.sel_color_entry.set_value(self.app.defaults['document_sel_color'])
self.ui.sel_color_button.setStyleSheet(
"background-color:%s" % self.app.defaults['document_sel_color'])
self.ui.font_size_cb.setCurrentIndex(int(self.app.defaults['document_font_size']))
self.document_editor_tab.handleTextChanged()
self.ser_attrs = ['options', 'kind', 'source_file']
if Qt.mightBeRichText(self.source_file):
self.document_editor_tab.code_editor.setHtml(self.source_file)
else:
for line in self.source_file.splitlines():
self.document_editor_tab.code_editor.append(line)
self.build_ui()
@property
def read_only(self):
return self._read_only
@read_only.setter
def read_only(self, val):
if val:
self._read_only = True
else:
self._read_only = False
def build_ui(self):
FlatCAMObj.build_ui(self)
tab_here = False
# try to not add too many times a tab that it is already installed
for idx in range(self.app.ui.plot_tab_area.count()):
if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
tab_here = True
break
# add the tab if it is not already added
if tab_here is False:
self.app.ui.plot_tab_area.addTab(self.document_editor_tab, '%s' % _("Document Editor"))
self.document_editor_tab.setObjectName(self.options['name'])
# Switch plot_area to CNCJob tab
self.app.ui.plot_tab_area.setCurrentWidget(self.document_editor_tab)
def on_autocomplete_changed(self, state):
if state:
self.document_editor_tab.code_editor.completer_enable = True
else:
self.document_editor_tab.code_editor.completer_enable = False
def on_tab_size_change(self, val=None):
try:
self.ui.tab_size_spinner.returnPressed.disconnect(self.on_tab_size_change)
except TypeError:
pass
if val:
self.ui.tab_size_spinner.set_value(val)
tab_balue = int(self.ui.tab_size_spinner.get_value())
self.document_editor_tab.code_editor.setTabStopWidth(tab_balue)
self.app.defaults['document_tab_size'] = tab_balue
self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change)
def on_text_changed(self):
self.source_file = self.document_editor_tab.code_editor.toHtml()
# print(self.source_file)
def font_family(self, font):
# self.document_editor_tab.code_editor.selectAll()
font.setPointSize(float(self.ui.font_size_cb.get_value()))
self.document_editor_tab.code_editor.setCurrentFont(font)
self.font_name = self.ui.font_type_cb.currentFont().family()
def font_size(self):
# self.document_editor_tab.code_editor.selectAll()
self.document_editor_tab.code_editor.setFontPointSize(float(self.ui.font_size_cb.get_value()))
def on_bold_button(self):
if self.ui.font_bold_tb.isChecked():
self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Bold)
self.font_bold = True
else:
self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Normal)
self.font_bold = False
def on_italic_button(self):
if self.ui.font_italic_tb.isChecked():
self.document_editor_tab.code_editor.setFontItalic(True)
self.font_italic = True
else:
self.document_editor_tab.code_editor.setFontItalic(False)
self.font_italic = False
def on_underline_button(self):
if self.ui.font_under_tb.isChecked():
self.document_editor_tab.code_editor.setFontUnderline(True)
self.font_underline = True
else:
self.document_editor_tab.code_editor.setFontUnderline(False)
self.font_underline = False
# Setting font colors handlers
def on_font_color_entry(self):
self.app.defaults['document_font_color'] = self.ui.font_color_entry.get_value()
self.ui.font_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_font_color']))
def on_font_color_button(self):
current_color = QtGui.QColor(self.app.defaults['document_font_color'])
c_dialog = QtWidgets.QColorDialog()
font_color = c_dialog.getColor(initial=current_color)
if font_color.isValid() is False:
return
self.document_editor_tab.code_editor.setTextColor(font_color)
self.ui.font_color_button.setStyleSheet("background-color:%s" % str(font_color.name()))
new_val = str(font_color.name())
self.ui.font_color_entry.set_value(new_val)
self.app.defaults['document_font_color'] = new_val
# Setting selection colors handlers
def on_selection_color_entry(self):
self.app.defaults['document_sel_color'] = self.ui.sel_color_entry.get_value()
self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_sel_color']))
def on_selection_color_button(self):
current_color = QtGui.QColor(self.app.defaults['document_sel_color'])
c_dialog = QtWidgets.QColorDialog()
sel_color = c_dialog.getColor(initial=current_color)
if sel_color.isValid() is False:
return
p = QtGui.QPalette()
p.setColor(QtGui.QPalette.Highlight, sel_color)
p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('white'))
self.document_editor_tab.code_editor.setPalette(p)
self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(sel_color.name()))
new_val = str(sel_color.name())
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.
Attributes to include are listed in ``self.ser_attrs``.
:return: A dictionary-encoded copy of the object.
:rtype: dict
"""
d = {}
for attr in self.ser_attrs:
d[attr] = getattr(self, attr)
return d
def from_dict(self, d):
"""
Sets object's attributes from a dictionary.
Attributes to include are listed in ``self.ser_attrs``.
This method will look only for only and all the
attributes in ``self.ser_attrs``. They must all
be present. Use only for deserializing saved
objects.
:param d: Dictionary of attributes to set in the object.
:type d: dict
:return: None
"""
for attr in self.ser_attrs:
setattr(self, attr, d[attr])

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1501
AppObjects/FlatCAMGerber.py Normal file

File diff suppressed because it is too large Load Diff

516
AppObjects/FlatCAMObj.py Normal file
View File

@@ -0,0 +1,516 @@
# ##########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
# ##########################################################
# ##########################################################
# File modified by: Marius Stanciu #
# ##########################################################
import inspect # TODO: For debugging only.
from AppGUI.ObjectUI import *
from Common import LoudDict
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
import sys
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
# Interrupts plotting process if FlatCAMObj has been deleted
class ObjectDeleted(Exception):
pass
class ValidationError(Exception):
def __init__(self, message, errors):
super().__init__(message)
self.errors = errors
class FlatCAMObj(QtCore.QObject):
"""
Base type of objects handled in FlatCAM. These become interactive
in the AppGUI, can be plotted, and their options can be modified
by the user in their respective forms.
"""
# Instance of the application to which these are related.
# The app should set this value.
app = None
# signal to plot a single object
plot_single_object = QtCore.pyqtSignal()
def __init__(self, name):
"""
Constructor.
:param name: Name of the object given by the user.
:return: FlatCAMObj
"""
QtCore.QObject.__init__(self)
# View
self.ui = None
self.options = LoudDict(name=name)
self.options.set_change_callback(self.on_options_change)
self.form_fields = {}
# store here the default data for Geometry Data
self.default_data = {}
# 2D mode
# Axes must exist and be attached to canvas.
self.axes = None
self.kind = None # Override with proper name
if self.app.is_legacy is False:
self.shapes = self.app.plotcanvas.new_shape_group()
# self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2)
else:
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name)
self.mark_shapes = {}
self.item = None # Link with project view item
self.muted_ui = False
self.deleted = False
try:
self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \
self.app.defaults["global_tolerance"] else 0.01
except ValueError:
self._drawing_tolerance = 0.01
self.isHovering = False
self.notHovering = True
# Flag to show if a selection shape is drawn
self.selection_shape_drawn = False
# self.units = 'IN'
self.units = self.app.defaults['units']
self.plot_single_object.connect(self.single_object_plot)
def __del__(self):
pass
def __str__(self):
return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
def from_dict(self, d):
"""
This supersedes ``from_dict`` in derived classes. Derived classes
must inherit from FlatCAMObj first, then from derivatives of Geometry.
``self.options`` is only updated, not overwritten. This ensures that
options set by the app do not vanish when reading the objects
from a project file.
:param d: Dictionary with attributes to set.
:return: None
"""
for attr in self.ser_attrs:
if attr == 'options':
self.options.update(d[attr])
else:
try:
setattr(self, attr, d[attr])
except KeyError:
log.debug("FlatCAMObj.from_dict() --> KeyError: %s. "
"Means that we are loading an old project that don't"
"have all attributes in the latest application version." % str(attr))
pass
def on_options_change(self, key):
# Update form on programmatically options change
self.set_form_item(key)
# Set object visibility
if key == 'plot':
self.visible = self.options['plot']
self.optionChanged.emit(key)
def set_ui(self, ui):
self.ui = ui
self.form_fields = {"name": self.ui.name_entry}
assert isinstance(self.ui, ObjectUI)
self.ui.name_entry.returnPressed.connect(self.on_name_activate)
try:
# it will raise an exception for those FlatCAM objects that do not build UI with the common elements
self.ui.offset_button.clicked.connect(self.on_offset_button_click)
except (TypeError, AttributeError):
pass
try:
self.ui.scale_button.clicked.connect(self.on_scale_button_click)
except (TypeError, AttributeError):
pass
try:
self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click)
except (TypeError, AttributeError):
pass
# Creates problems on focusOut
try:
self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
except (TypeError, AttributeError):
pass
try:
self.ui.transformations_button.clicked.connect(self.app.transform_tool.run)
except (TypeError, AttributeError):
pass
# self.ui.skew_button.clicked.connect(self.on_skew_button_click)
def build_ui(self):
"""
Sets up the UI/form for this object. Show the UI in the App.
:return: None
"""
self.muted_ui = True
log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
try:
# HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale()
# it seems that the takewidget() does generate a focus out event for the QDoubleSpinbox ...
# and reconnect after the takeWidget() is done
# self.ui.scale_entry.returnPressed.disconnect(self.on_scale_button_click)
self.app.ui.selected_scroll_area.takeWidget()
# self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click)
except Exception as e:
self.app.log.debug("FlatCAMObj.build_ui() --> Nothing to remove: %s" % str(e))
self.app.ui.selected_scroll_area.setWidget(self.ui)
# self.ui.setMinimumWidth(100)
# self.ui.setMaximumWidth(self.app.ui.selected_tab.sizeHint().width())
self.muted_ui = False
def on_name_activate(self, silent=None):
old_name = copy(self.options["name"])
new_name = self.ui.name_entry.get_value()
if new_name != old_name:
# update the SHELL auto-completer model data
try:
self.app.myKeywords.remove(old_name)
self.app.myKeywords.append(new_name)
self.app.shell._edit.set_model_data(self.app.myKeywords)
self.app.ui.code_editor.set_model_data(self.app.myKeywords)
except Exception:
log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list")
self.options["name"] = self.ui.name_entry.get_value()
self.default_data["name"] = self.ui.name_entry.get_value()
self.app.collection.update_view()
if silent:
self.app.inform.emit('[success] %s: %s %s: %s' % (
_("Name changed from"), str(old_name), _("to"), str(new_name)
)
)
def on_offset_button_click(self):
self.app.defaults.report_usage("obj_on_offset_button")
self.read_form()
vector_val = self.ui.offsetvector_entry.get_value()
def worker_task():
with self.app.proc_container.new(_("Offsetting...")):
self.offset(vector_val)
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
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(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))
return
if type(factor) != float:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
# if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0
if factor == 1.0:
self.app.inform.emit('[success] %s' % _("Scale done."))
return
log.debug("FlatCAMObj.on_scale_button_click()")
def worker_task():
with self.app.proc_container.new(_("Scaling...")):
self.scale(factor)
self.app.inform.emit('[success] %s' % _("Scale done."))
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
def on_skew_button_click(self):
self.app.defaults.report_usage("obj_on_skew_button")
self.read_form()
x_angle = self.ui.xangle_entry.get_value()
y_angle = self.ui.yangle_entry.get_value()
def worker_task():
with self.app.proc_container.new(_("Skewing...")):
self.skew(x_angle, y_angle)
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
def to_form(self):
"""
Copies options to the UI form.
:return: None
"""
log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()")
for option in self.options:
try:
self.set_form_item(option)
except Exception as err:
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()), str(err))
def read_form(self):
"""
Reads form into ``self.options``.
:return: None
:rtype: None
"""
log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
for option in self.options:
try:
self.read_form_item(option)
except Exception:
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()))
def set_form_item(self, option):
"""
Copies the specified option to the UI form.
:param option: Name of the option (Key in ``self.options``).
:type option: str
:return: None
"""
try:
self.form_fields[option].set_value(self.options[option])
except KeyError:
# self.app.log.warn("Tried to set an option or field that does not exist: %s" % option)
pass
def read_form_item(self, option):
"""
Reads the specified option from the UI form into ``self.options``.
:param option: Name of the option.
:type option: str
:return: None
"""
try:
self.options[option] = self.form_fields[option].get_value()
except KeyError:
pass
# self.app.log.warning("Failed to read option from field: %s" % option)
def plot(self, kind=None):
"""
Plot this object (Extend this method to implement the actual plotting).
Call this in descendants before doing the plotting.
:param kind: Used by only some of the FlatCAM objects
:return: Whether to continue plotting or not depending on the "plot" option. Boolean
"""
log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
if self.deleted:
return False
self.clear()
return True
def single_object_plot(self):
def plot_task():
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': plot_task, 'params': []})
def serialize(self):
"""
Returns a representation of the object as a dictionary so
it can be later exported as JSON. Override this method.
:return: Dictionary representing the object
:rtype: dict
"""
return
def deserialize(self, obj_dict):
"""
Re-builds an object from its serialized version.
:param obj_dict: Dictionary representing a FlatCAMObj
:type obj_dict: dict
:return: None
"""
return
def add_shape(self, **kwargs):
if self.deleted:
raise ObjectDeleted()
else:
key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs)
return key
def add_mark_shape(self, apid, **kwargs):
if self.deleted:
raise ObjectDeleted()
else:
key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, layer=0, **kwargs)
return key
def update_filters(self, last_ext, filter_string):
"""
Will modify the filter string that is used when saving a file (a list of file extensions) to have the last
used file extension as the first one in the special string
:param last_ext: The file extension that was last used to save a file
:param filter_string: A key in self.app.defaults that holds a string with the filter from QFileDialog
used when saving a file
:return: None
"""
filters = copy(self.app.defaults[filter_string])
filter_list = filters.split(';;')
filter_list_enum_1 = enumerate(filter_list)
# search for the last element in the filters which should always be "All Files (*.*)"
last_elem = ''
for elem in list(filter_list_enum_1):
if '(*.*)' in elem[1]:
last_elem = filter_list.pop(elem[0])
filter_list_enum = enumerate(filter_list)
for elem in list(filter_list_enum):
if '.' + last_ext in elem[1]:
used_ext = filter_list.pop(elem[0])
# sort the extensions back
filter_list.sort(key=lambda x: x.rpartition('.')[2])
# add as a first element the last used extension
filter_list.insert(0, used_ext)
# add back the element that should always be the last (All Files)
filter_list.append(last_elem)
self.app.defaults[filter_string] = ';;'.join(filter_list)
return
@staticmethod
def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors]
@property
def visible(self):
return self.shapes.visible
@visible.setter
def visible(self, value, threaded=True):
log.debug("FlatCAMObj.visible()")
current_visibility = self.shapes.visible
# self.shapes.visible = value # maybe this is slower in VisPy? use enabled property?
def task(current_visibility):
if current_visibility is True:
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
try:
self.annotation.visible = value
except Exception:
pass
if threaded:
self.app.worker_task.emit({'fcn': task, 'params': [current_visibility]})
else:
task(current_visibility)
@property
def drawing_tolerance(self):
self.units = self.app.defaults['units'].upper()
tol = self._drawing_tolerance if self.units == 'MM' or not self.units else self._drawing_tolerance / 25.4
return tol
@drawing_tolerance.setter
def drawing_tolerance(self, value):
self.units = self.app.defaults['units'].upper()
self._drawing_tolerance = value if self.units == 'MM' or not self.units else value / 25.4
def clear(self, update=False):
self.shapes.clear(update)
# Not all object types has annotations
try:
self.annotation.clear(update)
except AttributeError:
pass
def delete(self):
# Free resources
del self.ui
del self.options
# Set flag
self.deleted = True

284
AppObjects/FlatCAMScript.py Normal file
View File

@@ -0,0 +1,284 @@
# ##########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
# ##########################################################
# ##########################################################
# File modified by: Marius Stanciu #
# ##########################################################
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 AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class ScriptObject(FlatCAMObj):
"""
Represents a TCL script object.
"""
optionChanged = QtCore.pyqtSignal(str)
ui_type = ScriptObjectUI
def __init__(self, name):
self.decimals = self.app.decimals
log.debug("Creating a ScriptObject object...")
FlatCAMObj.__init__(self, name)
self.kind = "script"
self.options.update({
"plot": True,
"type": 'Script',
"source_file": '',
})
self.units = ''
self.script_editor_tab = None
self.ser_attrs = ['options', 'kind', 'source_file']
self.source_file = ''
self.script_code = ''
self.units_found = self.app.defaults['units']
def set_ui(self, ui):
"""
Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
:param ui:
:return:
"""
FlatCAMObj.set_ui(self, ui)
log.debug("ScriptObject.set_ui()")
assert isinstance(self.ui, ScriptObjectUI), \
"Expected a ScriptObjectUI, got %s" % type(self.ui)
self.units = self.app.defaults['units'].upper()
self.units_found = self.app.defaults['units']
# Fill form fields only on object create
self.to_form()
# Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b':
self.ui.level.setText(_(
'<span style="color:green;"><b>Basic</b></span>'
))
else:
self.ui.level.setText(_(
'<span style="color:red;"><b>Advanced</b></span>'
))
self.script_editor_tab = TextEditor(app=self.app, plain_text=True, parent=self.app.ui)
# tab_here = False
# # try to not add too many times a tab that it is already installed
# for idx in range(self.app.ui.plot_tab_area.count()):
# if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
# tab_here = True
# break
#
# # add the tab if it is not already added
# if tab_here is False:
# self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
# self.script_editor_tab.setObjectName(self.options['name'])
# self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
# self.script_editor_tab.setObjectName(self.options['name'])
# first clear previous text in text editor (if any)
# self.script_editor_tab.code_editor.clear()
# self.script_editor_tab.code_editor.setReadOnly(False)
self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter'])
self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter'])
self.script_editor_tab.buttonRun.show()
# Switch plot_area to Script Editor tab
self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
self.script_editor_tab.buttonOpen.clicked.disconnect()
self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
self.script_editor_tab.buttonSave.clicked.disconnect()
self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code)
self.script_editor_tab.handleTextChanged()
self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
self.ser_attrs = ['options', 'kind', 'source_file']
# ---------------------------------------------------- #
# ----------- LOAD THE TEXT SOURCE FILE -------------- #
# ---------------------------------------------------- #
self.app.proc_container.view.set_busy(_("Loading..."))
self.script_editor_tab.t_frame.hide()
try:
self.script_editor_tab.code_editor.setPlainText(self.source_file)
# for line in self.source_file.splitlines():
# QtWidgets.QApplication.processEvents()
# self.script_editor_tab.code_editor.append(line)
except Exception as e:
log.debug("ScriptObject.set_ui() --> %s" % str(e))
self.script_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.End)
self.script_editor_tab.t_frame.show()
self.app.proc_container.view.set_idle()
self.build_ui()
def build_ui(self):
FlatCAMObj.build_ui(self)
tab_here = False
# try to not add too many times a tab that it is already installed
for idx in range(self.app.ui.plot_tab_area.count()):
if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
tab_here = True
break
# add the tab if it is not already added
if tab_here is False:
self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
self.script_editor_tab.setObjectName(self.options['name'])
self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
def parse_file(self, filename):
"""
Will set an attribute of the object, self.source_file, with the parsed data.
:param filename: Tcl Script file to parse
:return: None
"""
with open(filename, "r") as opened_script:
script_content = opened_script.readlines()
script_content = ''.join(script_content)
self.source_file = script_content
def handle_run_code(self):
# trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
# tries to print on a hidden widget, therefore show the dock if hidden
if self.app.ui.shell_dock.isHidden():
self.app.ui.shell_dock.show()
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():
# do not process lines starting with '#' = comment and empty lines
if not tcl_command_line.startswith('#') and tcl_command_line != '':
# id FlatCAM is run in Windows then replace all the slashes with
# the UNIX style slash that TCL understands
if sys.platform == 'win32':
if "open" in tcl_command_line:
tcl_command_line = tcl_command_line.replace('\\', '/')
if old_line != '':
new_command = old_line + tcl_command_line + '\n'
else:
new_command = tcl_command_line
# execute the actual Tcl command
try:
result = self.app.shell.tcl.eval(str(new_command))
if result != 'None':
self.app.shell.append_output(result + '\n')
old_line = ''
except tk.TclError:
old_line = old_line + tcl_command_line + '\n'
except Exception as e:
log.debug("ScriptObject.handleRunCode() --> %s" % str(e))
if old_line != '':
# it means that the script finished with an error
result = self.app.shell.tcl.eval("set errorInfo")
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):
if state:
self.script_editor_tab.code_editor.completer_enable = True
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.
Attributes to include are listed in ``self.ser_attrs``.
:return: A dictionary-encoded copy of the object.
:rtype: dict
"""
d = {}
for attr in self.ser_attrs:
d[attr] = getattr(self, attr)
return d
def from_dict(self, d):
"""
Sets object's attributes from a dictionary.
Attributes to include are listed in ``self.ser_attrs``.
This method will look only for only and all the
attributes in ``self.ser_attrs``. They must all
be present. Use only for deserializing saved
objects.
:param d: Dictionary of attributes to set in the object.
:type d: dict
:return: None
"""
for attr in self.ser_attrs:
setattr(self, attr, d[attr])

File diff suppressed because it is too large Load Diff

0
AppObjects/__init__.py Normal file
View File