From 53338a218601d4ddfd3ff4c53a150171f6fa6b32 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 12 Oct 2019 04:49:50 +0300 Subject: [PATCH] - fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing --- FlatCAMObj.py | 17 +++- README.md | 5 + camlib.py | 52 +++-------- flatcamEditors/FlatCAMGrbEditor.py | 6 +- flatcamParsers/ParseGerber.py | 143 +++++++++++++++++++++++------ 5 files changed, 147 insertions(+), 76 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 27ca4e98..41b45f25 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -784,15 +784,15 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if str(self.apertures[ap_code]['type']) == 'R' or str(self.apertures[ap_code]['type']) == 'O': ap_dim_item = QtWidgets.QTableWidgetItem( - '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'] * self.file_units_factor, - self.decimals, self.apertures[ap_code]['height'] * self.file_units_factor + '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'], + self.decimals, self.apertures[ap_code]['height'] ) ) ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) elif str(self.apertures[ap_code]['type']) == 'P': ap_dim_item = QtWidgets.QTableWidgetItem( - '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'] * self.file_units_factor, - self.decimals, self.apertures[ap_code]['nVertices'] * self.file_units_factor) + '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'], + self.decimals, self.apertures[ap_code]['nVertices']) ) ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) else: @@ -802,7 +802,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): try: if self.apertures[ap_code]['size'] is not None: ap_size_item = QtWidgets.QTableWidgetItem( - '%.*f' % (self.decimals, float(self.apertures[ap_code]['size'] * self.file_units_factor))) + '%.*f' % (self.decimals, float(self.apertures[ap_code]['size']))) else: ap_size_item = QtWidgets.QTableWidgetItem('') except KeyError: @@ -1408,6 +1408,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber): :return: None :rtype: None """ + + # units conversion to get a conversion should be done only once even if we found multiple + # units declaration inside a Gerber file (it can happen to find also the obsolete declaration) + if self.conversion_done is True: + log.debug("Gerber units conversion cancelled. Already done.") + return + log.debug("FlatCAMObj.FlatCAMGerber.convert_units()") factor = Gerber.convert_units(self, units) diff --git a/README.md b/README.md index 0949e39e..0d1e8443 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +12.10.2019 + +- fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing + + 11.10.2019 - added a Bookmark Manager and a Bookmark menu in the Help Menu diff --git a/camlib.py b/camlib.py index 6d49633b..df9ab8e8 100644 --- a/camlib.py +++ b/camlib.py @@ -467,7 +467,7 @@ class Geometry(object): def __init__(self, geo_steps_per_circle=None): # Units (in or mm) - self.units = Geometry.defaults["units"] + self.units = self.app.defaults["units"] # Final geometry: MultiPolygon or list (of geometry constructs) self.solid_geometry = None @@ -1756,7 +1756,7 @@ class Geometry(object): return optimized_geometry - def convert_units(self, units): + def convert_units(self, obj_units): """ Converts the units of the object to ``units`` by scaling all the geometry appropriately. This call ``scale()``. Don't call @@ -1767,20 +1767,23 @@ class Geometry(object): :return: Scaling factor resulting from unit change. :rtype: float """ - log.debug("camlib.Geometry.convert_units()") - if units.upper() == self.units.upper(): + if obj_units.upper() == self.units.upper(): + log.debug("camlib.Geometry.convert_units() --> Factor: 1") return 1.0 - if units.upper() == "MM": + if obj_units.upper() == "MM": factor = 25.4 - elif units.upper() == "IN": + log.debug("camlib.Geometry.convert_units() --> Factor: 25.4") + elif obj_units.upper() == "IN": factor = 1 / 25.4 + log.debug("camlib.Geometry.convert_units() --> Factor: %s" % str(1 / 25.4)) else: - log.error("Unsupported units: %s" % str(units)) + log.error("Unsupported units: %s" % str(obj_units)) + log.debug("camlib.Geometry.convert_units() --> Factor: 1") return 1.0 - self.units = units + self.units = obj_units self.scale(factor, factor) self.file_units_factor = factor return factor @@ -5321,39 +5324,6 @@ def dict2obj(d): # log.error("Cannot plot: " + str(type(g))) # continue - -def parse_gerber_number(strnumber, int_digits, frac_digits, zeros): - """ - Parse a single number of Gerber coordinates. - - :param strnumber: String containing a number in decimal digits - from a coordinate data block, possibly with a leading sign. - :type strnumber: str - :param int_digits: Number of digits used for the integer - part of the number - :type frac_digits: int - :param frac_digits: Number of digits used for the fractional - part of the number - :type frac_digits: int - :param zeros: If 'L', leading zeros are removed and trailing zeros are kept. Same situation for 'D' when - no zero suppression is done. If 'T', is in reverse. - :type zeros: str - :return: The number in floating point. - :rtype: float - """ - - ret_val = None - - if zeros == 'L' or zeros == 'D': - ret_val = int(strnumber) * (10 ** (-frac_digits)) - - if zeros == 'T': - int_val = int(strnumber) - ret_val = (int_val * (10 ** ((int_digits + frac_digits) - len(strnumber)))) * (10 ** (-frac_digits)) - - return ret_val - - # def alpha_shape(points, alpha): # """ # Compute the alpha shape (concave hull) of a set of points. diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 3794a49f..313169a8 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -3739,7 +3739,7 @@ class FlatCAMGrbEditor(QtCore.QObject): self.gerber_obj = orig_grb_obj self.gerber_obj_options = orig_grb_obj.options - file_units = self.gerber_obj.gerber_units if self.gerber_obj.gerber_units else 'IN' + file_units = self.gerber_obj.units if self.gerber_obj.units else 'IN' app_units = self.app.defaults['units'] self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1 @@ -3773,7 +3773,7 @@ class FlatCAMGrbEditor(QtCore.QObject): conv_apertures[apid][key] = self.gerber_obj.apertures[apid][key] self.gerber_obj.apertures = conv_apertures - self.gerber_obj.gerber_units = app_units + self.gerber_obj.units = app_units # ############################################################# ## # APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY @@ -4035,7 +4035,7 @@ class FlatCAMGrbEditor(QtCore.QObject): grb_obj.multigeo = False grb_obj.follow = False - grb_obj.gerber_units = app_obj.defaults['units'] + grb_obj.units = app_obj.defaults['units'] try: grb_obj.create_geometry() diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index bfe0b9f3..07c26728 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -99,7 +99,7 @@ class Gerber(Geometry): ''' # store the file units here: - self.gerber_units = self.app.defaults['gerber_def_units'] + self.units = self.app.defaults['gerber_def_units'] # aperture storage self.apertures = {} @@ -205,6 +205,10 @@ class Gerber(Geometry): self.am1_re = re.compile(r'^%AM([^\*]+)\*([^%]+)?(%)?$') self.am2_re = re.compile(r'(.*)%$') + # flag to store if a conversion was done. It is needed because multiple units declarations can be found + # 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"] def aperture_parse(self, apertureId, apertureType, apParameters): @@ -501,10 +505,11 @@ class Gerber(Geometry): # Example: %MOIN*% match = self.mode_re.search(gline) if match: - self.gerber_units = match.group(1) - log.debug("Gerber units found = %s" % self.gerber_units) + self.units = match.group(1) + log.debug("Gerber units found = %s" % self.units) # Changed for issue #80 - self.convert_units(match.group(1)) + # self.convert_units(match.group(1)) + self.conversion_done = True continue # ############################################################# ## @@ -523,10 +528,11 @@ class Gerber(Geometry): "D-no zero suppression)" % self.gerber_zeros) log.debug("Gerber format found. Coordinates type = %s (Absolute or Relative)" % absolute) - self.gerber_units = match.group(5) - log.debug("Gerber units found = %s" % self.gerber_units) + self.units = match.group(5) + log.debug("Gerber units found = %s" % self.units) # Changed for issue #80 - self.convert_units(match.group(5)) + # self.convert_units(match.group(5)) + self.conversion_done = True continue # ############################################################# ## @@ -551,10 +557,11 @@ class Gerber(Geometry): "D-no zerosuppressionn)" % self.gerber_zeros) log.debug("Gerber format found. Coordinates type = %s (Absolute or Relative)" % absolute) - self.gerber_units = match.group(1) - log.debug("Gerber units found = %s" % self.gerber_units) + self.units = match.group(1) + log.debug("Gerber units found = %s" % self.units) # Changed for issue #80 - self.convert_units(match.group(5)) + # self.convert_units(match.group(5)) + self.conversion_done = True continue # ############################################################# ## @@ -565,7 +572,8 @@ class Gerber(Geometry): obs_gerber_units = {'0': 'IN', '1': 'MM'}[match.group(1)] log.warning("Gerber obsolete units found = %s" % obs_gerber_units) # Changed for issue #80 - self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)]) + # self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)]) + self.conversion_done = True continue # ############################################################# ## @@ -1353,13 +1361,6 @@ class Gerber(Geometry): self.apertures[last_path_aperture]['geometry'] = [] self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) - # TODO: make sure to keep track of units changes because right now it seems to happen in a weird way - # find out the conversion factor used to convert inside the self.apertures keys: size, width, height - file_units = self.gerber_units if self.gerber_units else 'IN' - app_units = self.app.defaults['units'] - - conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1 - # --- Apply buffer --- # this treats the case when we are storing geometry as paths self.follow_geometry = follow_buffer @@ -1420,6 +1421,9 @@ class Gerber(Geometry): # pass else: self.solid_geometry = self.solid_geometry.difference(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) @@ -1541,7 +1545,7 @@ class Gerber(Geometry): # fixed issue of getting bounds only for one level lists of objects # now it can get bounds for nested lists of objects - log.debug("camlib.Gerber.bounds()") + log.debug("parseGerber.Gerber.bounds()") if self.solid_geometry is None: log.debug("solid_geometry is None") @@ -1582,6 +1586,38 @@ class Gerber(Geometry): bounds_coords = bounds_rec(self.solid_geometry) return bounds_coords + def convert_units(self, obj_units): + """ + Converts the units of the object to ``units`` by scaling all + the geometry appropriately. This call ``scale()``. Don't call + it again in descendents. + + :param units: "IN" or "MM" + :type units: str + :return: Scaling factor resulting from unit change. + :rtype: float + """ + + if obj_units.upper() == self.units.upper(): + log.debug("parseGerber.Gerber.convert_units() --> Factor: 1") + return 1.0 + + if obj_units.upper() == "MM": + factor = 25.4 + log.debug("parseGerber.Gerber.convert_units() --> Factor: 25.4") + elif obj_units.upper() == "IN": + factor = 1 / 25.4 + log.debug("parseGerber.Gerber.convert_units() --> Factor: %s" % str(1 / 25.4)) + else: + log.error("Unsupported units: %s" % str(obj_units)) + log.debug("parseGerber.Gerber.convert_units() --> Factor: 1") + return 1.0 + + self.units = obj_units + self.file_units_factor = factor + self.scale(factor, factor) + return factor + def scale(self, xfactor, yfactor=None, point=None): """ Scales the objects' geometry on the XY plane by a given factor. @@ -1604,7 +1640,7 @@ class Gerber(Geometry): :param point: reference point for scaling operation :rtype : None """ - log.debug("camlib.Gerber.scale()") + log.debug("parseGerber.Gerber.scale()") try: xfactor = float(xfactor) @@ -1664,14 +1700,35 @@ class Gerber(Geometry): # we need to scale the geometry stored in the Gerber apertures, too try: for apid in self.apertures: + new_geometry = list() if 'geometry' in self.apertures[apid]: for geo_el in self.apertures[apid]['geometry']: + new_geo_el = dict() if 'solid' in geo_el: - geo_el['solid'] = scale_geom(geo_el['solid']) + new_geo_el['solid'] = scale_geom(geo_el['solid']) if 'follow' in geo_el: - geo_el['follow'] = scale_geom(geo_el['follow']) + new_geo_el['follow'] = scale_geom(geo_el['follow']) if 'clear' in geo_el: - geo_el['clear'] = scale_geom(geo_el['clear']) + new_geo_el['clear'] = scale_geom(geo_el['clear']) + new_geometry.append(new_geo_el) + + self.apertures[apid]['geometry'] = deepcopy(new_geometry) + + try: + if str(self.apertures[apid]['type']) == 'R' or str(self.apertures[apid]['type']) == 'O': + self.apertures[apid]['width'] *= xfactor + self.apertures[apid]['height'] *= xfactor + elif str(self.apertures[apid]['type']) == 'P': + self.apertures[apid]['diam'] *= xfactor + self.apertures[apid]['nVertices'] *= xfactor + except KeyError: + pass + + try: + if self.apertures[apid]['size'] is not None: + self.apertures[apid]['size'] = float(self.apertures[apid]['size'] * xfactor) + except KeyError: + pass except Exception as e: log.debug('camlib.Gerber.scale() Exception --> %s' % str(e)) @@ -1708,7 +1765,7 @@ class Gerber(Geometry): :type vect: tuple :return: None """ - log.debug("camlib.Gerber.offset()") + log.debug("parseGerber.Gerber.offset()") try: dx, dy = vect @@ -1792,7 +1849,7 @@ class Gerber(Geometry): :type point: list :return: None """ - log.debug("camlib.Gerber.mirror()") + log.debug("parseGerber.Gerber.mirror()") px, py = point xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis] @@ -1866,7 +1923,7 @@ class Gerber(Geometry): :param point: reference point for skewing operation :return None """ - log.debug("camlib.Gerber.skew()") + log.debug("parseGerber.Gerber.skew()") px, py = point @@ -1927,7 +1984,7 @@ class Gerber(Geometry): :param point: :return: """ - log.debug("camlib.Gerber.rotate()") + log.debug("parseGerber.Gerber.rotate()") px, py = point @@ -1980,3 +2037,35 @@ class Gerber(Geometry): self.app.inform.emit('[success] %s' % _("Gerber Rotate done.")) self.app.proc_container.new_text = '' + + +def parse_gerber_number(strnumber, int_digits, frac_digits, zeros): + """ + Parse a single number of Gerber coordinates. + + :param strnumber: String containing a number in decimal digits + from a coordinate data block, possibly with a leading sign. + :type strnumber: str + :param int_digits: Number of digits used for the integer + part of the number + :type frac_digits: int + :param frac_digits: Number of digits used for the fractional + part of the number + :type frac_digits: int + :param zeros: If 'L', leading zeros are removed and trailing zeros are kept. Same situation for 'D' when + no zero suppression is done. If 'T', is in reverse. + :type zeros: str + :return: The number in floating point. + :rtype: float + """ + + ret_val = None + + if zeros == 'L' or zeros == 'D': + ret_val = int(strnumber) * (10 ** (-frac_digits)) + + if zeros == 'T': + int_val = int(strnumber) + ret_val = (int_val * (10 ** ((int_digits + frac_digits) - len(strnumber)))) * (10 ** (-frac_digits)) + + return ret_val