""" Exporting of GNU PCB files to DXF. """ import ezdxf import sys import pcb_types class DxfFootprintWriter(): """ Writer for DXF files from GNU PCB footprints. """ def __init__(self): self._doc = ezdxf.new('R12') self._msp = self._doc.modelspace() self._layers = {} self._addLayer('clearance', 8) # gray self._addLayer('hole_drill', 3) # green self._addLayer('mask', 16) # dark red self._addLayer('name', 7) # white self._addLayer('number', 7) # white self._addLayer('pad_component', 1) # red self._addLayer('pad_solder', 1) # red self._addLayer('pin_copper', 1) # red self._addLayer('pin_drill', 5) # blue self._addLayer('silk_center', 2) # yellow def _addCircle(self, x: pcb_types.Coordinate, y: pcb_types.Coordinate, d: pcb_types.Coordinate, layer_name: str): self._msp.add_circle((x.mm, -y.mm), radius=d.mm / 2, dxfattribs={'layer': layer_name}) def _addOctagon(self, cx: pcb_types.Coordinate, cy: pcb_types.Coordinate, size: pcb_types.Coordinate, layer_name: str): l = size.mm / 2 s = l / (1 + 2**.5) points = [(cx.mm - s, -cy.mm - l), (cx.mm + s, -cy.mm - l), (cx.mm + l, -cy.mm - s), (cx.mm + l, -cy.mm + s), (cx.mm + s, -cy.mm + l), (cx.mm - s, -cy.mm + l), (cx.mm - l, -cy.mm + s), (cx.mm - l, -cy.mm - s), (cx.mm - s, -cy.mm - l)] self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name}) def _addRect(self, x1: pcb_types.Coordinate, y1: pcb_types.Coordinate, x2: pcb_types.Coordinate, y2: pcb_types.Coordinate, width: pcb_types.Coordinate, layer_name: str): v1 = pcb_types.Vector(x1, y1) v2 = pcb_types.Vector(x2, y2) i = (v2 - v1).resized(width / 2) # half width in direction o = i.rotatedCW() # half width orthogonal to direction p1 = v1 - i - o p2 = v1 - i + o p3 = v2 + i + o p4 = v2 + i - o points = [(p1.x.mm, -p1.y.mm), (p2.x.mm, -p2.y.mm), (p3.x.mm, -p3.y.mm), (p4.x.mm, -p4.y.mm), (p1.x.mm, -p1.y.mm)] self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name}) def _addRoundRect(self, x1: pcb_types.Coordinate, y1: pcb_types.Coordinate, x2: pcb_types.Coordinate, y2: pcb_types.Coordinate, width: pcb_types.Coordinate, layer_name: str): # FIXME: approximated rounding used for now, should be really round v1 = pcb_types.Vector(x1, y1) v2 = pcb_types.Vector(x2, y2) i = (v2 - v1).resized(width / 2) # half width in direction steps = range(-90, 91, 15) points = [] for deg in steps: p = v1 - i.rotated(deg) points.append((p.x.mm, -p.y.mm)) for deg in steps: p = v2 + i.rotated(deg) points.append((p.x.mm, -p.y.mm)) points.append(points[0]) self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name}) def _addSquare(self, cx: pcb_types.Coordinate, cy: pcb_types.Coordinate, size: pcb_types.Coordinate, layer_name: str): points = [(cx.mm - size.mm / 2, -cy.mm - size.mm / 2), (cx.mm + size.mm / 2, -cy.mm - size.mm / 2), (cx.mm + size.mm / 2, -cy.mm + size.mm / 2), (cx.mm - size.mm / 2, -cy.mm + size.mm / 2), (cx.mm - size.mm / 2, -cy.mm - size.mm / 2)] self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name}) def _addLayer(self, name: str, color: int = 7): if name in self._layers: return self._layers[name] = self._doc.layers.new(name=name, dxfattribs={ 'linetype': 'Continuous', 'color': color }) def _addLine(self, x1: pcb_types.Coordinate, y1: pcb_types.Coordinate, x2: pcb_types.Coordinate, y2: pcb_types.Coordinate, layer_name: str): self._msp.add_line((x1.mm, -y1.mm), (x2.mm, -y2.mm), dxfattribs={'layer': layer_name}) def _addText(self, x: pcb_types.Coordinate, y: pcb_types.Coordinate, t: str, layer_name: str, height: pcb_types.Coordinate = None, align: str = None): dxfattribs = {'layer': layer_name} if height is not None: dxfattribs['height'] = height.mm text = self._msp.add_text(t, dxfattribs=dxfattribs) kwargs = {} if align is not None: kwargs['align'] = align text.set_pos((x.mm, -y.mm), **kwargs) def _drawHole(self, pin: pcb_types.Pin): self._addCircle(pin.r_x, pin.r_y, pin.drill, 'hole_drill') self._addCircle(pin.r_x, pin.r_y, pin.thickness + pin.clearance, 'clearance') self._addCircle(pin.r_x, pin.r_y, pin.mask, 'mask') self._drawNameNumber(pin.r_x, pin.r_y, pin.name, pin.number, height=pin.drill / 2) def _drawNameNumber(self, x: pcb_types.Coordinate, y: pcb_types.Coordinate, name: str, number: str, height: pcb_types.Coordinate = None): self._addText(x, y, name, 'name', height=height, align='BOTTOM_CENTER') self._addText(x, y, number, 'number', height=height, align='TOP_CENTER') def _drawPadRound(self, pad: pcb_types.Pad, pad_layer: str): self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.thickness, pad_layer) self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.thickness + pad.clearance, 'clearance') self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.mask, 'mask') self._drawNameNumber((pad.r_x1 + pad.r_x2) / 2, (pad.r_y1 + pad.r_y2) / 2, pad.name, pad.number, height=pad.thickness / 3) def _drawPadSquare(self, pad: pcb_types.Pad, pad_layer: str): self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.thickness, pad_layer) self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.thickness + pad.clearance, 'clearance') self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.mask, 'mask') self._drawNameNumber((pad.r_x1 + pad.r_x2) / 2, (pad.r_y1 + pad.r_y2) / 2, pad.name, pad.number, height=pad.thickness / 3) def _drawPinOctagon(self, pin: pcb_types.Pin): self._addOctagon(pin.r_x, pin.r_y, pin.thickness, 'pin_copper') self._addCircle(pin.r_x, pin.r_y, pin.drill, 'pin_drill') self._addOctagon(pin.r_x, pin.r_y, pin.thickness + pin.clearance, 'clearance') self._addOctagon(pin.r_x, pin.r_y, pin.mask, 'mask') self._drawNameNumber(pin.r_x, pin.r_y, pin.name, pin.number, height=pin.drill / 2) def _drawPinRound(self, pin: pcb_types.Pin): self._addCircle(pin.r_x, pin.r_y, pin.thickness, 'pin_copper') self._addCircle(pin.r_x, pin.r_y, pin.drill, 'pin_drill') self._addCircle(pin.r_x, pin.r_y, pin.thickness + pin.clearance, 'clearance') self._addCircle(pin.r_x, pin.r_y, pin.mask, 'mask') self._drawNameNumber(pin.r_x, pin.r_y, pin.name, pin.number, height=pin.drill / 2) def _drawPinSquare(self, pin: pcb_types.Pin): self._addSquare(pin.r_x, pin.r_y, pin.thickness, 'pin_copper') self._addCircle(pin.r_x, pin.r_y, pin.drill, 'pin_drill') self._addSquare(pin.r_x, pin.r_y, pin.thickness + pin.clearance, 'clearance') self._addSquare(pin.r_x, pin.r_y, pin.mask, 'mask') self._drawNameNumber(pin.r_x, pin.r_y, pin.name, pin.number, height=pin.drill / 2) def drawElementLine(self, element_line: pcb_types.ElementLine): """ Draw element line to DXF. """ self._addLine(element_line.r_x1, element_line.r_y1, element_line.r_x2, element_line.r_y2, 'silk_center') # TODO element_line.thickness # TODO element_line.s_flags onsolder def drawFootprint(self, fp: pcb_types.Element): """ Draw footprint to DXF. """ # draw entities in header # TODO # draw entities in body type2method = { pcb_types.ElementLine: self.drawElementLine, pcb_types.Pin: self.drawPin, pcb_types.Pad: self.drawPad } for entity in fp.body: try: method = type2method[type(entity)] method(entity) except KeyError: # TODO print( 'warning: ignoring unknown entity of' f' type {type(entity).__name__:s}', file=sys.stderr) def drawPad(self, pad: pcb_types.Pad): """ Draw pad to DXF. """ if 'onsolder' in pad.s_flags: pad_layer = 'pad_solder' else: pad_layer = 'pad_component' if 'square' in pad.s_flags: return self._drawPadSquare(pad, pad_layer) return self._drawPadRound(pad, pad_layer) def drawPin(self, pin: pcb_types.Pin): """ Draw pin to DXF. """ if 'hole' in pin.s_flags: return self._drawHole(pin) if 'octagon' in pin.s_flags: return self._drawPinOctagon(pin) if 'square' in pin.s_flags: return self._drawPinSquare(pin) return self._drawPinRound(pin) def writeDxf(self, file_name: str): """ Write DXF file. """ self._doc.saveas(file_name)