Explorar el Código

- finished a very rough and limited HPGL2 file import

Marius Stanciu hace 6 años
padre
commit
02b567971d
Se han modificado 5 ficheros con 428 adiciones y 503 borrados
  1. 105 1
      FlatCAMApp.py
  2. 0 1
      FlatCAMObj.py
  3. 1 0
      README.md
  4. 4 0
      flatcamGUI/FlatCAMGUI.py
  5. 318 501
      flatcamParsers/ParseHPGL2.py

+ 105 - 1
FlatCAMApp.py

@@ -63,6 +63,8 @@ from flatcamEditors.FlatCAMExcEditor import FlatCAMExcEditor
 from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor
 from flatcamEditors.FlatCAMTextEditor import TextEditor
 
+from flatcamParsers.ParseHPGL2 import HPGL2
+
 from FlatCAMProcess import *
 from FlatCAMWorkerStack import WorkerStack
 # from flatcamGUI.VisPyVisuals import Color
@@ -1771,7 +1773,7 @@ class App(QtCore.QObject):
 
         self.ui.menufileimportdxf.triggered.connect(lambda: self.on_file_importdxf("geometry"))
         self.ui.menufileimportdxf_as_gerber.triggered.connect(lambda: self.on_file_importdxf("gerber"))
-
+        self.ui.menufileimport_hpgl2_as_geo.triggered.connect(self.on_fileopenhpgl2)
         self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
         self.ui.menufileexportpng.triggered.connect(self.on_file_exportpng)
         self.ui.menufileexportexcellon.triggered.connect(self.on_file_exportexcellon)
@@ -9295,6 +9297,44 @@ class App(QtCore.QObject):
             # thread safe. The new_project()
             self.open_project(filename)
 
+    def on_fileopenhpgl2(self, signal: bool = None, name=None):
+        """
+        File menu callback for opening a HPGL2.
+
+        :param signal: required because clicking the entry will generate a checked signal which needs a container
+        :return: None
+        """
+
+        self.report_usage("on_fileopenhpgl2")
+        App.log.debug("on_fileopenhpgl2()")
+
+        _filter_ = "HPGL2 Files (*.plt);;" \
+                   "All Files (*.*)"
+
+        if name is None:
+            try:
+                filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"),
+                                                                       directory=self.get_last_folder(),
+                                                                       filter=_filter_)
+            except TypeError:
+                filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), filter=_filter_)
+
+            filenames = [str(filename) for filename in filenames]
+        else:
+            filenames = [name]
+            self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n"
+                                                         "Canvas initialization finished in"), '%.2f' % self.used_time,
+                                                       _("Opening HPGL2 file.")),
+                                    alignment=Qt.AlignBottom | Qt.AlignLeft,
+                                    color=QtGui.QColor("gray"))
+
+        if len(filenames) == 0:
+            self.inform.emit('[WARNING_NOTCL] %s' % _("Open HPGL2 file cancelled."))
+        else:
+            for filename in filenames:
+                if filename != '':
+                    self.worker_task.emit({'fcn': self.open_hpgl2, 'params': [filename]})
+
     def on_file_openconfig(self, signal: bool = None):
         """
         File menu callback for opening a config file.
@@ -10931,6 +10971,70 @@ class App(QtCore.QObject):
             self.inform.emit('[success] %s: %s' %
                              (_("Opened"), filename))
 
+    def open_hpgl2(self, filename, outname=None):
+        """
+        Opens a HPGL2 file, parses it and creates a new object for
+        it in the program. Thread-safe.
+
+        :param outname: Name of the resulting object. None causes the
+            name to be that of the file.
+        :param filename: HPGL2 file filename
+        :type filename: str
+        :return: None
+        """
+        filename = filename
+
+        # How the object should be initialized
+        def obj_init(geo_obj, app_obj):
+
+            # assert isinstance(geo_obj, FlatCAMGeometry), \
+            #     "Expected to initialize a FlatCAMGeometry but got %s" % type(geo_obj)
+
+            # Opening the file happens here
+            obj = HPGL2()
+            try:
+                HPGL2.parse_file(obj, filename)
+            except IOError:
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename))
+                return "fail"
+            except ParseError as err:
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(err)))
+                app_obj.log.error(str(err))
+                return "fail"
+            except Exception as e:
+                log.debug("App.open_hpgl2() --> %s" % str(e))
+                msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
+                msg += traceback.format_exc()
+                app_obj.inform.emit(msg)
+                return "fail"
+
+            geo_obj.multigeo = True
+            geo_obj.solid_geometry = obj.solid_geometry
+            geo_obj.tools = obj.tools
+
+            # if geo_obj.is_empty():
+            #     app_obj.inform.emit('[ERROR_NOTCL] %s' %
+            #                         _("Object is not HPGL2 file or empty. Aborting object creation."))
+            #     return "fail"
+
+        App.log.debug("open_hpgl2()")
+
+        with self.proc_container.new(_("Opening HPGL2")) as proc:
+            # Object name
+            name = outname or filename.split('/')[-1].split('\\')[-1]
+
+            # # ## Object creation # ##
+            ret = self.new_object("geometry", name, obj_init, autoselected=False)
+            if ret == 'fail':
+                self.inform.emit('[ERROR_NOTCL]%s' %  _(' Open HPGL2 failed. Probable not a HPGL2 file.'))
+                return 'fail'
+
+            # Register recent file
+            self.file_opened.emit("geometry", filename)
+
+            # GUI feedback
+            self.inform.emit('[success] %s: %s' % (_("Opened"), filename))
+
     def open_script(self, filename, outname=None, silent=False):
         """
         Opens a Script file, parses it and creates a new object for

