FlatCAMScript.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 2/5/2014 #
  6. # MIT Licence #
  7. # ##########################################################
  8. # ##########################################################
  9. # File modified by: Marius Stanciu #
  10. # ##########################################################
  11. from appEditors.AppTextEditor import AppTextEditor
  12. from appObjects.FlatCAMObj import *
  13. from appGUI.ObjectUI import *
  14. import tkinter as tk
  15. import sys
  16. import gettext
  17. import appTranslation as fcTranslate
  18. import builtins
  19. fcTranslate.apply_language('strings')
  20. if '_' not in builtins.__dict__:
  21. _ = gettext.gettext
  22. class ScriptObject(FlatCAMObj):
  23. """
  24. Represents a TCL script object.
  25. """
  26. optionChanged = QtCore.pyqtSignal(str)
  27. ui_type = ScriptObjectUI
  28. def __init__(self, name):
  29. self.decimals = self.app.decimals
  30. log.debug("Creating a ScriptObject object...")
  31. FlatCAMObj.__init__(self, name)
  32. self.kind = "script"
  33. self.options.update({
  34. "plot": True,
  35. "type": 'Script',
  36. "source_file": '',
  37. })
  38. self.units = ''
  39. self.script_editor_tab = None
  40. self.ser_attrs = ['options', 'kind', 'source_file']
  41. self.source_file = ''
  42. self.script_code = ''
  43. self.script_filename=''
  44. self.units_found = self.app.defaults['units']
  45. def set_ui(self, ui):
  46. """
  47. Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
  48. :param ui:
  49. :return:
  50. """
  51. FlatCAMObj.set_ui(self, ui)
  52. log.debug("ScriptObject.set_ui()")
  53. assert isinstance(self.ui, ScriptObjectUI), \
  54. "Expected a ScriptObjectUI, got %s" % type(self.ui)
  55. self.units = self.app.defaults['units'].upper()
  56. self.units_found = self.app.defaults['units']
  57. # Fill form fields only on object create
  58. self.to_form()
  59. # Show/Hide Advanced Options
  60. if self.app.defaults["global_app_level"] == 'b':
  61. self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _("Basic"))
  62. else:
  63. self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _("Advanced"))
  64. self.script_editor_tab = AppTextEditor(app=self.app, plain_text=True, parent=self.app.ui)
  65. # tab_here = False
  66. # # try to not add too many times a tab that it is already installed
  67. # for idx in range(self.app.ui.plot_tab_area.count()):
  68. # if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
  69. # tab_here = True
  70. # break
  71. #
  72. # # add the tab if it is not already added
  73. # if tab_here is False:
  74. # self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
  75. # self.script_editor_tab.setObjectName(self.options['name'])
  76. # self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
  77. # self.script_editor_tab.setObjectName(self.options['name'])
  78. # first clear previous text in text editor (if any)
  79. # self.script_editor_tab.code_editor.clear()
  80. # self.script_editor_tab.code_editor.setReadOnly(False)
  81. self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter'])
  82. self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter'])
  83. self.script_editor_tab.buttonRun.show()
  84. # Switch plot_area to Script Editor tab
  85. self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
  86. flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
  87. self.script_editor_tab.buttonOpen.clicked.disconnect()
  88. self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt))
  89. self.script_editor_tab.buttonSave.clicked.disconnect()
  90. self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt))
  91. self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code)
  92. self.script_editor_tab.handleTextChanged()
  93. self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed)
  94. self.ser_attrs = ['options', 'kind', 'source_file']
  95. # ---------------------------------------------------- #
  96. # ----------- LOAD THE TEXT SOURCE FILE -------------- #
  97. # ---------------------------------------------------- #
  98. self.app.proc_container.view.set_busy('%s...' % _("Loading"))
  99. self.script_editor_tab.t_frame.hide()
  100. try:
  101. # self.script_editor_tab.code_editor.setPlainText(self.source_file)
  102. self.script_editor_tab.load_text(self.source_file, move_to_end=True)
  103. except Exception as e:
  104. log.debug("ScriptObject.set_ui() --> %s" % str(e))
  105. self.script_editor_tab.t_frame.show()
  106. self.app.proc_container.view.set_idle()
  107. self.build_ui()
  108. def build_ui(self):
  109. FlatCAMObj.build_ui(self)
  110. tab_here = False
  111. # try to not add too many times a tab that it is already installed
  112. for idx in range(self.app.ui.plot_tab_area.count()):
  113. if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
  114. tab_here = True
  115. break
  116. # add the tab if it is not already added
  117. if tab_here is False:
  118. self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
  119. self.script_editor_tab.setObjectName(self.options['name'])
  120. self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
  121. def parse_file(self, filename):
  122. """
  123. Will set an attribute of the object, self.source_file, with the parsed data.
  124. :param filename: Tcl Script file to parse
  125. :return: None
  126. """
  127. with open(filename, "r") as opened_script:
  128. script_content = opened_script.readlines()
  129. script_content = ''.join(script_content)
  130. self.source_file = script_content
  131. self.script_filename = filename
  132. def handle_run_code(self):
  133. # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
  134. # tries to print on a hidden widget, therefore show the dock if hidden
  135. if self.app.ui.shell_dock.isHidden():
  136. self.app.ui.shell_dock.show()
  137. self.app.shell.open_processing() # Disables input box.
  138. # make sure that the pixmaps are not updated when running this as they will crash
  139. # TODO find why the pixmaps load crash when run from this object (perhaps another thread?)
  140. self.app.ui.fcinfo.lock_pmaps = True
  141. self.script_code = self.script_editor_tab.code_editor.toPlainText()
  142. old_line = ''
  143. # set tcl info script to actual scriptfile
  144. set_tcl_script_name='''proc procExists p {{
  145. return uplevel 1 [expr {{[llength [info command $p]] > 0}}]
  146. }}
  147. if {{[procExists "info_original"]==0}} {{
  148. rename info info_original
  149. }}
  150. proc info args {{
  151. switch [lindex $args 0] {{
  152. script {{
  153. return "{0}"
  154. }}
  155. default {{
  156. return [uplevel info_original $args]
  157. }}
  158. }}
  159. }}'''.format(self.script_filename)
  160. for tcl_command_line in set_tcl_script_name.splitlines()+self.script_code.splitlines():
  161. # do not process lines starting with '#' = comment and empty lines
  162. if not tcl_command_line.startswith('#') and tcl_command_line != '':
  163. # id FlatCAM is run in Windows then replace all the slashes with
  164. # the UNIX style slash that TCL understands
  165. if sys.platform == 'win32':
  166. if "open" in tcl_command_line:
  167. tcl_command_line = tcl_command_line.replace('\\', '/')
  168. if old_line != '':
  169. new_command = old_line + tcl_command_line + '\n'
  170. else:
  171. new_command = tcl_command_line
  172. # execute the actual Tcl command
  173. try:
  174. result = self.app.shell.tcl.eval(str(new_command))
  175. if result != 'None':
  176. self.app.shell.append_output(result + '\n')
  177. old_line = ''
  178. except tk.TclError:
  179. old_line = old_line + tcl_command_line + '\n'
  180. except Exception as e:
  181. log.debug("ScriptObject.handleRunCode() --> %s" % str(e))
  182. if old_line != '':
  183. # it means that the script finished with an error
  184. result = self.app.shell.tcl.eval("set errorInfo")
  185. log.error("Exec command Exception: %s\n" % result)
  186. self.app.shell.append_error('ERROR: %s\n' % result)
  187. self.app.ui.fcinfo.lock_pmaps = False
  188. self.app.shell.close_processing()
  189. def on_autocomplete_changed(self, state):
  190. if state:
  191. self.script_editor_tab.code_editor.completer_enable = True
  192. else:
  193. self.script_editor_tab.code_editor.completer_enable = False
  194. def mirror(self, axis, point):
  195. pass
  196. def offset(self, vect):
  197. pass
  198. def rotate(self, angle, point):
  199. pass
  200. def scale(self, xfactor, yfactor=None, point=None):
  201. pass
  202. def skew(self, angle_x, angle_y, point):
  203. pass
  204. def buffer(self, distance, join, factor=None):
  205. pass
  206. def bounds(self, flatten=False):
  207. return None, None, None, None
  208. def to_dict(self):
  209. """
  210. Returns a representation of the object as a dictionary.
  211. Attributes to include are listed in ``self.ser_attrs``.
  212. :return: A dictionary-encoded copy of the object.
  213. :rtype: dict
  214. """
  215. d = {}
  216. for attr in self.ser_attrs:
  217. d[attr] = getattr(self, attr)
  218. return d
  219. def from_dict(self, d):
  220. """
  221. Sets object's attributes from a dictionary.
  222. Attributes to include are listed in ``self.ser_attrs``.
  223. This method will look only for only and all the
  224. attributes in ``self.ser_attrs``. They must all
  225. be present. Use only for deserializing saved
  226. objects.
  227. :param d: Dictionary of attributes to set in the object.
  228. :type d: dict
  229. :return: None
  230. """
  231. for attr in self.ser_attrs:
  232. setattr(self, attr, d[attr])