diff --git a/README.md b/README.md index 78ffec12..1138ec88 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +15.05.2019 + +- rewrited the Gerber Parser in camlib - success +- moved the self.apertures[aperture]['geometry'] processing for clear_geometry (geometry made with Gerber LPC command) in Gerber Editor + 12.05.2019 - some modifications to ToolCutout diff --git a/camlib.py b/camlib.py index 4f9f36ef..594d2182 100644 --- a/camlib.py +++ b/camlib.py @@ -2175,8 +2175,11 @@ class Gerber (Geometry): # store the file units here: gerber_units = 'IN' - # this is for temporary storage of geometry until it is added to poly_buffer - geo = None + # 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 @@ -2204,6 +2207,8 @@ class Gerber (Geometry): previous_x = None previous_y = None + current_d = None + # Absolute or Relative/Incremental coordinates # Not implemented absolute = True @@ -2226,20 +2231,21 @@ class Gerber (Geometry): try: for gline in glines: line_num += 1 - self.source_file += gline + '\n' - ### Cleanup + # Cleanup # gline = gline.strip(' \r\n') # log.debug("Line=%3s %s" % (line_num, gline)) - #### Ignored lines - ## Comments + # ############################################################### + # Ignored lines # + # Comments ###### + # ############################################################### match = self.comm_re.search(gline) if match: continue - ### Polarity change + # Polarity change ######## # Example: %LPD*% or %LPC*% # If polarity changes, creates geometry from current # buffer, then adds or subtracts accordingly. @@ -2248,30 +2254,31 @@ class Gerber (Geometry): new_polarity = match.group(1) # log.info("Polarity CHANGE, LPC = %s, poly_buff = %s" % (self.is_lpc, poly_buffer)) self.is_lpc = True if new_polarity == 'C' else False - if len(path) > 1 and current_polarity != new_polarity: # finish the current path and add it to the storage # --- Buffered ---- width = self.apertures[last_path_aperture]["size"] - - geo_f = LineString(path) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - follow_buffer.append(geo_f) - poly_buffer.append(geo_s) - geo_dict = dict() - geo_dict['follow'] = geo_f - if self.is_lpc: - geo_dict['clear'] = geo_s - else: - geo_dict['solid'] = geo_s - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: + geo_f = LineString(path) + if not geo_f.is_empty: + follow_buffer.append(geo_f) + geo_dict['follow'] = geo_f + + geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) + if not geo_s.is_empty: + 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(geo_dict) + self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) path = [path[-1]] @@ -2293,8 +2300,10 @@ class Gerber (Geometry): current_polarity = new_polarity continue - ### Number format + # ############################################################### + # Number format ################## # Example: %FSLAX24Y24*% + # ############################################################### # TODO: This is ignoring most of the format. Implement the rest. match = self.fmt_re.search(gline) if match: @@ -2320,7 +2329,9 @@ class Gerber (Geometry): self.convert_units(match.group(1)) continue - ### Combined Number format and Mode --- Allegro does this + # ############################################################### + # Combined Number format and Mode --- Allegro does this ######### + # ############################################################### match = self.fmt_re_alt.search(gline) if match: absolute = {'A': 'Absolute', 'I': 'Relative'}[match.group(2)] @@ -2339,7 +2350,9 @@ class Gerber (Geometry): self.convert_units(match.group(5)) continue - ### Search for OrCAD way for having Number format + # ############################################################### + # Search for OrCAD way for having Number format + # ############################################################### match = self.fmt_re_orcad.search(gline) if match: if match.group(1) is not None: @@ -2363,7 +2376,9 @@ class Gerber (Geometry): self.convert_units(match.group(5)) continue - ### Units (G70/1) OBSOLETE + # ############################################################### + # Units (G70/1) OBSOLETE + # ############################################################### match = self.units_re.search(gline) if match: obs_gerber_units = {'0': 'IN', '1': 'MM'}[match.group(1)] @@ -2372,17 +2387,21 @@ class Gerber (Geometry): self.convert_units({'0': 'IN', '1': 'MM'}[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 - ### Aperture Macros + # ############################################################### + # Aperture Macros ####################################### # Having this at the beginning will slow things down # but macros can have complicated statements than could # be caught by other patterns. + # ############################################################### if current_macro is None: # No macro started yet match = self.am1_re.search(gline) # Start macro if match, else not an AM, carry on. @@ -2416,49 +2435,51 @@ class Gerber (Geometry): self.aperture_parse(match.group(1), match.group(2), match.group(3)) continue - ### Operation code alone + # ############################################################### + # Operation code alone ######################## # Operation code alone, usually just D03 (Flash) # self.opcode_re = re.compile(r'^D0?([123])\*$') + # ############################################################### match = self.opcode_re.search(gline) if match: current_operation_code = int(match.group(1)) + current_d = current_operation_code if current_operation_code == 3: - ## --- Buffered --- + # --- Buffered --- try: log.debug("Bare op-code %d." % current_operation_code) - # flash = Gerber.create_flash_geometry(Point(path[-1]), - # self.apertures[current_aperture]) + + geo_dict = dict() flash = Gerber.create_flash_geometry( Point(current_x, current_y), self.apertures[current_aperture], int(self.steps_per_circle)) - if not flash.is_empty: - geo_f = Point(current_x, current_y) - geo_s = flash - # follow_buffer.append(geo_f) - poly_buffer.append(geo_s) + geo_dict['follow'] = Point([current_x, current_y]) - geo_dict = dict() - geo_dict['follow'] = geo_f - if self.is_lpc: - geo_dict['clear'] = geo_s + if not flash.is_empty: + poly_buffer.append(flash) + if self.is_lpc is True: + geo_dict['clear'] = flash else: - geo_dict['solid'] = geo_s - try: - self.apertures[current_aperture]['geometry'].append(geo_dict) - except KeyError: + geo_dict['solid'] = flash + + if last_path_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(geo_dict) + self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict)) except IndexError: log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline)) continue - ### Tool/aperture change + # ############################################################### + # Tool/aperture change # Example: D12* + # ############################################################### match = self.tool_re.search(gline) if match: current_aperture = match.group(1) @@ -2475,96 +2496,134 @@ class Gerber (Geometry): # Take care of the current path with the previous tool if len(path) > 1: - if self.apertures[last_path_aperture]["type"] != 'R': - width = self.apertures[last_path_aperture]["size"] - - geo_f = LineString(path) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - follow_buffer.append(geo_f) - poly_buffer.append(geo_s) - + if self.apertures[last_path_aperture]["type"] == 'R': + # do nothing because 'R' type moving aperture is none at once + pass + else: geo_dict = dict() - geo_dict['follow'] = geo_f - if self.is_lpc: - geo_dict['clear'] = geo_s - else: - geo_dict['solid'] = geo_s - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: + geo_f = LineString(path) + if not geo_f.is_empty: + follow_buffer.append(geo_f) + geo_dict['follow'] = geo_f + + # --- Buffered ---- + width = self.apertures[last_path_aperture]["size"] + geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) + if not geo_s.is_empty: + 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(geo_dict) + self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) path = [path[-1]] continue - ### G36* - Begin region + # ############################################################### + # G36* - Begin region + # ############################################################### if self.regionon_re.search(gline): - 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' - self.apertures[last_path_aperture]['type'] = 'REG' - if len(path) > 1: # Take care of what is left in the path - width = self.apertures[last_path_aperture]["size"] - - geo_f = LineString(path) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - follow_buffer.append(geo_f) - poly_buffer.append(geo_s) geo_dict = dict() - geo_dict['follow'] = geo_f - if self.is_lpc: - geo_dict['clear'] = geo_s - else: - geo_dict['solid'] = geo_s + geo_f = LineString(path) + if not geo_f.is_empty: + follow_buffer.append(geo_f) + geo_dict['follow'] = geo_f - self.apertures[last_path_aperture]['geometry'].append(geo_dict) + # --- Buffered ---- + width = self.apertures[last_path_aperture]["size"] + geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) + if not geo_s.is_empty: + 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)) path = [path[-1]] making_region = True continue - ### G37* - End region + # ############################################################### + # G37* - End region + # ############################################################### if self.regionoff_re.search(gline): making_region = False + + if '0' not in self.apertures: + self.apertures['0'] = {} + self.apertures['0']['type'] = 'REG' + self.apertures['0']['size'] = 0.0 + self.apertures['0']['geometry'] = [] + # if D02 happened before G37 we now have a path with 1 element only so we have to add the current # geo to the poly_buffer otherwise we loose it # if current_operation_code == 2: + # print(path) + # geo_dict = dict() + # if geo_f: + # if not geo_f.is_empty: + # follow_buffer.append(geo_f) + # geo_dict['follow'] = geo_f + # if geo_s: + # if not geo_s.is_empty: + # poly_buffer.append(geo_s) + # if self.is_lpc is True: + # geo_dict['clear'] = geo_s + # else: + # geo_dict['solid'] = geo_s + # + # if geo_s or geo_f: + # self.apertures['0']['geometry'].append(deepcopy(geo_dict)) + # + # continue # Only one path defines region? # This can happen if D02 happened before G37 and - # is not an error. + # is not and error. if len(path) < 3: + # print "ERROR: Path contains less than 3 points:" + #path = [[current_x, current_y]] continue - # usually the D02 will add the path to the geo but if there is no D02 before G37 here comes the - # rescue - geo_f = LineString(path) - geo_s = Polygon(path) - - if not geo_s.is_valid: - geo_s = geo_s.buffer(0, int(self.steps_per_circle / 4)) - if not geo_s.is_empty: - poly_buffer.append(geo_s) - follow_buffer.append(geo_f) + # For regions we may ignore an aperture that is None + # --- Buffered --- geo_dict = dict() - geo_dict['follow'] = geo_f - if not geo_s.is_empty: - if self.is_lpc: - geo_dict['clear'] = geo_s - else: - geo_dict['solid'] = geo_s + region_f = Polygon(path).exterior + if not region_f.is_empty: + follow_buffer.append(region_f) + geo_dict['follow'] = region_f - self.apertures['0']['geometry'].append(geo_dict) + region_s = Polygon(path) + if not region_s.is_valid: + region_s = region_s.buffer(0, int(self.steps_per_circle / 4)) + + if not region_s.is_empty: + poly_buffer.append(region_s) + if self.is_lpc is True: + geo_dict['clear'] = region_s + else: + geo_dict['solid'] = region_s + + if not region_s.is_empty or not region_f.is_empty: + self.apertures['0']['geometry'].append(deepcopy(geo_dict)) path = [[current_x, current_y]] # Start new path continue @@ -2593,18 +2652,6 @@ class Gerber (Geometry): # NOTE: Letting it continue allows it to react to the # operation code. - if current_aperture is None or 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'] = [] - if current_aperture is None: - current_aperture = '0' - else: - last_path_aperture = '0' - width = 0 - # Parse coordinates if match.group(2) is not None: linear_x = parse_gerber_number(match.group(2), @@ -2628,11 +2675,7 @@ class Gerber (Geometry): # 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) - # but if it's the first point in the path in may create an exception - try: - if path[-1] != [current_x, current_y]: - path.append([current_x, current_y]) - except IndexError: + if path[-1] != [current_x, current_y]: path.append([current_x, current_y]) if making_region is False: @@ -2647,42 +2690,29 @@ class Gerber (Geometry): 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)) - - r_x = maxx - minx - r_y = maxy - miny - geo_f = Point(r_x, r_y) - geo_s = shply_box(minx, miny, maxx, maxy) - follow_buffer.append(geo_f) - poly_buffer.append(geo_s) + 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 - if self.is_lpc: + + geo_s = shply_box(minx, miny, maxx, maxy) + poly_buffer.append(geo_s) + if self.is_lpc is True: geo_dict['clear'] = geo_s else: geo_dict['solid'] = geo_s - try: - self.apertures[current_aperture]['geometry'].append(geo_dict) - except KeyError: + + 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(geo_dict) + self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict)) except: pass - last_path_aperture = current_aperture - else: - last_path_aperture = '0' - - else: - self.app.inform.emit(_("[WARNING] Coordinates missing, line ignored: %s") % str(gline)) - self.app.inform.emit(_("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!")) - - elif current_operation_code == 2: - # finish current path - if len(path) > 1: - geo_s = None - geo_f = None - + 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'] = {} @@ -2690,41 +2720,87 @@ class Gerber (Geometry): self.apertures['0']['size'] = 0.0 self.apertures['0']['geometry'] = [] last_path_aperture = '0' - width = 0 - else: - width = self.apertures[last_path_aperture]["size"] + else: + self.app.inform.emit(_("[WARNING] Coordinates missing, line ignored: %s") % str(gline)) + self.app.inform.emit(_("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!")) + elif current_operation_code == 2: + if len(path) > 1: + geo_s = None + geo_f = 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( - "Not enough points in path to create a Polygon %s %s" % (gline, line_num)) - try: - geo_f = LineString(path) - except ValueError: - log.warning( - "Not enough points in path to create a LineString %s %s" % (gline, line_num)) + log.warning("Problem %s %s" % (gline, line_num)) + self.app.inform.emit(_("[ERROR] Region does not have enough points. " + "File will be processed but there are parser errors. " + "Line number: %s") % 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)) - geo_f = LineString(path) - if self.apertures[last_path_aperture]["type"] != 'R': - poly_buffer.append(deepcopy(geo_s)) - follow_buffer.append(deepcopy(geo_f)) + try: + if self.apertures[last_path_aperture]["type"] != 'R': + if not geo_s.is_empty: + 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)) + poly_buffer.append(geo_s) + if self.is_lpc is True: + geo_dict['clear'] = geo_s + else: + geo_dict['solid'] = geo_s - geo_dict = dict() - geo_dict['follow'] = geo_f - if geo_s: - if self.is_lpc: - geo_dict['clear'] = deepcopy(geo_s) - else: - geo_dict['solid'] = deepcopy(geo_s) - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: - self.apertures[last_path_aperture]['geometry'] = [] - self.apertures[last_path_aperture]['geometry'].append(geo_dict) + 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: @@ -2737,52 +2813,76 @@ class Gerber (Geometry): # Not allowed in region mode. elif current_operation_code == 3: - width = self.apertures[last_path_aperture]["size"] - # finish the path draw until now - if len(path) > 1 and self.apertures[last_path_aperture]["type"] != 'R': - - geo_f = LineString(path) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - follow_buffer.append(geo_f) - poly_buffer.append(geo_s) - + # Create path draw so far. + if len(path) > 1: + # --- Buffered ---- geo_dict = dict() - geo_dict['follow'] = geo_f - if self.is_lpc: - geo_dict['clear'] = geo_s - else: - geo_dict['solid'] = geo_s - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: + + # this treats the case when we are storing geometry as paths + geo_f = LineString(path) + if not geo_f.is_empty: + try: + if self.apertures[last_path_aperture]["type"] != 'R': + follow_buffer.append(geo_f) + geo_dict['follow'] = geo_f + except Exception as e: + log.debug("camlib.Gerber.parse_lines() --> G01 match D03 --> %s" % str(e)) + follow_buffer.append(geo_f) + geo_dict['follow'] = geo_f + + # this treats the case when we are storing geometry as solids + width = self.apertures[last_path_aperture]["size"] + geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) + if not geo_s.is_empty: + try: + if self.apertures[last_path_aperture]["type"] != 'R': + poly_buffer.append(geo_s) + if self.is_lpc is True: + geo_dict['clear'] = geo_s + else: + geo_dict['solid'] = geo_s + except: + 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(geo_dict) + self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + # Reset path starting point path = [[linear_x, linear_y]] + # --- BUFFERED --- # Draw the flash - geo_f = Point(linear_x, linear_y) - geo_s = Gerber.create_flash_geometry( - Point([linear_x, linear_y]), + # this treats the case when we are storing geometry as paths + geo_dict = dict() + geo_flash = Point([linear_x, linear_y]) + follow_buffer.append(geo_flash) + geo_dict['follow'] = geo_flash + + # this treats the case when we are storing geometry as solids + flash = Gerber.create_flash_geometry( + Point( [linear_x, linear_y]), self.apertures[current_aperture], int(self.steps_per_circle) ) - follow_buffer.append(geo_f) - if not geo_s.is_empty: - poly_buffer.append(geo_s) - - geo_dict = dict() - geo_dict['follow'] = geo_f - if not geo_s.is_empty: - if self.is_lpc: - geo_dict['clear'] = geo_s + if not flash.is_empty: + poly_buffer.append(flash) + if self.is_lpc is True: + geo_dict['clear'] = flash else: - geo_dict['solid'] = geo_s - try: - self.apertures[current_aperture]['geometry'].append(geo_dict) - except KeyError: + geo_dict['solid'] = flash + + 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(geo_dict) + self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict)) # 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 @@ -2853,41 +2953,35 @@ class Gerber (Geometry): # Nothing created! Pen Up. if current_operation_code == 2: log.warning("Arc with D2. (%d)" % line_num) - - # if we have something drawn until this moment, add it if len(path) > 1: - # if last_path_aperture is None: - # log.warning("No aperture defined for curent path. (%d)" % line_num) + geo_dict = dict() + 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' - width = 0 + 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) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - if not geo_s.is_empty: + if not geo_f.is_empty: follow_buffer.append(geo_f) - poly_buffer.append(geo_s) + geo_dict['follow'] = geo_f - geo_dict = dict() - geo_dict['follow'] = geo_f - if not geo_s.is_empty: - if self.is_lpc: - geo_dict['clear'] = geo_s + # 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: + poly_buffer.append(buffered) + if self.is_lpc is True: + geo_dict['clear'] = buffered else: - geo_dict['solid'] = geo_s - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: + 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(geo_dict) + self.apertures[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) current_x = circular_x current_y = circular_y @@ -2998,28 +3092,42 @@ class Gerber (Geometry): # In case that G01 (moving) aperture is rectangular, there is no need to still create # another geo since we already created a shapely box using the start and end coordinates found in # path variable. We do it only for other apertures than 'R' type - if self.apertures[last_path_aperture]["type"] != 'R': + if self.apertures[last_path_aperture]["type"] == 'R': + pass + else: # EOF, create shapely LineString if something still in path - width = self.apertures[last_path_aperture]["size"] - - geo_f = LineString(path) - geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) - follow_buffer.append(geo_f) - if not geo_s.is_empty: - poly_buffer.append(geo_s) + ## --- Buffered --- geo_dict = dict() - geo_dict['follow'] = geo_f + # 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 + width = self.apertures[last_path_aperture]["size"] + geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4)) if not geo_s.is_empty: - if self.is_lpc: + poly_buffer.append(geo_s) + if self.is_lpc is True: geo_dict['clear'] = geo_s else: geo_dict['solid'] = geo_s - try: - self.apertures[last_path_aperture]['geometry'].append(geo_dict) - except KeyError: + + 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(geo_dict) + 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 = gerber_units if 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 @@ -3028,6 +3136,9 @@ class Gerber (Geometry): # this treats the case when we are storing geometry as solids log.warning("Joining %d polygons." % len(poly_buffer)) + for td in self.apertures: + print(td, self.apertures[td]) + if len(poly_buffer) == 0: log.error("Object is not Gerber file or empty. Aborting Object creation.") return 'fail' @@ -3283,6 +3394,15 @@ class Gerber (Geometry): self.app.inform.emit(_("[success] Gerber Scale done.")) + + ## solid_geometry ??? + # It's a cascaded union of objects. + # self.solid_geometry = affinity.scale(self.solid_geometry, factor, + # factor, origin=(0, 0)) + + # # Now buffered_paths, flash_geometry and solid_geometry + # self.create_geometry() + def offset(self, vect): """ Offsets the objects' geometry on the XY plane by a given vector. diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 8435a32e..912f8964 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -3245,46 +3245,56 @@ class FlatCAMGrbEditor(QtCore.QObject): except Exception as e: log.debug("FlatCAMGrbEditor.edit_fcgerber() --> %s" % str(e)) + # ############################################################### + # APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY + # ############################################################### - # # --- the following section is useful for Gerber editor only --- # # log.warning("Applying clear geometry in the apertures dict.") - # # list of clear geos that are to be applied to the entire file - # global_clear_geo = [] - # - # for apid in self.apertures: - # if 'geometry' in self.apertures[apid]: - # for elem in self.apertures[apid]['geometry']: - # if 'clear' in elem: - # global_clear_geo.append(elem['clear']) - # log.warning("Found %d clear polygons." % len(global_clear_geo)) - # - # for apid in self.apertures: - # geo_list = [] - # if 'geometry' in self.apertures[apid]: - # for elem in self.apertures[apid]['geometry']: - # if 'solid' in elem: - # for clear_geo in global_clear_geo: - # new_elem = dict() - # # Make sure that the clear_geo is within the solid_geo otherwise we loose - # # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to - # # delete it - # if clear_geo.within(elem['solid']): - # new_elem['solid'] = elem['solid'].difference(clear_geo) - # else: - # new_elem['solid'] = elem['solid'] - # if 'follow' in elem: - # new_elem['follow'] = elem['follow'] - # if 'clear' in elem: - # new_elem['clear'] = elem['clear'] - # geo_list.append(deepcopy(new_elem)) - # self.apertures[apid]['geometry'] = deepcopy(geo_list) - # geo_list[:] = [] - # - # log.warning("Polygon difference done for %d apertures." % len(self.apertures)) + # list of clear geos that are to be applied to the entire file + global_clear_geo = [] + for apid in self.gerber_obj.apertures: + # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo + if 'geometry' in self.gerber_obj.apertures[apid]: + for elem in self.gerber_obj.apertures[apid]['geometry']: + if 'clear' in elem: + global_clear_geo.append(elem['clear']) + log.warning("Found %d clear polygons." % len(global_clear_geo)) + + for apid in self.gerber_obj.apertures: + temp_elem = [] + if 'geometry' in self.gerber_obj.apertures[apid]: + for elem in self.gerber_obj.apertures[apid]['geometry']: + if 'solid' in elem: + solid_geo = elem['solid'] + for clear_geo in global_clear_geo: + # Make sure that the clear_geo is within the solid_geo otherwise we loose + # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to + # delete it + if clear_geo.within(solid_geo): + solid_geo = solid_geo.difference(clear_geo) + try: + for poly in solid_geo: + new_elem = dict() + + new_elem['solid'] = poly + if 'clear' in elem: + new_elem['clear'] = poly + if 'follow' in elem: + new_elem['follow'] = poly + temp_elem.append(deepcopy(new_elem)) + except TypeError: + new_elem = dict() + new_elem['solid'] = solid_geo + if 'clear' in elem: + new_elem['clear'] = solid_geo + if 'follow' in elem: + new_elem['follow'] = solid_geo + temp_elem.append(deepcopy(new_elem)) + self.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_elem) + log.warning("Polygon difference done for %d apertures." % len(self.gerber_obj.apertures)) # and then add it to the storage elements (each storage elements is a member of a list - def job_thread(self, apid): with self.app.proc_container.new(_("Adding aperture: %s geo ...") % str(apid)): storage_elem = [] @@ -3404,30 +3414,33 @@ class FlatCAMGrbEditor(QtCore.QObject): for storage_apid, storage_val in local_storage_dict.items(): grb_obj.apertures[storage_apid] = {} - for k, v in storage_val.items(): + for k, val in storage_val.items(): if k == 'geometry': grb_obj.apertures[storage_apid][k] = [] - for geo_el in v: - new_geo = dict() + for geo_el in val: geometric_data = geo_el.geo - for key in geometric_data: - if key == 'solid': - new_geo[key] = geometric_data['solid'] - poly_buffer.append(deepcopy(new_geo['solid'])) - if key == 'follow': - if isinstance(geometric_data[key], Polygon): - buff_val = -(int(storage_apid) / 2) - geo_f = geo_el.geo.buffer(buff_val).exterior - new_geo[key] = geo_f - else: - new_geo[key] = geometric_data[key] - follow_buffer.append(deepcopy(new_geo['follow'])) - if key == 'clear': - new_geo[key] = geometric_data['clear'] - grb_obj.apertures[storage_apid][k].append(deepcopy(new_geo)) + new_geo_el = dict() + if 'solid' in geometric_data: + new_geo_el['solid'] = geometric_data['solid'] + poly_buffer.append(deepcopy(new_geo_el['solid'])) + + if 'follow' in geometric_data: + if isinstance(geometric_data['follow'], Polygon): + buff_val = -(int(storage_apid) / 2) + geo_f = (geometric_data['follow'].buffer(buff_val)).exterior + new_geo_el['follow'] = geo_f + else: + new_geo_el['follow'] = geometric_data['follow'] + follow_buffer.append(deepcopy(new_geo_el['follow'])) + + if 'clear' in geometric_data: + new_geo_el['clear'] = geometric_data['clear'] + + if new_geo_el: + grb_obj.apertures[storage_apid][k].append(deepcopy(new_geo_el)) else: - grb_obj.apertures[storage_apid][k] = v + grb_obj.apertures[storage_apid][k] = val grb_obj.aperture_macros = deepcopy(self.gerber_obj.aperture_macros)