begin DXF export of pads
Stefan Schuermans

Stefan Schuermans commited on 2021-02-17 19:13:39
Showing 2 changed files, with 141 additions and 1 deletions.

... ...
@@ -22,6 +22,8 @@ class DxfFootprintWriter():
22 22
         self._addLayer('mask', 16)  # dark red
23 23
         self._addLayer('name', 7)  # white
24 24
         self._addLayer('number', 7)  # white
25
+        self._addLayer('pad_component', 1)  # red
26
+        self._addLayer('pad_solder', 1)  # red
25 27
         self._addLayer('pin_copper', 1)  # red
26 28
         self._addLayer('pin_drill', 5)  # blue
27 29
         self._addLayer('silk_center', 2)  # yellow
... ...
@@ -43,6 +45,40 @@ class DxfFootprintWriter():
43 45
                   (cx.mm - s, -cy.mm - l)]
44 46
         self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name})
45 47
 
48
+    def _addRect(self, x1: pcb_types.Coordinate, y1: pcb_types.Coordinate,
49
+                 x2: pcb_types.Coordinate, y2: pcb_types.Coordinate,
50
+                 width: pcb_types.Coordinate, layer_name: str):
51
+        v1 = pcb_types.Vector(x1, y1)
52
+        v2 = pcb_types.Vector(x2, y2)
53
+        i = (v2 - v1).resized(width / 2)  # half width in direction
54
+        o = i.rotatedCW()  # half width orthogonal to direction
55
+        p1 = v1 - i - o
56
+        p2 = v1 - i + o
57
+        p3 = v2 + i + o
58
+        p4 = v2 + i - o
59
+        points = [(p1.x.mm, -p1.y.mm), (p2.x.mm, -p2.y.mm),
60
+                  (p3.x.mm, -p3.y.mm), (p4.x.mm, -p4.y.mm),
61
+                  (p1.x.mm, -p1.y.mm)]
62
+        self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name})
63
+
64
+    def _addRoundRect(self, x1: pcb_types.Coordinate, y1: pcb_types.Coordinate,
65
+                      x2: pcb_types.Coordinate, y2: pcb_types.Coordinate,
66
+                      width: pcb_types.Coordinate, layer_name: str):
67
+        # FIXME: approximated rounding used for now, should be really round
68
+        v1 = pcb_types.Vector(x1, y1)
69
+        v2 = pcb_types.Vector(x2, y2)
70
+        i = (v2 - v1).resized(width / 2)  # half width in direction
71
+        steps = range(-90, 91, 15)
72
+        points = []
73
+        for deg in steps:
74
+            p = v1 - i.rotated(deg)
75
+            points.append((p.x.mm, -p.y.mm))
76
+        for deg in steps:
77
+            p = v2 + i.rotated(deg)
78
+            points.append((p.x.mm, -p.y.mm))
79
+        points.append(points[0])
80
+        self._msp.add_polyline2d(points, dxfattribs={'layer': layer_name})
81
+
46 82
     def _addSquare(self, cx: pcb_types.Coordinate, cy: pcb_types.Coordinate,
47 83
                    size: pcb_types.Coordinate, layer_name: str):
48 84
         points = [(cx.mm - size.mm / 2, -cy.mm - size.mm / 2),
... ...
@@ -108,6 +144,31 @@ class DxfFootprintWriter():
108 144
                       height=height,
109 145
                       align='TOP_CENTER')
110 146
 
147
+    def _drawPadRound(self, pad: pcb_types.Pad, pad_layer: str):
148
+        self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2,
149
+                           pad.thickness, pad_layer)
150
+        self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2,
151
+                           pad.thickness + pad.clearance, 'clearance')
152
+        self._addRoundRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.mask,
153
+                           'mask')
154
+        self._drawNameNumber((pad.r_x1 + pad.r_x2) / 2,
155
+                             (pad.r_y1 + pad.r_y2) / 2,
156
+                             pad.name,
157
+                             pad.number,
158
+                             height=pad.thickness / 3)
159
+
160
+    def _drawPadSquare(self, pad: pcb_types.Pad, pad_layer: str):
161
+        self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.thickness,
162
+                      pad_layer)
163
+        self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2,
164
+                      pad.thickness + pad.clearance, 'clearance')
165
+        self._addRect(pad.r_x1, pad.r_y1, pad.r_x2, pad.r_y2, pad.mask, 'mask')
166
+        self._drawNameNumber((pad.r_x1 + pad.r_x2) / 2,
167
+                             (pad.r_y1 + pad.r_y2) / 2,
168
+                             pad.name,
169
+                             pad.number,
170
+                             height=pad.thickness / 3)
171
+
111 172
     def _drawPinOctagon(self, pin: pcb_types.Pin):