+ 0 - 1
FlatCAMObj.py

@@ -5744,7 +5744,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
     def plot_element(self, element, color='#FF0000FF', visible=None):
 
         visible = visible if visible else self.options['plot']
-
         try:
             for sub_el in element:
                 self.plot_element(sub_el)

+ 1 - 0
README.md

@@ -17,6 +17,7 @@ CAD program, and create G-Code for Isolation routing.
 - added option to save objects as PDF files in File -> Save menu
 - optimized the FlatCAMGerber.clear_plot_apertures() method
 - some changes in the ObjectUI and for the Geometry UI
+- finished a very rough and limited HPGL2 file import 
 
 11.12.2019
 

+ 4 - 0
flatcamGUI/FlatCAMGUI.py

@@ -168,6 +168,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                                                              _('&DXF as Gerber Object ...'), self)
         self.menufileimport.addAction(self.menufileimportdxf_as_gerber)
         self.menufileimport.addSeparator()
+        self.menufileimport_hpgl2_as_geo = QtWidgets.QAction(QtGui.QIcon('share/dxf16.png'),
+                                                             _('HPGL2 as Geometry Object ...'), self)
+        self.menufileimport.addAction(self.menufileimport_hpgl2_as_geo)
+        self.menufileimport.addSeparator()
 
         # Export ...
         self.menufileexport = self.menufile.addMenu(QtGui.QIcon('share/export.png'), _('Export'))

+ 318 - 501
flatcamParsers/ParseHPGL2.py

@@ -1,7 +1,7 @@
 # ############################################################
 # FlatCAM: 2D Post-processing for Manufacturing              #
 # http://flatcam.org                                         #
-# File Author: Marius Adrina Stanciu (c)                     #
+# File Author: Marius Adrian Stanciu (c)                     #
 # Date: 12/11/2019                                           #
 # MIT Licence                                                #
 # ############################################################
@@ -17,7 +17,7 @@ from copy import deepcopy
 import sys
 
 from shapely.ops import cascaded_union, unary_union
-from shapely.geometry import Polygon, MultiPolygon, LineString, Point
+from shapely.geometry import Polygon, MultiPolygon, LineString, Point, MultiLineString
 import shapely.affinity as affinity
 from shapely.geometry import box as shply_box
 
@@ -62,7 +62,54 @@ class HPGL2(Geometry):
         self.coord_mm_factor = 0.040
 
         # store the file units here:
