diff options
Diffstat (limited to 'gerbonara/cad/kicad/footprints.py')
-rw-r--r-- | gerbonara/cad/kicad/footprints.py | 70 |
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={}) |