Explorar o código

- added Repetier postprocessor file
- removed "added ability to regenerate objects (it's actually deletion followed by recreation)" because of the way Python pass parameters to functions by reference instead of copy

Marius Stanciu %!s(int64=7) %!d(string=hai) anos
pai
achega
7272b46dd9
Modificáronse 7 ficheiros con 367 adicións e 175 borrados
  1. 5 6
      FlatCAMApp.py
  2. 6 5
      FlatCAMObj.py
  3. 1 7
      ObjectCollection.py
  4. 3 1
      README.md
  5. 2 2
      camlib.py
  6. 152 154
      flatcamTools/ToolSolderPaste.py
  7. 198 0
      postprocessors/Repetier.py

+ 5 - 6
FlatCAMApp.py

@@ -149,7 +149,7 @@ class App(QtCore.QObject):
     # Emitted by 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 new_object_available.
-    object_created = QtCore.pyqtSignal(object, bool, bool, bool)
+    object_created = QtCore.pyqtSignal(object, bool, bool)
 
     # Emitted when a object has been changed (like scaled, mirrored)
     object_changed = QtCore.pyqtSignal(object)
@@ -2298,7 +2298,7 @@ class App(QtCore.QObject):
         # Re-buid the recent items menu
         self.setup_recent_items()
 
-    def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True, overwrite=False):
+    def new_object(self, kind, name, initialize, active=True, fit=True, plot=True, autoselected=True):
         """
         Creates a new specalized FlatCAMObj and attaches it to the application,
         this is, updates the GUI accordingly, any other records and plots it.
@@ -2325,7 +2325,6 @@ class App(QtCore.QObject):
         App.log.debug("new_object()")
         obj_plot = plot
         obj_autoselected = autoselected
-        obj_overwrite = overwrite
 
         t0 = time.time()  # Debug
 
@@ -2414,7 +2413,7 @@ class App(QtCore.QObject):
 
         # Move the object to the main thread and let the app know that it is available.
         obj.moveToThread(QtWidgets.QApplication.instance().thread())
-        self.object_created.emit(obj, obj_plot, obj_autoselected, obj_overwrite)
+        self.object_created.emit(obj, obj_plot, obj_autoselected)
 
         return obj
 
@@ -2431,7 +2430,7 @@ class App(QtCore.QObject):
 
         self.new_object('geometry', 'new_g', initialize, plot=False)
 
-    def on_object_created(self, obj, plot, autoselect, overwrite):
+    def on_object_created(self, obj, plot, autoselect):
         """
         Event callback for object creation.
 
@@ -2442,7 +2441,7 @@ class App(QtCore.QObject):
         self.log.debug("on_object_created()")
 
         # The Collection might change the name if there is a collision
-        self.collection.append(obj, overwrite=overwrite)
+        self.collection.append(obj)
 
         # after adding the object to the collection always update the list of objects that are in the collection
         self.all_objects_list = self.collection.get_list()

+ 6 - 5
FlatCAMObj.py

@@ -5000,20 +5000,21 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
         try:
             for key in self.cnc_tools:
-                if self.cnc_tools[key]['data']['ppname_g'] == 'marlin':
+                ppg = self.cnc_tools[key]['data']['ppname_g']
+                if ppg == 'marlin' or ppg == 'Repetier':
                     marlin = True
                     break
-                if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
+                if ppg == 'hpgl':
                     hpgl = True
                     break
-                if "toolchange_probe" in self.cnc_tools[key]['data']['ppname_g'].lower():
+                if "toolchange_probe" in ppg.lower():
                     probe_pp = True
                     break
         except Exception as e:
             log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e))
 
         try:
-            if self.options['ppname_e'] == 'marlin':
+            if self.options['ppname_e'] == 'marlin' or self.options['ppname_e'] == 'Repetier':
                 marlin = True
         except Exception as e:
             log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
@@ -5025,7 +5026,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e))
 
         if marlin is True:
-            gcode = ';Marlin G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s\n' % \
+            gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date:    %s\n' % \
                     (str(self.app.version), str(self.app.version_date)) + '\n'
 
             gcode += ';Name: ' + str(self.options['name']) + '\n'

+ 1 - 7
ObjectCollection.py