-        self.units = self.app.defaults['gerber_def_units']
+        self.units = 'MM'
+
+        # storage for the tools
+        self.tools = dict()
+
+        self.default_data = dict()
+        self.default_data.update({
+            "name": '_ncc',
+            "plot": self.app.defaults["geometry_plot"],
+            "cutz": self.app.defaults["geometry_cutz"],
+            "vtipdia": self.app.defaults["geometry_vtipdia"],
+            "vtipangle": self.app.defaults["geometry_vtipangle"],
+            "travelz": self.app.defaults["geometry_travelz"],
+            "feedrate": self.app.defaults["geometry_feedrate"],
+            "feedrate_z": self.app.defaults["geometry_feedrate_z"],
+            "feedrate_rapid": self.app.defaults["geometry_feedrate_rapid"],
+            "dwell": self.app.defaults["geometry_dwell"],
+            "dwelltime": self.app.defaults["geometry_dwelltime"],
+            "multidepth": self.app.defaults["geometry_multidepth"],
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "depthperpass": self.app.defaults["geometry_depthperpass"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "extracut_length": self.app.defaults["geometry_extracut_length"],
+            "toolchange": self.app.defaults["geometry_toolchange"],
+            "toolchangez": self.app.defaults["geometry_toolchangez"],
+            "endz": self.app.defaults["geometry_endz"],
+            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
+            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
+            "startz": self.app.defaults["geometry_startz"],
+
+            "tooldia": self.app.defaults["tools_painttooldia"],
+            "paintmargin": self.app.defaults["tools_paintmargin"],
+            "paintmethod": self.app.defaults["tools_paintmethod"],
+            "selectmethod": self.app.defaults["tools_selectmethod"],
+            "pathconnect": self.app.defaults["tools_pathconnect"],
+            "paintcontour": self.app.defaults["tools_paintcontour"],
+            "paintoverlap": self.app.defaults["tools_paintoverlap"],
+
+            "nccoverlap": self.app.defaults["tools_nccoverlap"],
+            "nccmargin": self.app.defaults["tools_nccmargin"],
+            "nccmethod": self.app.defaults["tools_nccmethod"],
+            "nccconnect": self.app.defaults["tools_nccconnect"],
+            "ncccontour": self.app.defaults["tools_ncccontour"],
+            "nccrest": self.app.defaults["tools_nccrest"]
+        })
+
+        # flag to be set True when tool is detected
+        self.tool_detected = False
 
         # will store the geometry's as solids
         self.solid_geometry = None
@@ -82,17 +129,17 @@ class HPGL2(Geometry):
         # comment
         self.comment_re = re.compile(r"^CO\s*[\"']([a-zA-Z0-9\s]*)[\"'];?$")
         # absolute move to x, y
-        self.abs_move_re = re.compile(r"^PA\s*(-?\d+\.\d+?),?\s*(-?\d+\.\d+?)*;?$")
+        self.abs_move_re = re.compile(r"^PA\s*(-?\d+\.?\d+?),?\s*(-?\d+\.?\d+?)*;?$")
         # relative move to x, y
         self.rel_move_re = re.compile(r"^PR\s*(-?\d+\.\d+?),?\s*(-?\d+\.\d+?)*;?$")
         # pen position
         self.pen_re = re.compile(r"^(P[U|D]);?$")
         # Initialize
-        self.mode_re = re.compile(r'^(IN);?$')
+        self.initialize_re = re.compile(r'^(IN);?$')
+
         # select pen
         self.sp_re = re.compile(r'SP(\d);?$')
 
-
         self.fmt_re_alt = re.compile(r'%FS([LTD])?([AI])X(\d)(\d)Y\d\d\*MO(IN|MM)\*%$')
         self.fmt_re_orcad = re.compile(r'(G\d+)*\**%FS([LTD])?([AI]).*X(\d)(\d)Y\d\d\*%$')
 
@@ -108,12 +155,6 @@ class HPGL2(Geometry):
         self.circ_re = re.compile(r'^(?:G0?([23]))?(?=.*X([+-]?\d+))?(?=.*Y([+-]?\d+))' +
                                   '?(?=.*I([+-]?\d+))?(?=.*J([+-]?\d+))?[XYIJ][^D]*(?:D0([12]))?\*$')
 
-        # G01/2/3 Occurring without coordinates
-        self.interp_re = re.compile(r'^(?:G0?([123]))\*')
-
-        # Single G74 or multi G75 quadrant for circular interpolation
-        self.quad_re = re.compile(r'^G7([45]).*\*$')
-
         # Absolute/Relative G90/1 (OBSOLETE)
         self.absrel_re = re.compile(r'^G9([01])\*$')
 
@@ -121,25 +162,13 @@ class HPGL2(Geometry):
         # in a Gerber file (normal or obsolete ones)
         self.conversion_done = False
 
-        self.use_buffer_for_union = self.app.defaults["gerber_use_buffer_for_union"]
+        self.in_header = None
 
-    def parse_file(self, filename, follow=False):
+    def parse_file(self, filename):
         """
-        Calls Gerber.parse_lines() with generator of lines
-        read from the given file. Will split the lines if multiple
-        statements are found in a single original line.
-
-        The following line is split into two::
-
-            G54D11*G36*
-
-        First is ``G54D11*`` and seconds is ``G36*``.
 
-        :param filename: Gerber file to parse.
+        :param filename: HPGL2 file to parse.
         :type filename: str
-        :param follow: If true, will not create polygons, just lines
-            following the gerber path.
-        :type follow: bool
         :return: None
         """
 
@@ -148,10 +177,9 @@ class HPGL2(Geometry):
 
     def parse_lines(self, glines):
         """
-        Main Gerber parser. Reads Gerber and populates ``self.paths``, ``self.apertures``,
-        ``self.flashes``, ``self.regions`` and ``self.units``.
+        Main HPGL2 parser.
 
-        :param glines: Gerber code as list of strings, each element being
+        :param glines: HPGL2 code as list of strings, each element being
             one line of the source file.
         :type glines: list
         :return: None
@@ -159,33 +187,9 @@ class HPGL2(Geometry):
         """
 
         # Coordinates of the current path, each is [x, y]
-        path = []
+        path = list()
 
-        # this is for temporary storage of solid geometry until it is added to poly_buffer
-        geo_s = None
-
-        # this is for temporary storage of follow geometry until it is added to follow_buffer
-        geo_f = None
-
-        # Polygons are stored here until there is a change in polarity.
-        # Only then they are combined via cascaded_union and added or
-        # subtracted from solid_geometry. This is ~100 times faster than
-        # applying a union for every new polygon.
-        poly_buffer = []
-
-        # store here the follow geometry
-        follow_buffer = []
-
-        last_path_aperture = None
-        current_aperture = None
-
-        # 1,2 or 3 from "G01", "G02" or "G03"
-        current_interpolation_mode = None
-
-        # 1 or 2 from "D01" or "D02"
-        # Note this is to support deprecated Gerber not putting
-        # an operation code at the end of every coordinate line.
-        current_operation_code = None
+        geo_buffer = []
 
         # Current coordinates
         current_x = None
@@ -193,31 +197,17 @@ class HPGL2(Geometry):
         previous_x = None
         previous_y = None
 
-        current_d = None
-
-        # Absolute or Relative/Incremental coordinates
-        # Not implemented
-        absolute = True
+        # store the pen (tool) status
+        pen_status = 'up'
 
-        # How to interpret circular interpolation: SINGLE or MULTI
-        quadrant_mode = None
-
-        # Indicates we are parsing an aperture macro
-        current_macro = None
-
-        # Indicates the current polarity: D-Dark, C-Clear
-        current_polarity = 'D'
-
-        # If a region is being defined
-        making_region = False
+        # store the current tool here
+        current_tool = None
 
         # ### Parsing starts here ## ##
         line_num = 0
         gline = ""
 
-        s_tol = float(self.app.defaults["gerber_simp_tolerance"])
-
-        self.app.inform.emit('%s %d %s.' % (_("Gerber processing. Parsing"), len(glines), _("lines")))
+        self.app.inform.emit('%s %d %s.' % (_("HPGL2 processing. Parsing"), len(glines), _("lines")))
         try:
             for gline in glines:
                 if self.app.abort_flag:
@@ -235,466 +225,293 @@ class HPGL2(Geometry):
                 # Ignored lines #####
                 # Comments      #####
                 # ###################
-                match = self.comm_re.search(gline)
-                if match:
-                    continue
-
-                # ## Mode (IN/MM)
-                # Example: %MOIN*%
-                match = self.mode_re.search(gline)
+                match = self.comment_re.search(gline)
                 if match:
-                    self.units = match.group(1)
-                    log.debug("Gerber units found = %s" % self.units)
-                    # Changed for issue #80
-                    # self.convert_units(match.group(1))
-                    self.conversion_done = True
+                    log.debug(str(match.group(1)))
                     continue
 
-                # ############################################################# ##
-                # Absolute/relative coordinates G90/1 OBSOLETE ######## ##
-                # ##################################################### ##
+                # #####################################################
+                # Absolute/relative coordinates G90/1 OBSOLETE ########
+                # #####################################################
                 match = self.absrel_re.search(gline)
                 if match:
                     absolute = {'0': "Absolute", '1': "Relative"}[match.group(1)]
                     log.warning("Gerber obsolete coordinates type found = %s (Absolute or Relative) " % absolute)
                     continue
 
-                # ## G01 - Linear interpolation plus flashes
-                # Operation code (D0x) missing is deprecated... oh well I will support it.
-                # REGEX: r'^(?:G0?(1))?(?:X(-?\d+))?(?:Y(-?\d+))?(?:D0([123]))?\*$'
-                match = self.lin_re.search(gline)
+                # search for the initialization
+                match = self.initialize_re.search(gline)
                 if match:
-                    # Parse coordinates
-                    if match.group(2) is not None:
-                        linear_x = parse_number(match.group(2),
-                                                       self.int_digits, self.frac_digits, self.gerber_zeros)
-                        current_x = linear_x
-                    else:
-                        linear_x = current_x
-                    if match.group(3) is not None:
-                        linear_y = parse_number(match.group(3),
-                                                       self.int_digits, self.frac_digits, self.gerber_zeros)
-                        current_y = linear_y
-                    else:
-                        linear_y = current_y
-
-                    # Parse operation code
-                    if match.group(4) is not None:
-                        current_operation_code = int(match.group(4))
-
-                    # Pen down: add segment
-                    if current_operation_code == 1:
-                        # if linear_x or linear_y are None, ignore those
-                        if current_x is not None and current_y is not None:
-                            # only add the point if it's a new one otherwise skip it (harder to process)
-                            if path[-1] != [current_x, current_y]:
-                                path.append([current_x, current_y])
-
-                            if making_region is False:
-                                # if the aperture is rectangle then add a rectangular shape having as parameters the
-                                # coordinates of the start and end point and also the width and height
-                                # of the 'R' aperture
-                                try:
-                                    if self.apertures[current_aperture]["type"] == 'R':
-                                        width = self.apertures[current_aperture]['width']
-                                        height = self.apertures[current_aperture]['height']
-                                        minx = min(path[0][0], path[1][0]) - width / 2
-                                        maxx = max(path[0][0], path[1][0]) + width / 2
-                                        miny = min(path[0][1], path[1][1]) - height / 2
-                                        maxy = max(path[0][1], path[1][1]) + height / 2
-                                        log.debug("Coords: %s - %s - %s - %s" % (minx, miny, maxx, maxy))
-
-                                        geo_dict = dict()
-                                        geo_f = Point([current_x, current_y])
-                                        follow_buffer.append(geo_f)
-                                        geo_dict['follow'] = geo_f
-
-                                        geo_s = shply_box(minx, miny, maxx, maxy)
-                                        if self.app.defaults['gerber_simplification']:
-                                            poly_buffer.append(geo_s.simplify(s_tol))
-                                        else:
-                                            poly_buffer.append(geo_s)
-
-                                        if self.is_lpc is True:
-                                            geo_dict['clear'] = geo_s
-                                        else:
-                                            geo_dict['solid'] = geo_s
-
-                                        if current_aperture not in self.apertures:
-                                            self.apertures[current_aperture] = dict()
-                                        if 'geometry' not in self.apertures[current_aperture]:
-                                            self.apertures[current_aperture]['geometry'] = []
-                                        self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
-                                except Exception as e:
-                                    pass
-                            last_path_aperture = current_aperture
-                            # we do this for the case that a region is done without having defined any aperture
-                            if last_path_aperture is None:
-                                if '0' not in self.apertures:
-                                    self.apertures['0'] = {}
-                                    self.apertures['0']['type'] = 'REG'
-                                    self.apertures['0']['size'] = 0.0
-                                    self.apertures['0']['geometry'] = []
-                                last_path_aperture = '0'
-                        else:
-                            self.app.inform.emit('[WARNING] %s: %s' %
-                                                 (_("Coordinates missing, line ignored"), str(gline)))
-                            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                                 _("GERBER file might be CORRUPT. Check the file !!!"))
-                    elif current_operation_code == 2:
-                        if len(path) > 1:
-                            geo_s = None
-
-                            geo_dict = dict()
-                            # --- BUFFERED ---
-                            # this treats the case when we are storing geometry as paths only
-                            if making_region:
-                                # we do this for the case that a region is done without having defined any aperture
-                                if last_path_aperture is None:
-                                    if '0' not in self.apertures:
-                                        self.apertures['0'] = {}
-                                        self.apertures['0']['type'] = 'REG'
-                                        self.apertures['0']['size'] = 0.0
-                                        self.apertures['0']['geometry'] = []
-                                    last_path_aperture = '0'
-                                geo_f = Polygon()
-                            else:
-                                geo_f = LineString(path)
-
-                            try:
-                                if self.apertures[last_path_aperture]["type"] != 'R':
-                                    if not geo_f.is_empty:
-                                        follow_buffer.append(geo_f)
-                                        geo_dict['follow'] = geo_f
-                            except Exception as e:
-                                log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
-                                if not geo_f.is_empty:
-                                    follow_buffer.append(geo_f)
-                                    geo_dict['follow'] = geo_f
-
-                            # this treats the case when we are storing geometry as solids
-                            if making_region:
-                                # we do this for the case that a region is done without having defined any aperture
-                                if last_path_aperture is None:
-                                    if '0' not in self.apertures:
-                                        self.apertures['0'] = {}
-                                        self.apertures['0']['type'] = 'REG'
-                                        self.apertures['0']['size'] = 0.0
-                                        self.apertures['0']['geometry'] = []
-                                    last_path_aperture = '0'
-
-                                try:
-                                    geo_s = Polygon(path)
-                                except ValueError:
-                                    log.warning("Problem %s %s" % (gline, line_num))
-                                    self.app.inform.emit('[ERROR] %s: %s' %
-                                                         (_("Region does not have enough points. "
-                                                            "File will be processed but there are parser errors. "
-                                                            "Line number"), str(line_num)))
-                            else:
-                                if last_path_aperture is None:
-                                    log.warning("No aperture defined for curent path. (%d)" % line_num)
-                                width = self.apertures[last_path_aperture]["size"]  # TODO: WARNING this should fail!
-                                geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
-
-                            try:
-                                if self.apertures[last_path_aperture]["type"] != 'R':
-                                    if not geo_s.is_empty:
-                                        if self.app.defaults['gerber_simplification']:
-                                            poly_buffer.append(geo_s.simplify(s_tol))
-                                        else:
-                                            poly_buffer.append(geo_s)
-
-                                        if self.is_lpc is True:
-                                            geo_dict['clear'] = geo_s
-                                        else:
-                                            geo_dict['solid'] = geo_s
-                            except Exception as e:
-                                log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
-                                if self.app.defaults['gerber_simplification']:
-                                    poly_buffer.append(geo_s.simplify(s_tol))
-                                else:
-                                    poly_buffer.append(geo_s)
-
-                                if self.is_lpc is True:
-                                    geo_dict['clear'] = geo_s
-                                else:
-                                    geo_dict['solid'] = geo_s
-
-                            if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
-                            if 'geometry' not in self.apertures[last_path_aperture]:
-                                self.apertures[last_path_aperture]['geometry'] = []
-                            self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
-
-                        # if linear_x or linear_y are None, ignore those
-                        if linear_x is not None and linear_y is not None:
-                            path = [[linear_x, linear_y]]  # Start new path
-                        else:
-                            self.app.inform.emit('[WARNING] %s: %s' %
-                                                 (_("Coordinates missing, line ignored"), str(gline)))
-                            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                                 _("GERBER file might be CORRUPT. Check the file !!!"))
-
-                    # maybe those lines are not exactly needed but it is easier to read the program as those coordinates
-                    # are used in case that circular interpolation is encountered within the Gerber file
-                    current_x = linear_x
-                    current_y = linear_y
-
-                    # log.debug("Line_number=%3s X=%s Y=%s (%s)" % (line_num, linear_x, linear_y, gline))
+                    self.in_header = False
                     continue
 
