def parse_gerber(filename): info("parsing gerber file \"" + filename + "\"...") #global units #global zero_suppression_mode #global coordinate_mode #global layer_polarity #global x_format #global y_format #global number_of_digits #global ratio global decimal_places # fixme - a bit funny if this changes between gerbers we're reading zero_suppression_mode = "leading" coordinate_mode = "absolute" layer_polarity = "dark" x_format = "55" y_format = "55" #units = "mm" lines = [] for line in open(filename): line = line.rstrip("\n\r") lines.append(line) gerber_instructions = [] global apertures apertures = {} aperture = "00" matched_units = 0 matched_format = 0 set_ratio = 0 #gerber_instructions.append("hi there") for line in lines: matches = 0 match = re.search("^%MO([IM][NM])\*%$", line) if match: matches = matches + 1 if (match.group(1) == "IN"): #debug("set units to inches: " + line) ratio = 1000.0 / 25.4 / 1.55 # fixme/todo: 1.55 is a magic number here else: #debug("set units to mm: " + line) ratio = 1.0 matched_units = 1 match = re.search("^%FS([LTD])([AI])X([0-9][0-9])Y([0-9][0-9])\*%$", line) if match: matches = matches + 1 debug("set format: " + line) if (match.group(1) == "L"): zero_suppression_mode = "leading" else: zero_suppression_mode = "trailing" if (match.group(2) == "A"): coordinate_mode = "absolute" else: coordinate_mode = "incremental" x_format = match.group(3) y_format = match.group(4) decimal_places = int(x_format[1]) number_of_digits = int(x_format[0]) + int(x_format[1]) number_of_digits = number_of_digits + 1 # some gerber files output 7 digits for "24" format... #decimal_places = decimal_places + 1 debug("number of digits to use for coordinates = " + str(number_of_digits)) matched_format = 1 match = re.search("^%ADD([0-9]+)C,([.0-9]+)\*%$", line) # %ADD010C,0.0254*% if match: matches = matches + 1 #aperture_length = len(match.group(1)) ap = int(match.group(1)) apertures[ap] = ("C", float(match.group(2)), float(match.group(2))) debug("aperture definition: " + line) match = re.search("^%ADD([0-9]+)R,([.0-9]+)X([.0-9]+)\*%$", line) # %ADD028R,2.6X1.6*% or %ADD34R,0.0138X0.0472*% if match: matches = matches + 1 ap = int(match.group(1)) apertures[ap] = ("R", float(match.group(2)), float(match.group(3))) debug("aperture definition: " + line) match = re.search("^%ADD([0-9]+)O,([.0-9]+)X([.0-9]+)\*%$", line) # %ADD11O,0.0138X0.0669*% if match: matches = matches + 1 ap = int(match.group(1)) apertures[ap] = ("O", float(match.group(2)), float(match.group(3))) debug("aperture definition: " + line) match = re.search("^%LN([a-zA-Z0-9]+)\*%$", line) if match: matches = matches + 1 gerber_instructions.append("setlayer" + match.group(1)) match = re.search("^(G0[1-3])\*$", line) if match: matches = matches + 1 gerber_instructions.append(match.group(1)) match = re.search("^(G0[1-3][XY].*)\*$", line) if match: matches = matches + 1 gerber_instructions.append(match.group(1)) match = re.search("^([XY].*)\*$", line) if match: matches = matches + 1 #debug(line) gerber_instructions.append(match.group(1)) match = re.search("^(G54D)([0-9]+)\*$", line) if match: matches = matches + 1 debug("aperture selection: " + match.group(2)) gerber_instructions.append(match.group(1) + match.group(2)) #debug(match.group(1)) match = re.search("^%LP([DC])\*%$", line) if match: # this unfortunately only grabs the last instance... matches = matches + 1 if (match.group(1) == "D"): layer_polarity = "dark" else: layer_polarity = "clear" match = re.search("^%IPNEG\*%$", line) # %IPNEG% = reverse whole layer if match: matches = matches + 1 # do something here for this... match = re.search("^%IPPOS\*%$", line) # %IPPOS% = whole layer normal if match: matches = matches + 1 match = re.search("^G04.*$", line) if match: matches = matches + 1 match = re.search("^%$", line) if match: matches = matches + 1 match = re.search("^\*$", line) if match: matches = matches + 1 match = re.search("^G74\*$", line) if match: warning("cannot handle single quadrant G74 mode") matches = matches + 1 #exit() match = re.search("^G75\*$", line) # multi-quadrant mode if match: matches = matches + 1 match = re.search("^$", line) if match: matches = matches + 1 match = re.search("^M02\*$", line) # M02* = end of job if match: matches = matches + 1 match = re.search("^%AM(.*)\*$", line) # AM = aperture macro definition if match: matches = matches + 1 warning("ignoring aperture macro \"" + match.group(1) + "\"") match = re.search("^([$0-9]*),", line) # aperture macro definition continuation if match: matches = matches + 1 match = re.search("^%AD(.*)\*$", line) # AD = aperture macro instantiation if match: matches = matches + 1 error("aperture instantiation \"" + match.group(1) + "\" ignored - this part needs to be coded") match = re.search("^%IN(.*)\*%$", line) # IN = image name if match: matches = matches + 1 debug("ignoring image name \"" + match.group(1) + "\"") if (matches == 0): warning("did not parse \"" + line + "\"") if (set_ratio == 0) and (matched_units == 1) and (matched_format == 1): ratio = ratio / (10.0**int(decimal_places)) debug("ratio = " + str(ratio)) gerber_instructions.append("setratio" + str(ratio)) set_ratio = 1 if (matched_format == 0): error("did not find format line", 2) for aperture in apertures: (CROP, w, h) = apertures[aperture] debug("aperture[" + str(aperture) + "]: " + CROP + " " + str(w) + " " + str(h)) #debug("units: " + units) debug("x_format: " + x_format) debug("y_format: " + y_format) debug("layer_polarity: " + layer_polarity) debug("zero_suppression_mode: " + zero_suppression_mode) debug("coordinate_mode: " + coordinate_mode) return gerber_instructions
def draw_gerber_layer(parent_object, gerber_instructions, layer_name, color = "#000000", stroke_width = laser_stroke_width): layer = add_layer(parent_object, layer_name) overall_layer = layer group = add_group(layer, color=color, stroke_width=stroke_width) #group.stroke(color=color, width=float(stroke_width), miterlimit=4, opacity=1) #group.dasharray("none") #group.fill("none") debug2("instructions:") x = 0 y = 0 x_old = 0 y_old = 0 x_string = "0" y_string = "0" i_string = "0" d_string = "02" # pen up i = 0 j = 0 aperture = "00" mode = "linear" ratio = 5.0 #global verbosity #verbosity = 4 for instruction in gerber_instructions: debug("instruction: " + instruction) match = re.search("^setratio([.0-9]*[e]*[-0-9]*)$", instruction) if match: ratio = float(match.group(1)) debug("found ratio: " + str(ratio)) match = re.search("^setlayer(.*)$", instruction) if match: layer_name = match.group(1) layer = add_layer(overall_layer, layer_name) group = add_group(layer, color=color, stroke_width=stroke_width) match = re.search("^G54D([1-9][0-9])$", instruction) # "G54D22" means follow the goto X,Y instructions after this line, and flash the D22 aperture every time we get a D03 ("flash aperture") command if match: debug("aperture selection: " + match.group(1)) aperture = int(match.group(1)) if not aperture in apertures.keys(): error("can't find aperture #" + str(aperture), 4) (CROP, w, h) = apertures[aperture] w = 25.4 * w # fixme/todo: magic number here h = 25.4 * h # fixme/todo: magic number here debug("aperture[" + str(aperture) + "]: " + CROP + " " + str(w) + " " + str(h)) d_string = "04" # skip doing anything this pass through the following code match = re.search("^G(0[0-3])(.*)$", instruction) if match: instruction = match.group(2) #debug2("remaining instruction: " + instruction) if (match.group(1) == "01"): mode = "linear" debug("mode = linear") elif (match.group(1) == "02"): mode = "cc_arc" debug("mode = cc arc") elif (match.group(1) == "03"): mode = "cw_arc" debug("mode = cw arc") else: error("?", 3) match = 1 while match: #match = re.search("^([XYIJ])([-0-9]{1," + str(number_of_digits) + "})(.*)$", instruction) match = re.search("^([XYIJ])([-0-9]{1,10})(.*)$", instruction) if match: instruction = match.group(3) #debug2("remaining instruction: " + instruction) if (match.group(1) == "X"): x_string = match.group(2) x = +int(x_string) * ratio elif (match.group(1) == "Y"): y_string = match.group(2) y = +int(y_string) * ratio elif (match.group(1) == "I"): i_string = match.group(2) i = +int(i_string) * ratio elif (match.group(1) == "J"): j_string = match.group(2) j = +int(j_string) * ratio match = re.search("^D(0[1-3])$", instruction) if match: d_string = match.group(1) if (d_string == "01"): # pen down debug(" " + x_string + " " + y_string + " " + d_string) if (mode == "linear"): group.add(svg.line( (x_old,y_old), (x,y) )) #debug("(" + str(x_old) + "," + str(y_old) + ") -> (" + str(x) + "," + str(y) + ") with pen down") else: # PADS: "G03X58250I-3315J-5D01*" or "G03X201969Y117031I0J-1969D01*" # altium: "G03*", then "X-1778Y-10160I2540J0D01*" radius = math.sqrt(i**2+j**2) delta_x = x - x_old delta_y = y - y_old x_center = x_old + i y_center = y_old + j debug("old(" + str(x_old) + "," + str(y_old) + ") -> xy(" + str(x) + "," + str(y) + ") -> ij(" + str(i) + "," + str(j) + ") -> center(" + str(x_center) + "," + str(y_center) + ")") #debug("(x,y,i,j) = (" + str(x) + "," + str(y) + "," + str(i) + "," + str(j) + ")") if (mode == "cc_arc"): #debug("cw arc here") # from http://stackoverflow.com/questions/25019441/arc-pie-cut-in-svgwrite # dwg.path(d="M {0},{1} l {2},{3} a {4},{4} 0 0,0 {5},{6} z".format(start_x, start_y, m0, n0, radius, m1, n1), # M=moveto, L=lineto, a=arc?, z=close path # a: (rx ry x-axis-rotation large-arc-flag sweep-flag x y) string = "M {0},{1} a {2},{2} 0 0,0 {3},{4}".format(x_old, y_old, radius, delta_x, delta_y) elif (mode == "cw_arc"): string = "M {0},{1} a {2},{2} 0 0,1 {3},{4}".format(x_old, y_old, radius, delta_x, delta_y) else: error("?", 4) #group.add(svg.circle( (x_center,y_center), radius, fill="none" )) #group.add(svg.line( (x_old, y_old), (x, y) )) debug(string) group.add(svg.path(d=string, fill="none")) elif (d_string == "02"): pass # maybe change to a new group here? elif (d_string == "03"): if (aperture == 0): warning("unhandled D03 flash operation") else: t = laser_stroke_width / 2.0 #x = x / 1.5 #y = y / 1.5 debug("flashing aperture[" + str(aperture) + "]: " + CROP + " " + str(w) + " " + str(h) + " at (" + str(x) + "," + str(y) + ")") if (CROP == "R"): group.add(svg.rect( (x-w/2.0+t,y-h/2.0+t), (w-2*t,h-2*t) )) # should add a special case here for O (oval) and make a rectangle and two half-circles elif (d_string == "04"): pass # silently fall thorough, otherwise G54D commands trigger output the first pass through else: error("unknown d string", 5) x_old = x y_old = y return overall_layer
panel_frame_thickness = 10.0 # mm - overall border thickness #panel_tab_length = 7.0 # mm #panel_tab_width = 2.0 # mm x_gap_between_instances_of_boards = 5.0 # mm y_gap_between_instances_of_boards = 5.0 # mm #stencil_half_moon_location = "NorthSouth" stencil_half_moon_location = "EastWest" number_of_extra_half_moons_per_side = 1 protoboard_width = 151 # should measure this width so that the mirror-image ends up in the right place protoboard_height = 151 if (fill_protoboard == 1): number_of_horizontal_instances = int(math.floor(protoboard_width / board_width)) number_of_vertical_instances = int(math.floor(protoboard_height / board_height)) if (number_of_horizontal_instances < 1) or (number_of_vertical_instances < 1): error("can't fit board on panel", 6) panel_width = number_of_horizontal_instances * board_width panel_height = number_of_vertical_instances * board_height panel_width = panel_width + (number_of_horizontal_instances - 1) * x_gap_between_instances_of_boards panel_height = panel_height + (number_of_horizontal_instances - 1) * y_gap_between_instances_of_boards extra_x_offset = (protoboard_width - panel_width ) / 2.0 extra_y_offset = (protoboard_height - panel_height) / 2.0 panel_width = protoboard_width panel_height = protoboard_height else: #extra_x_offset = x_gap_between_instances_of_boards # or 0.0/whatever #extra_y_offset = y_gap_between_instances_of_boards # or 0.0/whatever extra_x_offset = panel_frame_thickness # + panel_tab_length extra_y_offset = panel_frame_thickness panel_width = number_of_horizontal_instances * board_width + 2.0 * extra_x_offset panel_height = number_of_vertical_instances * board_height + 2.0 * extra_y_offset