summaryrefslogtreecommitdiff
path: root/gerbonara/cad/kicad/footprints.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara/cad/kicad/footprints.py')
-rw-r--r--gerbonara/cad/kicad/footprints.py70
1 files changed, 40 insertions, 30 deletions
diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py
index cb7b69d..1d7ee08 100644
--- a/gerbonara/cad/kicad/footprints.py
+++ b/gerbonara/cad/kicad/footprints.py
@@ -55,7 +55,7 @@ class Text:
type: AtomChoice(Atom.reference, Atom.value, Atom.user) = Atom.user
text: str = ""
at: AtPos = field(default_factory=AtPos)
- unlocked: Flag() = False
+ unlocked: OmitDefault(Named(YesNoAtom())) = False
layer: Named(str) = None
uuid: UUID = field(default_factory=UUID)
hide: Flag() = False
@@ -243,7 +243,7 @@ class Arc:
@sexp_type('fp_poly')
class Polygon:
- pts: PointList = field(default_factory=PointList)
+ pts: PointList = field(default_factory=list)
layer: Named(str) = None
uuid: UUID = field(default_factory=UUID)
width: Named(float) = None
@@ -253,13 +253,13 @@ class Polygon:
tstamp: Timestamp = None
def render(self, variables=None, cache=None):
- if len(self.pts.xy) < 2:
+ if len(self.pts) < 2:
return
dasher = Dasher(self)
- start = self.pts.xy[0]
+ start = self.pts[0]
dasher.move(start.x, start.y)
- for point in self.pts.xy[1:]:
+ for point in self.pts[1:]:
dasher.line(point.x, point.y)
if dasher.width > 0:
@@ -268,12 +268,12 @@ class Polygon:
yield go.Line(x1, -y1, x2, -y2, aperture=aperture, unit=MM)
if self.fill == Atom.solid:
- yield go.Region([(pt.x, -pt.y) for pt in self.pts.xy], unit=MM)
+ yield go.Region([(pt.x, -pt.y) for pt in self.pts], unit=MM)
@sexp_type('fp_curve')
class Curve:
- pts: PointList = field(default_factory=PointList)
+ pts: PointList = field(default_factory=list)
layer: Named(str) = None
uuid: UUID = field(default_factory=UUID)
width: Named(float) = None
@@ -314,7 +314,7 @@ class Dimension:
layer: Named(str) = None
uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
- pts: PointList = field(default_factory=PointList)
+ pts: PointList = field(default_factory=list)
height: Named(float) = None
orientation: Named(int) = 0
leader_length: Named(float) = None
@@ -334,12 +334,6 @@ class Drill:
offset: Rename(XYCoord) = None
-@sexp_type('net')
-class NetDef:
- number: int = None
- name: str = None
-
-
@sexp_type('options')
class CustomPadOptions:
clearance: Named(AtomChoice(Atom.outline, Atom.convexhull)) = Atom.outline
@@ -376,7 +370,7 @@ class Chamfer:
@sexp_type('pad')
-class Pad:
+class Pad(NetMixin):
number: str = None
type: AtomChoice(Atom.thru_hole, Atom.smd, Atom.connect, Atom.np_thru_hole) = None
shape: AtomChoice(Atom.circle, Atom.rect, Atom.oval, Atom.trapezoid, Atom.roundrect, Atom.custom) = None
@@ -395,7 +389,7 @@ class Pad:
thermal_bridge_width: Named(float) = 0.5
chamfer_ratio: Named(float) = None
chamfer: Chamfer = None
- net: NetDef = None
+ net: Net = None
tstamp: Timestamp = None
pin_function: Named(str) = None
pintype: Named(str) = None
@@ -411,7 +405,7 @@ class Pad:
options: OmitDefault(CustomPadOptions) = None
primitives: OmitDefault(CustomPadPrimitives) = None
_: SEXP_END = None
- footprint: object = None
+ footprint: object = field(repr=False, default=None)
def __after_parse__(self, parent=None):
self.layers = unfuck_layers(self.layers)
@@ -619,6 +613,7 @@ SUPPORTED_FILE_FORMAT_VERSIONS = [20210108, 20211014, 20221018, 20230517]
class Footprint:
name: str = None
_version: Named(int, name='version') = 20221018
+ uuid: UUID = field(default_factory=UUID)
generator: Named(str) = Atom.gerbonara
generator_version: Named(str) = __version__
locked: Flag() = False
@@ -657,11 +652,11 @@ class Footprint:
pads: List(Pad) = field(default_factory=list)
zones: List(Zone) = field(default_factory=list)
groups: List(Group) = field(default_factory=list)
+ embedded_fonts: Named(YesNoAtom()) = False
models: List(Model) = field(default_factory=list)
_ : SEXP_END = None
original_filename: str = None
- _bounding_box: tuple = None
- board: object = None
+ board: object = field(repr=False, default=None)
def __after_parse__(self, parent):
for pad in self.pads:
@@ -708,6 +703,10 @@ class Footprint:
if not self.property_value('Description', None):
self.set_property('Description', self.descr or '', 0, 0, 0)
+ def reset_nets(self):
+ for pad in self.pads:
+ pad.reset_net()
+
@property
def pads_by_number(self):
return {(int(pad.number) if pad.number.isnumeric() else pad.number): pad for pad in self.pads if pad.number}
@@ -734,6 +733,14 @@ class Footprint:
def offset(self, x=0, y=0):
self.at = self.at.with_offset(x, y)
+ def copy_placement(self, template):
+ # Fix up rotation of pads - KiCad saves each pad's rotation in *absolute* coordinates, not relative to the
+ # footprint. Because we overwrite the footprint's rotation below, we have to first fix all pads to match the
+ # new rotation.
+ self.rotate(math.radians(template.at.rotation - self.at.rotation))
+ self.at = copy.copy(template.at)
+ self.side = template.side
+
@property
def version(self):
return self._version
@@ -818,7 +825,7 @@ class Footprint:
self.layer = flip_layer(self.layer)
for obj in self.objects():
- if hasattr(obj, 'layer'):
+ if getattr(obj, 'layer', None) is not None:
obj.layer = flip_layer(obj.layer)
if hasattr(obj, 'layers'):
@@ -828,8 +835,9 @@ class Footprint:
obj.effects.justify.mirror = not obj.effects.justify.mirror
for obj in self.properties:
- obj.effects.justify.mirror = not obj.effects.justify.mirror
- obj.layer = flip_layer(obj.layer)
+ if obj.layer is not None:
+ obj.effects.justify.mirror = not obj.effects.justify.mirror
+ obj.layer = flip_layer(obj.layer)
@property
def single_sided(self):
@@ -868,19 +876,20 @@ class Footprint:
around the given coordinates in the global coordinate space. Otherwise rotate around the footprint's origin. """
if (cx, cy) != (None, None):
x, y = self.at.x-cx, self.at.y-cy
- self.at.x = math.cos(angle)*x - math.sin(angle)*y + cx
- self.at.y = math.sin(angle)*x + math.cos(angle)*y + cy
+ self.at.x = math.cos(-angle)*x - math.sin(-angle)*y + cx
+ self.at.y = math.sin(-angle)*x + math.cos(-angle)*y + cy
- self.at.rotation = (self.at.rotation - math.degrees(angle)) % 360
+ self.at.rotation = (self.at.rotation + math.degrees(angle)) % 360
for pad in self.pads:
- pad.at.rotation = (pad.at.rotation - math.degrees(angle)) % 360
+ pad.at.rotation = (pad.at.rotation + math.degrees(angle)) % 360
for prop in self.properties:
- prop.at.rotation = (prop.at.rotation - math.degrees(angle)) % 360
+ if prop.at is not None:
+ prop.at.rotation = (prop.at.rotation + math.degrees(angle)) % 360
for text in self.texts:
- text.at.rotation = (text.at.rotation - math.degrees(angle)) % 360
+ text.at.rotation = (text.at.rotation + math.degrees(angle)) % 360
def set_rotation(self, angle):
old_deg = self.at.rotation
@@ -891,7 +900,8 @@ class Footprint:
pad.at.rotation = (pad.at.rotation + delta) % 360
for prop in self.properties:
- prop.at.rotation = (prop.at.rotation + delta) % 360
+ if prop.at is not None:
+ prop.at.rotation = (prop.at.rotation + delta) % 360
for text in self.texts:
text.at.rotation = (text.at.rotation + delta) % 360
@@ -970,7 +980,7 @@ class Footprint:
layer_stack.drill_pth.append(fe)
def bounding_box(self, unit=MM):
- if not self._bounding_box:
+ if not hasattr(self, '_bounding_box'):
stack = LayerStack()
layer_map = {kc_id: gn_id for kc_id, gn_id in LAYER_MAP_K2G.items() if gn_id in stack}
self.render(stack, layer_map, x=0, y=0, rotation=0, flip=False, text=False, variables={})