-                # ## G02/3 - Circular interpolation
-                # 2-clockwise, 3-counterclockwise
-                # Ex. format: G03 X0 Y50 I-50 J0 where the X, Y coords are the coords of the End Point
-                match = self.circ_re.search(gline)
-                if match:
-                    arcdir = [None, None, "cw", "ccw"]
-
-                    mode, circular_x, circular_y, i, j, d = match.groups()
-
-                    try:
-                        circular_x = parse_number(circular_x,
-                                                         self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
-                        circular_x = current_x
-
-                    try:
-                        circular_y = parse_number(circular_y,
-                                                         self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
-                        circular_y = current_y
-
-                    # According to Gerber specification i and j are not modal, which means that when i or j are missing,
-                    # they are to be interpreted as being zero
-                    try:
-                        i = parse_number(i, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
-                        i = 0
-
-                    try:
-                        j = parse_number(j, self.int_digits, self.frac_digits, self.gerber_zeros)
-                    except Exception as e:
-                        j = 0
-
-                    if quadrant_mode is None:
-                        log.error("Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num)
-                        log.error(gline)
-                        continue
-
-                    if mode is None and current_interpolation_mode not in [2, 3]:
-                        log.error("Found arc without circular interpolation mode defined. (%d)" % line_num)
-                        log.error(gline)
-                        continue
-                    elif mode is not None:
-                        current_interpolation_mode = int(mode)
-
-                    # Set operation code if provided
-                    if d is not None:
-                        current_operation_code = int(d)
-
-                    # Nothing created! Pen Up.
-                    if current_operation_code == 2:
-                        log.warning("Arc with D2. (%d)" % line_num)
-                        if len(path) > 1:
-                            geo_dict = dict()
-
-                            if last_path_aperture is None:
-                                log.warning("No aperture defined for curent path. (%d)" % line_num)
-
-                            # --- BUFFERED ---
-                            width = self.apertures[last_path_aperture]["size"]
-
-                            # this treats the case when we are storing geometry as paths
-                            geo_f = LineString(path)
-                            if not geo_f.is_empty:
-                                follow_buffer.append(geo_f)
-                                geo_dict['follow'] = geo_f
-
-                            # this treats the case when we are storing geometry as solids
-                            buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
-                            if not buffered.is_empty:
-                                if self.app.defaults['gerber_simplification']:
-                                    poly_buffer.append(buffered.simplify(s_tol))
-                                else:
-                                    poly_buffer.append(buffered)
-
-                                if self.is_lpc is True:
-                                    geo_dict['clear'] = buffered
-                                else:
-                                    geo_dict['solid'] = buffered
-
-                            if last_path_aperture not in self.apertures:
-                                self.apertures[last_path_aperture] = dict()
-                            if 'geometry' not in self.apertures[last_path_aperture]:
-                                self.apertures[last_path_aperture]['geometry'] = []
-                            self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
-
-                        current_x = circular_x
-                        current_y = circular_y
-                        path = [[current_x, current_y]]  # Start new path
+                if self.in_header is False:
+                    # tools detection
+                    match = self.sp_re.search(gline)
+                    if match:
+                        tool = match.group(1)
+                        # self.tools[tool] = dict()
+                        self.tools.update({
+                            tool: {
+                                'tooldia': float('%.*f' %
+                                                 (
+                                                     self.decimals,
+                                                     float(self.app.defaults['geometry_cnctooldia'])
+                                                 )
+                                                 ),
+                                'offset': 'Path',
+                                'offset_value': 0.0,
+                                'type': 'Iso',
+                                'tool_type': 'C1',
+                                'data': deepcopy(self.default_data),
+                                'solid_geometry': list()
+                            }
+                        })
+
+                        if current_tool:
+                            if path:
+                                geo = LineString(path)
+                                self.tools[current_tool]['solid_geometry'].append(geo)
+                                geo_buffer.append(geo)
+                                path[:] = []
+
+                        current_tool = tool
                         continue
 
-                    # Flash should not happen here
-                    if current_operation_code == 3:
-                        log.error("Trying to flash within arc. (%d)" % line_num)
+                    # pen status detection
+                    match = self.pen_re.search(gline)
+                    if match:
+                        pen_status = {'PU': 'up', 'PD': 'down'}[match.group(1)]
                         continue
 
-                    if quadrant_mode == 'MULTI':
-                        center = [i + current_x, j + current_y]
-                        radius = np.sqrt(i ** 2 + j ** 2)
-                        start = np.arctan2(-j, -i)  # Start angle
-                        # Numerical errors might prevent start == stop therefore
-                        # we check ahead of time. This should result in a
-                        # 360 degree arc.
-                        if current_x == circular_x and current_y == circular_y:
-                            stop = start
+                    # linear move
+                    match = self.abs_move_re.search(gline)
+                    if match:
+                        # Parse coordinates
+                        if match.group(1) is not None:
+                            linear_x = parse_number(match.group(1))
+                            current_x = linear_x
                         else:
-                            stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
-
-                        this_arc = arc(center, radius, start, stop,
-                                       arcdir[current_interpolation_mode],
-                                       self.steps_per_circle)
+                            linear_x = current_x
 
-                        # The last point in the computed arc can have
-                        # numerical errors. The exact final point is the
-                        # specified (x, y). Replace.
-                        this_arc[-1] = (circular_x, circular_y)
-
-                        # Last point in path is current point
-                        # current_x = this_arc[-1][0]
-                        # current_y = this_arc[-1][1]
-                        current_x, current_y = circular_x, circular_y
-
-                        # Append
-                        path += this_arc
-                        last_path_aperture = current_aperture
+                        if match.group(2) is not None:
+                            linear_y = parse_number(match.group(2))
+                            current_y = linear_y
+                        else:
+                            linear_y = current_y
+
+                        # Pen down: add segment
+                        if pen_status == 'down':
+                            # if linear_x or linear_y are None, ignore those
+                            if current_x is not None and current_y is not None:
+                                # only add the point if it's a new one otherwise skip it (harder to process)
+                                if path[-1] != [current_x, current_y]:
+                                    path.append([current_x, current_y])
+                            else:
+                                self.app.inform.emit('[WARNING] %s: %s' %
+                                                     (_("Coordinates missing, line ignored"), str(gline)))
+
+                        elif pen_status == 'up':
+                            if len(path) > 1:
+                                geo = LineString(path)
+                                self.tools[current_tool]['solid_geometry'].append(geo)
+                                geo_buffer.append(geo)
+
+                            # if linear_x or linear_y are None, ignore those
+                            if linear_x is not None and linear_y is not None:
+                                path = [[linear_x, linear_y]]  # Start new path
+                            else:
+                                self.app.inform.emit('[WARNING] %s: %s' %
+                                                     (_("Coordinates missing, line ignored"), str(gline)))
 
+                        # log.debug("Line_number=%3s X=%s Y=%s (%s)" % (line_num, linear_x, linear_y, gline))
                         continue
 
-                    if quadrant_mode == 'SINGLE':
-
-                        center_candidates = [
-                            [i + current_x, j + current_y],
-                            [-i + current_x, j + current_y],
-                            [i + current_x, -j + current_y],
-                            [-i + current_x, -j + current_y]
-                        ]
-
-                        valid = False
-                        log.debug("I: %f  J: %f" % (i, j))
-                        for center in center_candidates:
-                            radius = np.sqrt(i ** 2 + j ** 2)
-
-                            # Make sure radius to start is the same as radius to end.
-                            radius2 = np.sqrt((center[0] - circular_x) ** 2 + (center[1] - circular_y) ** 2)
-                            if radius2 < radius * 0.95 or radius2 > radius * 1.05:
-                                continue  # Not a valid center.
-
-                            # Correct i and j and continue as with multi-quadrant.
-                            i = center[0] - current_x
-                            j = center[1] - current_y
-
-                            start = np.arctan2(-j, -i)  # Start angle
-                            stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
-                            angle = abs(arc_angle(start, stop, arcdir[current_interpolation_mode]))
-                            log.debug("ARC START: %f, %f  CENTER: %f, %f  STOP: %f, %f" %
-                                      (current_x, current_y, center[0], center[1], circular_x, circular_y))
-                            log.debug("START Ang: %f, STOP Ang: %f, DIR: %s, ABS: %.12f <= %.12f: %s" %
-                                      (start * 180 / np.pi, stop * 180 / np.pi, arcdir[current_interpolation_mode],
-                                       angle * 180 / np.pi, np.pi / 2 * 180 / np.pi, angle <= (np.pi + 1e-6) / 2))
-
-                            if angle <= (np.pi + 1e-6) / 2:
-                                log.debug("########## ACCEPTING ARC ############")
-                                this_arc = arc(center, radius, start, stop,
-                                               arcdir[current_interpolation_mode],
-                                               self.steps_per_circle)
-
-                                # Replace with exact values
-                                this_arc[-1] = (circular_x, circular_y)
-
-                                # current_x = this_arc[-1][0]
-                                # current_y = this_arc[-1][1]
-                                current_x, current_y = circular_x, circular_y
-
-                                path += this_arc
-                                last_path_aperture = current_aperture
-                                valid = True
-                                break
-
-                        if valid:
-                            continue
-                        else:
-                            log.warning("Invalid arc in line %d." % line_num)
-
+                    # ## Circular interpolation
+                    # -clockwise,
+                    # -counterclockwise
+                    match = self.circ_re.search(gline)
+                    # if match:
+                    #     arcdir = [None, None, "cw", "ccw"]
+                    #
+                    #     mode, circular_x, circular_y, i, j, d = match.groups()
+                    #
+                    #     try:
+                    #         circular_x = parse_number(circular_x)
+                    #     except Exception as e:
+                    #         circular_x = current_x
+                    #
+                    #     try:
+                    #         circular_y = parse_number(circular_y)
+                    #     except Exception as e:
+                    #         circular_y = current_y
+                    #
+                    #     try:
+                    #         i = parse_number(i)
+                    #     except Exception as e:
+                    #         i = 0
+                    #
+                    #     try:
+                    #         j = parse_number(j)
+                    #     except Exception as e:
+                    #         j = 0
+                    #
+                    #     if mode is None and current_interpolation_mode not in [2, 3]:
+                    #         log.error("Found arc without circular interpolation mode defined. (%d)" % line_num)
+                    #         log.error(gline)
+                    #         continue
+                    #     elif mode is not None:
+                    #         current_interpolation_mode = int(mode)
+                    #
+                    #     # Set operation code if provided
+                    #     if d is not None:
+                    #         current_operation_code = int(d)
+                    #
+                    #     # Nothing created! Pen Up.
+                    #     if current_operation_code == 2:
+                    #         log.warning("Arc with D2. (%d)" % line_num)
+                    #         if len(path) > 1:
+                    #             geo_dict = dict()
+                    #
+                    #             if last_path_aperture is None:
+                    #                 log.warning("No aperture defined for curent path. (%d)" % line_num)
+                    #
+                    #             # --- BUFFERED ---
+                    #             width = self.apertures[last_path_aperture]["size"]
+                    #
+                    #             # this treats the case when we are storing geometry as paths
+                    #             geo_f = LineString(path)
+                    #             if not geo_f.is_empty:
+                    #                 geo_dict['follow'] = geo_f
+                    #
+                    #             # this treats the case when we are storing geometry as solids
+                    #             buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
+                    #
+                    #             if last_path_aperture not in self.apertures:
+                    #                 self.apertures[last_path_aperture] = dict()
+                    #             if 'geometry' not in self.apertures[last_path_aperture]:
+                    #                 self.apertures[last_path_aperture]['geometry'] = []
+                    #             self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict))
+                    #
+                    #         current_x = circular_x
+                    #         current_y = circular_y
+                    #         path = [[current_x, current_y]]  # Start new path
+                    #         continue
+                    #
+                    #     # Flash should not happen here
+                    #     if current_operation_code == 3:
+                    #         log.error("Trying to flash within arc. (%d)" % line_num)
+                    #         continue
+                    #
+                    #     if quadrant_mode == 'MULTI':
+                    #         center = [i + current_x, j + current_y]
+                    #         radius = np.sqrt(i ** 2 + j ** 2)
+                    #         start = np.arctan2(-j, -i)  # Start angle
+                    #         # Numerical errors might prevent start == stop therefore
+                    #         # we check ahead of time. This should result in a
+                    #         # 360 degree arc.
+                    #         if current_x == circular_x and current_y == circular_y:
+                    #             stop = start
+                    #         else:
+                    #             stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
+                    #
+                    #         this_arc = arc(center, radius, start, stop,
+                    #                        arcdir[current_interpolation_mode],
+                    #                        self.steps_per_circle)
+                    #
+                    #         # The last point in the computed arc can have
+                    #         # numerical errors. The exact final point is the
+                    #         # specified (x, y). Replace.
+                    #         this_arc[-1] = (circular_x, circular_y)
+                    #
+                    #         # Last point in path is current point
+                    #         # current_x = this_arc[-1][0]
+                    #         # current_y = this_arc[-1][1]
+                    #         current_x, current_y = circular_x, circular_y
+                    #
+                    #         # Append
+                    #         path += this_arc
+                    #         last_path_aperture = current_aperture
+                    #
+                    #         continue
+                    #
+                    #     if quadrant_mode == 'SINGLE':
+                    #
+                    #         center_candidates = [
+                    #             [i + current_x, j + current_y],
+                    #             [-i + current_x, j + current_y],
+                    #             [i + current_x, -j + current_y],
+                    #             [-i + current_x, -j + current_y]
+                    #         ]
+                    #
+                    #         valid = False
+                    #         log.debug("I: %f  J: %f" % (i, j))
+                    #         for center in center_candidates:
+                    #             radius = np.sqrt(i ** 2 + j ** 2)
+                    #
+                    #             # Make sure radius to start is the same as radius to end.
+                    #             radius2 = np.sqrt((center[0] - circular_x) ** 2 + (center[1] - circular_y) ** 2)
+                    #             if radius2 < radius * 0.95 or radius2 > radius * 1.05:
+                    #                 continue  # Not a valid center.
+                    #
+                    #             # Correct i and j and continue as with multi-quadrant.
+                    #             i = center[0] - current_x
+                    #             j = center[1] - current_y
+                    #
+                    #             start = np.arctan2(-j, -i)  # Start angle
+                    #             stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
+                    #             angle = abs(arc_angle(start, stop, arcdir[current_interpolation_mode]))
+                    #             log.debug("ARC START: %f, %f  CENTER: %f, %f  STOP: %f, %f" %
+                    #                       (current_x, current_y, center[0], center[1], circular_x, circular_y))
+                    #             log.debug("START Ang: %f, STOP Ang: %f, DIR: %s, ABS: %.12f <= %.12f: %s" %
+                    #                       (start * 180 / np.pi, stop * 180 / np.pi, arcdir[current_interpolation_mode],
+                    #                        angle * 180 / np.pi, np.pi / 2 * 180 / np.pi, angle <= (np.pi + 1e-6) / 2))
+                    #
+                    #             if angle <= (np.pi + 1e-6) / 2:
+                    #                 log.debug("########## ACCEPTING ARC ############")
+                    #                 this_arc = arc(center, radius, start, stop,
+                    #                                arcdir[current_interpolation_mode],
+                    #                                self.steps_per_circle)
+                    #
+                    #                 # Replace with exact values
+                    #                 this_arc[-1] = (circular_x, circular_y)
+                    #
+                    #                 # current_x = this_arc[-1][0]
+                    #                 # current_y = this_arc[-1][1]
+                    #                 current_x, current_y = circular_x, circular_y
+                    #
+                    #                 path += this_arc
+                    #                 last_path_aperture = current_aperture
+                    #                 valid = True
+                    #                 break
+                    #
+                    #         if valid:
+                    #             continue
+                    #         else:
+                    #             log.warning("Invalid arc in line %d." % line_num)
 
                 # ## Line did not match any pattern. Warn user.
                 log.warning("Line ignored (%d): %s" % (line_num, gline))
 
-            # --- Apply buffer ---
-            # this treats the case when we are storing geometry as paths
-            self.follow_geometry = follow_buffer
-
-            # this treats the case when we are storing geometry as solids
-
-            if len(poly_buffer) == 0 and len(self.solid_geometry) == 0:
-                log.error("Object is not Gerber file or empty. Aborting Object creation.")
+            if len(geo_buffer) == 0 and len(self.solid_geometry) == 0:
+                log.error("Object is not HPGL2 file or empty. Aborting Object creation.")
                 return 'fail'
 
-            log.warning("Joining %d polygons." % len(poly_buffer))
-            self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), len(poly_buffer)))
-
-            if self.use_buffer_for_union:
-                log.debug("Union by buffer...")
+            log.warning("Joining %d polygons." % len(geo_buffer))
+            self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), len(geo_buffer)))
 