112 173
         self._addOctagon(pin.r_x, pin.r_y, pin.thickness, 'pin_copper')
113 174
         self._addCircle(pin.r_x, pin.r_y, pin.drill, 'pin_drill')
... ...
@@ -162,7 +223,8 @@ class DxfFootprintWriter():
162 223
         # draw entities in body
163 224
         type2method = {
164 225
             pcb_types.ElementLine: self.drawElementLine,
165
-            pcb_types.Pin: self.drawPin
226
+            pcb_types.Pin: self.drawPin,
227
+            pcb_types.Pad: self.drawPad
166 228
         }
167 229
         for entity in fp.body:
168 230
             try:
... ...
@@ -175,6 +237,18 @@ class DxfFootprintWriter():
175 237
                     f' type {type(entity).__name__:s}',
176 238
                     file=sys.stderr)
177 239
 
240
+    def drawPad(self, pad: pcb_types.Pad):
241
+        """
242
+        Draw pad to DXF.
243
+        """
244
+        if 'onsolder' in pad.s_flags:
245
+            pad_layer = 'pad_solder'
246
+        else:
247
+            pad_layer = 'pad_component'
248
+        if 'square' in pad.s_flags:
249
+            return self._drawPadSquare(pad, pad_layer)
250
+        return self._drawPadRound(pad, pad_layer)
251
+
178 252
     def drawPin(self, pin: pcb_types.Pin):
179 253
         """
180 254
         Draw pin to DXF.
... ...
@@ -3,6 +3,7 @@ Types representing entities of GNU PCB files.
3 3
 """
4 4
 
5 5
 import collections
6
+import math
6 7
 
7 8
 
8 9
 class Coordinate():
... ...
@@ -62,6 +63,8 @@ class Coordinate():
62 63
         return Coordinate(self._raw - other._raw)
63 64
 
64 65
     def __truediv__(self, other):
66
+        if isinstance(other, Coordinate):
67
+            return self._raw / other._raw
65 68
         return Coordinate(self._raw / other)
66 69
 
67 70
     @property
... ...
@@ -89,6 +92,69 @@ class Coordinate():
89 92
         self._raw = round(raw)
90 93
 
91 94
 
95
+class Vector():
96
+    """
97
+    A 2D vector (2 coordinates) in a PCB file.
98
+    """
99
+
100
+    def __init__(self, x: Coordinate = None, y: Coordinate = None):
101
+        self._x = x if x is not None else Coordinate(0)
102
+        self._y = y if y is not None else Coordinate(0)
103
+
104
+    def __add__(self, other):
105
+        return Vector(self._x + other._x, self._y + other._y)
106
+
107
+    def __mul__(self, other):
108
+        return Vector(self._x * other, self._y * other)
109
+
110
+    def __neg__(self, other):
111
+        return Vector(-self._x, -self._y)
112
+
113
+    def __sub__(self, other):
114
+        return Vector(self._x - other._x, self._y - other._y)
115
+
116
+    def __truediv__(self, other):
117
+        return Vector(self._x / other, self._y / other)
118
+
119
+    @property
120
+    def x(self) -> Coordinate:
121
+        return self._x
122
+
123
+    @x.setter
124
+    def x(self, x: Coordinate):
125
+        self._x = x
126
+
127
+    @property
128
+    def y(self) -> Coordinate:
129
+        return self._y
130
+
131
+    @y.setter
132
+    def y(self, y: Coordinate):
133
+        self._y = y
134
+
135
+    @property
136
+    def length(self) -> Coordinate:
137
+        return Coordinate((self._x.raw**2 + self._y.raw**2)**.5)
138
+
139
+    def resized(self, length: Coordinate):
140
+        l = self.length
141
+        dx = self._x / l
142
+        dy = self._y / l
143
+        return Vector(length * dx, length * dy)
144
+
145
+    def rotated(self, degreesCW: float):
146
+        rad = degreesCW / 180 * math.pi
147
+        c = math.cos(rad)
148
+        s = math.sin(rad)
149
+        return Vector(self._x * c - self._y * s, self._x * s + self._y * c)
150
+
151
+    def rotatedCCW(self):
152
+        return Vector(self._y, -self._x)
153
+
154
+    def rotatedCW(self):
155
+        return Vector(-self._y, self._x)
156
+
157
+
92 158
 class Struct:
93 159
     """
94 160
     Base class for struct-like Python objects
95 161