@@ -642,7 +642,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         # return QtWidgets.QAbstractItemModel.flags(self, index)
 
-    def append(self, obj, active=False, overwrite=False):
+    def append(self, obj, active=False):
         FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
 
         name = obj.options["name"]
@@ -653,12 +653,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             # FlatCAMApp.App.log.debug("Promised object %s became available." % name)
             # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
 
-        # first delete the old object
-        if overwrite:
-            if name in self.get_names():
-                self.set_active(name)
-                self.delete_active(select_project=False)
-
         # Prevent same name
         while name in self.get_names():
             ## Create a new name

+ 3 - 1
README.md

@@ -11,7 +11,9 @@ CAD program, and create G-Code for Isolation routing.
 
 22.02.2019
 
-- 
+- added Repetier postprocessor file
+- removed "added ability to regenerate objects (it's actually deletion followed by recreation)" because of the way Python pass parameters to functions by reference instead of copy
+-
 
 21.02.2019
 

+ 2 - 2
camlib.py

@@ -5723,8 +5723,8 @@ class CNCjob(Geometry):
                     command['Z'] = 1
                 else:
                     command['Z'] = 0
-        elif self.pp_solderpaste is not None:
-            if 'Paste' in self.pp_solderpaste:
+        elif self.pp_solderpaste_name is not None:
+            if 'Paste' in self.pp_solderpaste_name:
                 match_paste = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
                 if match_paste:
                     command['X'] = float(match_paste.group(1).replace(" ", ""))

+ 152 - 154
flatcamTools/ToolSolderPaste.py

@@ -352,7 +352,7 @@ class SolderPaste(FlatCAMTool):
         # self.gcode_frame.setDisabled(True)
         # self.save_gcode_frame.setDisabled(True)
 
-        self.tools = {}
+        self.tooltable_tools = {}
         self.tooluid = 0
 
         self.options = LoudDict()
@@ -363,8 +363,8 @@ class SolderPaste(FlatCAMTool):
         ## Signals
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
-        self.soldergeo_btn.clicked.connect(self.on_create_geo)
-        self.solder_gcode_btn.clicked.connect(self.on_create_gcode)
+        self.soldergeo_btn.clicked.connect(self.on_create_geo_click)
+        self.solder_gcode_btn.clicked.connect(self.on_create_gcode_click)
         self.solder_gcode_view_btn.clicked.connect(self.on_view_gcode)
         self.solder_gcode_save_btn.clicked.connect(self.on_save_gcode)
 
@@ -424,10 +424,10 @@ class SolderPaste(FlatCAMTool):
 
         self.tooluid = 0
 
-        self.tools.clear()
+        self.tooltable_tools.clear()
         for tool_dia in dias:
             self.tooluid += 1