-                new_poly = MultiPolygon(poly_buffer)
-                if self.app.defaults["gerber_buffering"] == 'full':
-                    new_poly = new_poly.buffer(0.00000001)
-                    new_poly = new_poly.buffer(-0.00000001)
-                log.warning("Union(buffer) done.")
-            else:
-                log.debug("Union by union()...")
-                new_poly = cascaded_union(poly_buffer)
-                new_poly = new_poly.buffer(0, int(self.steps_per_circle / 4))
-                log.warning("Union done.")
-
-            if current_polarity == 'D':
-                self.app.inform.emit('%s' % _("Gerber processing. Applying Gerber polarity."))
-                if new_poly.is_valid:
-                    self.solid_geometry = self.solid_geometry.union(new_poly)
-                else:
-                    # I do this so whenever the parsed geometry of the file is not valid (intersections) it is still
-                    # loaded. Instead of applying a union I add to a list of polygons.
-                    final_poly = []
-                    try:
-                        for poly in new_poly:
-                            final_poly.append(poly)
-                    except TypeError:
-                        final_poly.append(new_poly)
-
-                    try:
-                        for poly in self.solid_geometry:
-                            final_poly.append(poly)
-                    except TypeError:
-                        final_poly.append(self.solid_geometry)
-
-                    self.solid_geometry = final_poly
-
-            else:
-                self.solid_geometry = self.solid_geometry.difference(new_poly)
+            new_poly = unary_union(geo_buffer)
+            self.solid_geometry = new_poly
 
-            # init this for the following operations
-            self.conversion_done = False
         except Exception as err:
             ex_type, ex, tb = sys.exc_info()
             traceback.print_tb(tb)
             # print traceback.format_exc()
 
-            log.error("Gerber PARSING FAILED. Line %d: %s" % (line_num, gline))
+            log.error("HPGL2 PARSING FAILED. Line %d: %s" % (line_num, gline))
 
-            loc = '%s #%d %s: %s\n' % (_("Gerber Line"), line_num, _("Gerber Line Content"), gline) + repr(err)
-            self.app.inform.emit('[ERROR] %s\n%s:' %
-                                 (_("Gerber Parser ERROR"), loc))
+            loc = '%s #%d %s: %s\n' % (_("HPGL2 Line"), line_num, _("HPGL2 Line Content"), gline) + repr(err)
+            self.app.inform.emit('[ERROR] %s\n%s:' % (_("HPGL2 Parser ERROR"), loc))
 
     def create_geometry(self):
         """
@@ -1245,5 +1062,5 @@ def parse_number(strnumber):
     :rtype: float
     """
 
-    return float(strnumber) * 40.0 # in milimeters
+    return float(strnumber) / 40.0 # in milimeters