-            self.tools.update({
+            self.tooltable_tools.update({
                 int(self.tooluid): {
                     'tooldia': float('%.4f' % tool_dia),
                     'data': deepcopy(self.options),
@@ -455,7 +455,7 @@ class SolderPaste(FlatCAMTool):
         self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
 
         sorted_tools = []
-        for k, v in self.tools.items():
+        for k, v in self.tooltable_tools.items():
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
         sorted_tools.sort(reverse=True)
 
@@ -464,7 +464,7 @@ class SolderPaste(FlatCAMTool):
         tool_id = 0
 
         for tool_sorted in sorted_tools:
-            for tooluid_key, tooluid_value in self.tools.items():
+            for tooluid_key, tooluid_value in self.tooltable_tools.items():
                 if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
                     tool_id += 1
                     id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
@@ -543,7 +543,7 @@ class SolderPaste(FlatCAMTool):
         # update the form
         try:
             # set the form with data from the newly selected tool
-            for tooluid_key, tooluid_value in self.tools.items():
+            for tooluid_key, tooluid_value in self.tooltable_tools.items():
                 if int(tooluid_key) == tooluid:
                     self.set_form(deepcopy(tooluid_value['data']))
         except Exception as e:
@@ -601,7 +601,7 @@ class SolderPaste(FlatCAMTool):
         current_row = self.tools_table.currentRow()
         uid = tooluid if tooluid else int(self.tools_table.item(current_row, 2).text())
         for key in self.form_fields:
-            self.tools[uid]['data'].update({
+            self.tooltable_tools[uid]['data'].update({
                 key: self.form_fields[key].get_value()
             })
 
@@ -618,7 +618,7 @@ class SolderPaste(FlatCAMTool):
         """
         Will read all the parameters of Solder Paste Tool from the provided val parameter and update the UI
         :param val: dictionary with values to store in the form
-        :param_type: dictionary
+        param_type: dictionary
         :return:
         """
 
@@ -656,9 +656,9 @@ class SolderPaste(FlatCAMTool):
             self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
             return
 
-        # construct a list of all 'tooluid' in the self.tools
+        # construct a list of all 'tooluid' in the self.tooltable_tools
         tool_uid_list = []
-        for tooluid_key in self.tools:
+        for tooluid_key in self.tooltable_tools:
             tool_uid_item = int(tooluid_key)
             tool_uid_list.append(tool_uid_item)
 
@@ -670,7 +670,7 @@ class SolderPaste(FlatCAMTool):
         self.tooluid = int(max_uid + 1)
 
         tool_dias = []
-        for k, v in self.tools.items():
+        for k, v in self.tooltable_tools.items():
             for tool_v in v.keys():
                 if tool_v == 'tooldia':
                     tool_dias.append(float('%.4f' % v[tool_v]))
@@ -683,7 +683,7 @@ class SolderPaste(FlatCAMTool):
         else:
             if muted is None:
                 self.app.inform.emit("[success] New Nozzle tool added to Tool Table.")
-            self.tools.update({
+            self.tooltable_tools.update({
                 int(self.tooluid): {
                     'tooldia': float('%.4f' % tool_dia),
                     'data': deepcopy(self.options),
@@ -697,7 +697,7 @@ class SolderPaste(FlatCAMTool):
         self.ui_disconnect()
 
         tool_dias = []
-        for k, v in self.tools.items():
+        for k, v in self.tooltable_tools.items():
             for tool_v in v.keys():
                 if tool_v == 'tooldia':
                     tool_dias.append(float('%.4f' % v[tool_v]))
@@ -719,13 +719,13 @@ class SolderPaste(FlatCAMTool):
 
             # identify the tool that was edited and get it's tooluid
             if new_tool_dia not in tool_dias:
-                self.tools[tooluid]['tooldia'] = new_tool_dia
+                self.tooltable_tools[tooluid]['tooldia'] = new_tool_dia
                 self.app.inform.emit("[success] Nozzle tool from Tool Table was edited.")
                 self.build_ui()
                 return
             else:
                 # identify the old tool_dia and restore the text in tool table
-                for k, v in self.tools.items():
+                for k, v in self.tooltable_tools.items():
                     if k == tooluid:
                         old_tool_dia = v['tooldia']
                         break
@@ -739,7 +739,7 @@ class SolderPaste(FlatCAMTool):
 
         deleted_tools_list = []
         if all:
-            self.tools.clear()
+            self.tooltable_tools.clear()
             self.build_ui()
             return
 
@@ -752,7 +752,7 @@ class SolderPaste(FlatCAMTool):
                 deleted_tools_list.append(rows_to_delete)
 
             for t in deleted_tools_list:
-                self.tools.pop(t, None)
+                self.tooltable_tools.pop(t, None)
             self.build_ui()
             return
 
@@ -766,7 +766,7 @@ class SolderPaste(FlatCAMTool):
                     deleted_tools_list.append(tooluid_del)
 
                 for t in deleted_tools_list:
-                    self.tools.pop(t, None)
+                    self.tooltable_tools.pop(t, None)
 
         except AttributeError:
             self.app.inform.emit("[WARNING_NOTCL] Delete failed. Select a Nozzle tool to delete.")
@@ -795,31 +795,34 @@ class SolderPaste(FlatCAMTool):
     def distance(pt1, pt2):
         return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
 
-    def on_create_geo(self):
-
-        proc = self.app.proc_container.new("Creating Solder Paste dispensing geometry.")
+    def on_create_geo_click(self, signal):
         name = self.obj_combo.currentText()
-
         if name == '':
             self.app.inform.emit("[WARNING_NOTCL] No SolderPaste mask Gerber object loaded.")
             return
 
+        obj = self.app.collection.get_by_name(name)
         # update the self.options
         self.read_form_to_options()
 
-        obj = self.app.collection.get_by_name(name)
+        self.on_create_geo(name=name, work_object=obj)
 
-        if type(obj.solid_geometry) is not list and type(obj.solid_geometry) is not MultiPolygon:
-            obj.solid_geometry = [obj.solid_geometry]
+    def on_create_geo(self, name, work_object):
+        proc = self.app.proc_container.new("Creating Solder Paste dispensing geometry.")
+        obj = work_object
 
         # Sort tools in descending order
         sorted_tools = []
-        for k, v in self.tools.items():
+        for k, v in self.tooltable_tools.items():
             # make sure that the tools diameter is more than zero and not zero
             if float(v['tooldia']) > 0:
                 sorted_tools.append(float('%.4f' % float(v['tooldia'])))
         sorted_tools.sort(reverse=True)
 
+        if not sorted_tools:
+            self.app.inform.emit("[WARNING_NOTCL] No Nozzle tools in the tool table.")
+            return 'fail'
+
         def geo_init(geo_obj, app_obj):
             geo_obj.options.update(self.options)
             geo_obj.solid_geometry = []
@@ -830,7 +833,6 @@ class SolderPaste(FlatCAMTool):
             geo_obj.special_group = 'solder_paste_tool'
 
             def solder_line(p, offset):
-
                 xmin, ymin, xmax, ymax = p.bounds
 
                 min = [xmin, ymin]
@@ -875,21 +877,16 @@ class SolderPaste(FlatCAMTool):
             rest_geo = []
             tooluid = 1
 
-            if not sorted_tools:
-                self.app.inform.emit("[WARNING_NOTCL] No Nozzle tools in the tool table.")
-                return 'fail'
-
             for tool in sorted_tools:
                 offset = tool / 2
-
-                for uid, v in self.tools.items():
+                for uid, v in self.tooltable_tools.items():
                     if float('%.4f' % float(v['tooldia'])) == tool:
                         tooluid = int(uid)
                         break
 
                 geo_obj.tools[tooluid] = {}
                 geo_obj.tools[tooluid]['tooldia'] = tool
-                geo_obj.tools[tooluid]['data'] = self.tools[tooluid]['data']
+                geo_obj.tools[tooluid]['data'] = deepcopy(self.tooltable_tools[tooluid]['data'])
                 geo_obj.tools[tooluid]['solid_geometry'] = []
                 geo_obj.tools[tooluid]['offset'] = 'Path'
                 geo_obj.tools[tooluid]['offset_value'] = 0.0
@@ -933,7 +930,7 @@ class SolderPaste(FlatCAMTool):
 
         def job_thread(app_obj):
             try:
-                app_obj.new_object("geometry", name + "_solderpaste", geo_init, overwrite=True)
+                app_obj.new_object("geometry", name + "_solderpaste", geo_init)
             except Exception as e:
                 proc.done()
                 traceback.print_stack()
@@ -945,9 +942,126 @@ class SolderPaste(FlatCAMTool):
         self.app.collection.promise(name)
 
         # Background
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app.paste_tool]})
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
 
+    def on_create_gcode_click(self, signal):
+        name = self.geo_obj_combo.currentText()
+        obj = self.app.collection.get_by_name(name)
+
+        if obj.special_group != 'solder_paste_tool':
+            self.app.inform.emit("[WARNING_NOTCL]This Geometry can't be processed. NOT a solder_paste_tool geometry.")
+            return 'fail'
+
+        a = 0
+        for tooluid_key in obj.tools:
+            if obj.tools[tooluid_key]['solid_geometry'] is None:
+                a += 1
+        if a == len(obj.tools):
+            self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
+            return 'fail'
+
+        # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
+        originar_name = obj.options['name'].rpartition('_')[0]
+        outname = "%s_%s" % (originar_name, 'cnc_solderpaste')
+
+        self.on_create_gcode(name=outname, workobject=obj)
+
+    def on_create_gcode(self, name, workobject, use_thread=True):
+        """
+        Creates a multi-tool CNCJob out of this Geometry object.
+        :return: None
+        """
+
+        obj = workobject
+
+        try:
+            xmin = obj.options['xmin']
+            ymin = obj.options['ymin']
+            xmax = obj.options['xmax']
+            ymax = obj.options['ymax']
+        except Exception as e:
+            log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
+            msg = "[ERROR] An internal error has ocurred. See shell.\n"
+            msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e)
+            msg += traceback.format_exc()
+            self.app.inform.emit(msg)
+            return
+
+        # Object initialization function for app.new_object()
+        # RUNNING ON SEPARATE THREAD!
+        def job_init(job_obj, app_obj):
+            assert isinstance(job_obj, FlatCAMCNCjob), \
+                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
+
+            tool_cnc_dict = {}
+
+            # this turn on the FlatCAMCNCJob plot for multiple tools
+            job_obj.multitool = True
+            job_obj.multigeo = True
+            job_obj.cnc_tools.clear()
+            job_obj.special_group = 'solder_paste_tool'
+
+            job_obj.options['xmin'] = xmin
+            job_obj.options['ymin'] = ymin
+            job_obj.options['xmax'] = xmax
+            job_obj.options['ymax'] = ymax
+
+            for tooluid_key, tooluid_value in obj.tools.items():
+                app_obj.progress.emit(20)
+
+                # find the tool_dia associated with the tooluid_key
+                tool_dia = tooluid_value['tooldia']
+                tool_cnc_dict = deepcopy(tooluid_value)
+
+                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
+                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+                job_obj.tool = int(tooluid_key)
+
+                # Propagate options
+                job_obj.options["tooldia"] = tool_dia
+                job_obj.options['tool_dia'] = tool_dia
+
+                ### CREATE GCODE ###
+                res = job_obj.generate_gcode_from_solderpaste_geo(**tooluid_value)
+
+                if res == 'fail':
+                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    return 'fail'
+                else:
+                    tool_cnc_dict['gcode'] = res
+
+                ### PARSE GCODE ###
+                tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
+
+                # TODO this serve for bounding box creation only; should be optimized
+                tool_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
+
+                # tell gcode_parse from which point to start drawing the lines depending on what kind of
+                # object is the source of gcode
+                job_obj.toolchange_xy_type = "geometry"
+                app_obj.progress.emit(80)
+
+                job_obj.cnc_tools.update({
+                    tooluid_key: deepcopy(tool_cnc_dict)
+                })
+                tool_cnc_dict.clear()
+
+        if use_thread:
+            # To be run in separate thread
+            def job_thread(app_obj):
+                with self.app.proc_container.new("Generating CNC Code"):
+                    if app_obj.new_object("cncjob", name, job_init) != 'fail':
+                        app_obj.inform.emit("[success]ToolSolderPaste CNCjob created: %s" % name)
+                        app_obj.progress.emit(100)
+
+            # Create a promise with the name
+            self.app.collection.promise(name)
+            # Send to worker
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+        else:
+            self.app.new_object("cncjob", name, job_init)
+
     def on_view_gcode(self):
         time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
 
@@ -1065,122 +1179,6 @@ class SolderPaste(FlatCAMTool):
         self.app.file_saved.emit("gcode", filename)
         self.app.inform.emit("[success] Solder paste dispenser GCode file saved to: %s" % filename)
 
-    def on_create_gcode(self, signal, use_thread=True):
-        """
-        Creates a multi-tool CNCJob out of this Geometry object.
-        :return: None
-        """
-
-        name = self.geo_obj_combo.currentText()
-        obj = self.app.collection.get_by_name(name)
-
-        if obj.special_group != 'solder_paste_tool':
-            self.app.inform.emit("[WARNING_NOTCL]This Geometry can't be processed. NOT a solder_paste_tool geometry.")
-            return
-
-        offset_str = ''
-        multitool_gcode = ''
-
-        # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
-        originar_name = obj.options['name'].rpartition('_')[0]
-        outname = "%s_%s" % (originar_name, '_cnc_solderpaste')
-
-        try:
-            xmin = obj.options['xmin']
-            ymin = obj.options['ymin']
-            xmax = obj.options['xmax']
-            ymax = obj.options['ymax']
-        except Exception as e:
-            log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
-            msg = "[ERROR] An internal error has ocurred. See shell.\n"
-            msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e)
-            msg += traceback.format_exc()
-            self.app.inform.emit(msg)
-            return
-
-
-        # Object initialization function for app.new_object()
-        # RUNNING ON SEPARATE THREAD!
-        def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, FlatCAMCNCjob), \
-                "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
-
-            tool_cnc_dict = {}
-
-            # this turn on the FlatCAMCNCJob plot for multiple tools
-            job_obj.multitool = True
-            job_obj.multigeo = True
-            job_obj.cnc_tools.clear()
-            job_obj.special_group = 'solder_paste_tool'
-
-            job_obj.options['xmin'] = xmin
-            job_obj.options['ymin'] = ymin
-            job_obj.options['xmax'] = xmax
-            job_obj.options['ymax'] = ymax
-
-            a = 0
-            for tooluid_key in obj.tools:
-                if obj.tools[tooluid_key]['solid_geometry'] is None:
-                    a += 1
-            if a == len(obj.tools):
-                self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
-                return 'fail'
-
-            for tooluid_key, tooluid_value in obj.tools.items():
-                app_obj.progress.emit(20)
-
-                # find the tool_dia associated with the tooluid_key
-                tool_dia = tooluid_value['tooldia']
-                tool_cnc_dict = deepcopy(tooluid_value)
-
-                job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
-                job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
-                job_obj.tool = int(tooluid_key)
-
-                # Propagate options
-                job_obj.options["tooldia"] = tool_dia
-                job_obj.options['tool_dia'] = tool_dia
-
-                ### CREATE GCODE ###
-                res = job_obj.generate_gcode_from_solderpaste_geo(**tooluid_value)
-
-                if res == 'fail':
-                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
-                    return 'fail'
-                else:
-                    tool_cnc_dict['gcode'] = res
-
-                ### PARSE GCODE ###
-                tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
-
-                # TODO this serve for bounding box creation only; should be optimized
-                tool_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
-
-                # tell gcode_parse from which point to start drawing the lines depending on what kind of
-                # object is the source of gcode
-                job_obj.toolchange_xy_type = "geometry"
-                app_obj.progress.emit(80)
-
-                job_obj.cnc_tools.update({
-                    tooluid_key: deepcopy(tool_cnc_dict)
-                })
-                tool_cnc_dict.clear()
-
-        if use_thread:
-            # To be run in separate thread
-            def job_thread(app_obj):
-                with self.app.proc_container.new("Generating CNC Code"):
-                    if app_obj.new_object("cncjob", outname, job_init, overwrite=True) != 'fail':
-                        app_obj.inform.emit("[success]ToolSolderPaste CNCjob created: %s" % outname)
-                        app_obj.progress.emit(100)
-
-            # Create a promise with the name
-            self.app.collection.promise(outname)
-            # Send to worker
-            self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
-        else:
-            self.app.new_object("cncjob", outname, job_init, overwrite=True)
-
     def reset_fields(self):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))

+ 198 - 0
postprocessors/Repetier.py

@@ -0,0 +1,198 @@
+from FlatCAMPostProc import *
+
+
+class Repetier(FlatCAMPostProc):
+
+    coordinate_format = "%.*f"
+    feedrate_format = '%.*f'
+    feedrate_rapid_format = feedrate_format
+
+    def start_code(self, p):
+        units = ' ' + str(p['units']).lower()
+        coords_xy = p['toolchange_xy']
+        gcode = ''
+
+        xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
+        xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
+        ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
+        ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
+
+        if str(p['options']['type']) == 'Geometry':
+            gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n' + '\n'
+
+        gcode += ';Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
+
+        if str(p['options']['type']) == 'Geometry':
+            gcode += ';Feedrate_Z: ' + str(p['feedrate_z']) + units + '/min' + '\n'
+
+        gcode += ';Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + '\n' + '\n'
+        gcode += ';Z_Cut: ' + str(p['z_cut']) + units + '\n'
+
+        if str(p['options']['type']) == 'Geometry':
+            if p['multidepth'] is True:
+                gcode += ';DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + '\n'
+
+        gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
+        gcode += ';Z Toolchange: ' + str(p['toolchangez']) + units + '\n'
+
+        if coords_xy is not None:
+            gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
+        else:
+            gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
+
+        gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
+        gcode += ';Z End: ' + str(p['endz']) + units + '\n'
+        gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+
+        if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            gcode += ';Postprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+        else:
+            gcode += ';Postprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
+
+        gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
+        gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
+
+        gcode += ';Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + '\n' + '\n'
+
+        gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
+        gcode += 'G90\n'
+
+        return gcode
+
+    def startz_code(self, p):
+        if p.startz is not None:
+            return 'G0 Z' + self.coordinate_format % (p.coords_decimals, p.startz)
+        else:
+            return ''
+
+    def lift_code(self, p):
+        return 'G0 Z' + self.coordinate_format%(p.coords_decimals, p.z_move) + " " + self.feedrate_rapid_code(p)
+
+    def down_code(self, p):
+        return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.end_feedrate_code(p)
+
+    def toolchange_code(self, p):
+        toolchangez = p.toolchangez
+        toolchangexy = p.toolchange_xy
+        f_plunge = p.f_plunge
+        gcode = ''
+
+        if toolchangexy is not None:
+            toolchangex = toolchangexy[0]
+            toolchangey = toolchangexy[1]
+
+        no_drills = 1
+
+        if int(p.tool) == 1 and p.startz is not None:
+            toolchangez = p.startz
+
+        if p.units.upper() == 'MM':
+            toolC_formatted = format(p.toolC, '.2f')
+        else:
+            toolC_formatted = format(p.toolC, '.4f')
+
+        if str(p['options']['type']) == 'Excellon':
+            for i in p['options']['Tools_in_use']:
+                if i[0] == p.tool:
+                    no_drills = i[2]
+
+            if toolchangexy is not None:
+                gcode = """
+G0 Z{toolchangez}
+G0 X{toolchangex} Y{toolchangey}                
+M84
+@pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
+""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
+           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+           tool=int(p.tool),
+           t_drills=no_drills,
+           toolC=toolC_formatted)
+            else:
+                gcode = """
+G0 Z{toolchangez}
+M84
+@pause Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
+""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+           tool=int(p.tool),
+           t_drills=no_drills,
+           toolC=toolC_formatted)
+
+            if f_plunge is True:
+                gcode += '\nG0 Z%.*f' % (p.coords_decimals, p.z_move)
+
+            return gcode
+
+        else:
+            if toolchangexy is not None:
+                gcode = """
+G0 Z{toolchangez}
+G0 X{toolchangex} Y{toolchangey}
+M84
+@pause Change to tool T{tool} with Tool Dia = {toolC}
+""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
+           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+           tool=int(p.tool),
+           toolC=toolC_formatted)
+            else:
+                gcode = """
+G0 Z{toolchangez}
+M84
+@pause Change to tool T{tool} with Tool Dia = {toolC}
+""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
+           tool=int(p.tool),
+           toolC=toolC_formatted)
+
+            if f_plunge is True:
+                gcode += '\nG0 Z%.*f' % (p.coords_decimals, p.z_move)
+
+            return gcode
+
+    def up_to_zero_code(self, p):
+        return 'G1 Z0' + " " + self.feedrate_code(p)
+
+    def position_code(self, p):
+        return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
+               (p.coords_decimals, p.x, p.coords_decimals, p.y)
+
+    def rapid_code(self, p):
+        return ('G0 ' + self.position_code(p)).format(**p) + " " + self.feedrate_rapid_code(p)
+
+    def linear_code(self, p):
+        return ('G1 ' + self.position_code(p)).format(**p) + " " + self.end_feedrate_code(p)
+
+    def end_code(self, p):
+        coords_xy = p['toolchange_xy']
+        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
+
+        if coords_xy is not None:
+            gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
+
+        return gcode
+
+    def feedrate_code(self, p):
+        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+
+    def end_feedrate_code(self, p):
+        return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
+
+    def feedrate_z_code(self, p):
+        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+
+    def feedrate_rapid_code(self, p):
+        return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
+
+    def spindle_code(self,p):
+        if p.spindlespeed:
+            return 'M106 S%d' % p.spindlespeed
+        else:
+            return 'M106'
+
+    def dwell_code(self, p):
+        if p.dwelltime:
+            return 'G4 P' + str(p.dwelltime)
+
+    def spindle_stop_code(self,p):
+        return 'M107'