From 6a8bd8dc3f5b961be24d089a4553143988a4071d Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 18 Nov 2025 23:43:29 +0100 Subject: [PATCH] Kicad 9.0.5 PCB compatibility WIP --- src/gerbonara/cad/kicad/base_types.py | 1 + src/gerbonara/cad/kicad/footprints.py | 15 ++++ .../cad/kicad/graphical_primitives.py | 9 +++ src/gerbonara/cad/kicad/pcb.py | 69 ++++--------------- src/gerbonara/cad/kicad/primitives.py | 42 +++++++++++ src/gerbonara/cad/kicad/schematic.py | 24 ------- 6 files changed, 82 insertions(+), 78 deletions(-) diff --git a/src/gerbonara/cad/kicad/base_types.py b/src/gerbonara/cad/kicad/base_types.py index 339b8f4..ce5f653 100644 --- a/src/gerbonara/cad/kicad/base_types.py +++ b/src/gerbonara/cad/kicad/base_types.py @@ -67,6 +67,7 @@ class UUID: @sexp_type('group') class Group: + locked: Flag() = False name: str = "" id: Named(str) = None uuid: UUID = field(default_factory=UUID) diff --git a/src/gerbonara/cad/kicad/footprints.py b/src/gerbonara/cad/kicad/footprints.py index 9ddac31..15efedd 100644 --- a/src/gerbonara/cad/kicad/footprints.py +++ b/src/gerbonara/cad/kicad/footprints.py @@ -329,6 +329,19 @@ class Chamfer: bottom_right: Flag() = False +@sexp_type('teardrops') +class TeardropSpec: + best_length_ratio: Named(float) = 1.0 + max_length: Named(float) = 2.0 + best_width_ratio: Named(float) = 1.0 + max_width: Named(float) = 2.0 + curve_points: Named(int) = 0 + filter_ratio: Named(float) = 0.9 + enabled: Named(YesNoAtom()) = True + allow_two_segments: Named(YesNoAtom()) = True + prefer_zone_connections: Named(YesNoAtom()) = True + + @sexp_type('pad') class Pad(NetMixin): number: str = None @@ -342,6 +355,7 @@ class Pad(NetMixin): properties: List(Property) = field(default_factory=list) remove_unused_layers: Named(YesNoAtom()) = False keep_end_layers: Named(YesNoAtom()) = False + zone_layer_connections: Named(Array(str)) = field(default_factory=list) uuid: UUID = field(default_factory=UUID) rect_delta: Rename(XYCoord) = None roundrect_rratio: Named(float) = None @@ -354,6 +368,7 @@ class Pad(NetMixin): pin_function: Named(str) = None pintype: Named(str) = None pinfunction: Named(str) = None + teardrops: TeardropSpec = None die_length: Named(float) = None solder_mask_margin: Named(float) = None solder_paste_margin: Named(float) = None diff --git a/src/gerbonara/cad/kicad/graphical_primitives.py b/src/gerbonara/cad/kicad/graphical_primitives.py index f62b0ef..ea112be 100644 --- a/src/gerbonara/cad/kicad/graphical_primitives.py +++ b/src/gerbonara/cad/kicad/graphical_primitives.py @@ -21,6 +21,7 @@ class TextLayer: @sexp_type('gr_text') class Text(TextMixin, BBoxMixin): + locked: Flag() = False text: str = '' at: AtPos = field(default_factory=AtPos) layer: TextLayer = field(default_factory=TextLayer) @@ -74,6 +75,7 @@ class TextBox(BBoxMixin): @sexp_type('gr_line') class Line(WidthMixin): + locked: Flag() = False start: Rename(XYCoord) = None end: Rename(XYCoord) = None angle: Named(float) = None # wat @@ -125,6 +127,7 @@ class FillMode: @sexp_type('gr_rect') class Rectangle(BBoxMixin, WidthMixin): + locked: Flag() = False start: Rename(XYCoord) = None end: Rename(XYCoord) = None layer: Named(str) = None @@ -158,6 +161,7 @@ class Rectangle(BBoxMixin, WidthMixin): @sexp_type('gr_circle') class Circle(BBoxMixin, WidthMixin): + locked: Flag() = False center: Rename(XYCoord) = None end: Rename(XYCoord) = None layer: Named(str) = None @@ -191,6 +195,7 @@ class Circle(BBoxMixin, WidthMixin): @sexp_type('gr_arc') class Arc(WidthMixin, BBoxMixin): + locked: Flag() = False start: Rename(XYCoord) = None mid: Rename(XYCoord) = None end: Rename(XYCoord) = None @@ -278,9 +283,11 @@ class Polygon(BBoxMixin, WidthMixin): @sexp_type('gr_curve') class Curve(BBoxMixin, WidthMixin): + locked: Flag() = False pts: PointList = field(default_factory=list) layer: Named(str) = None width: Named(float) = None + stroke: Stroke = field(default_factory=Stroke) uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None @@ -295,6 +302,8 @@ class Curve(BBoxMixin, WidthMixin): class AnnotationBBox: start: Rename(XYCoord) = None end: Rename(XYCoord) = None + width: Named(float) = None + fill: FillMode = False def render(self, variables=None): return [] diff --git a/src/gerbonara/cad/kicad/pcb.py b/src/gerbonara/cad/kicad/pcb.py index 6bbcad1..39895bb 100644 --- a/src/gerbonara/cad/kicad/pcb.py +++ b/src/gerbonara/cad/kicad/pcb.py @@ -60,6 +60,10 @@ def gn_layer_to_kicad(layer, flip=False): class GeneralSection: thickness: Named(float) = 1.60 legacy_teardrops: Named(YesNoAtom()) = False + drawings: Named(int) = 4 + tracks: Named(int) = 14 + modules: Named(int) = 2 + nets: Named(int) = 4 @sexp_type('layers') @@ -92,62 +96,15 @@ class StackupSettings: castellated_pads: Named(YesNoAtom()) = None edge_plating: Named(YesNoAtom()) = None - -@sexp_type('pcbplotparams') -class ExportSettings: - layerselection: Named(Atom) = None - plot_on_all_layers_selection: Named(Atom) = None - disableapertmacros: Named(YesNoAtom()) = False - usegerberextensions: Named(YesNoAtom()) = True - usegerberattributes: Named(YesNoAtom()) = True - usegerberadvancedattributes: Named(YesNoAtom()) = True - creategerberjobfile: Named(YesNoAtom()) = True - dashed_line_dash_ratio: Named(float) = 12.0 - dashed_line_gap_ratio: Named(float) = 3.0 - svguseinch: Named(YesNoAtom()) = False - svgprecision: Named(float) = 4 - excludeedgelayer: Named(YesNoAtom()) = False - plotframeref: Named(YesNoAtom()) = False - viasonmask: Named(YesNoAtom()) = False - mode: Named(int) = 1 - useauxorigin: Named(YesNoAtom()) = False - hpglpennumber: Named(int) = 1 - hpglpenspeed: Named(int) = 20 - hpglpendiameter: Named(float) = 15.0 - pdf_front_fp_property_popups: Named(YesNoAtom()) = True - pdf_back_fp_property_popups: Named(YesNoAtom()) = True - pdf_metadata: Named(YesNoAtom()) = True - dxfpolygonmode: Named(YesNoAtom()) = True - dxfimperialunits: Named(YesNoAtom()) = False - dxfusepcbnewfont: Named(YesNoAtom()) = True - psnegative: Named(YesNoAtom()) = False - psa4output: Named(YesNoAtom()) = False - plotreference: Named(YesNoAtom()) = True - plotvalue: Named(YesNoAtom()) = True - plotfptext: Named(YesNoAtom()) = True - plotinvisibletext: Named(YesNoAtom()) = False - sketchpadsonfab: Named(YesNoAtom()) = False - plotpadnumbers: Named(YesNoAtom()) = False - subtractmaskfromsilk: Named(YesNoAtom()) = False - outputformat: Named(int) = 1 - mirror: Named(YesNoAtom()) = False - drillshape: Named(int) = 0 - scaleselection: Named(int) = 1 - outputdirectory: Named(str) = "gerber" - - @sexp_type('setup') class BoardSetup: - stackup: OmitDefault(StackupSettings) = field(default_factory=StackupSettings) - pad_to_mask_clearance: Named(float) = None - solder_mask_min_width: Named(float) = None - pad_to_past_clearance: Named(float) = None - pad_to_paste_clearance_ratio: Named(float) = None - allow_soldermask_bridges_in_footprints: Named(YesNoAtom()) = False - tenting: Named(Array(AtomChoice(Atom.front, Atom.back))) = field(default_factory=lambda: [Atom.front, Atom.back]) - aux_axis_origin: Rename(XYCoord) = None - grid_origin: Rename(XYCoord) = None - export_settings: ExportSettings = field(default_factory=ExportSettings) + @classmethod + def __map__(kls, obj, parent=None, path=''): + return obj + + @classmethod + def __sexp__(kls, value): + yield value @sexp_type('segment') @@ -249,6 +206,7 @@ class Via(BBoxMixin): remove_unused_layers: Flag() = False keep_end_layers: Flag() = False free: Named(YesNoAtom()) = False + zone_layer_connections: Named(Array(str)) = field(default_factory=list) net: Named(int) = 0 uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None @@ -310,8 +268,11 @@ class Board: _version: Named(int, name='version') = 20230517 generator: Named(str) = Atom.gerbonara generator_version: Named(str) = Atom.gerbonara + legacy_generator: Named(Array(str), name='host') = None general: GeneralSection = None page: PageSettings = None + legacy_paper: Named(str, name='paper') = None + title_block: TitleBlock = None layers: Named(Array(Untagged(LayerSettings))) = field(default_factory=list) setup: BoardSetup = field(default_factory=BoardSetup) properties: List(Property) = field(default_factory=list) diff --git a/src/gerbonara/cad/kicad/primitives.py b/src/gerbonara/cad/kicad/primitives.py index 2ec89eb..2d1c9ef 100644 --- a/src/gerbonara/cad/kicad/primitives.py +++ b/src/gerbonara/cad/kicad/primitives.py @@ -120,6 +120,7 @@ class ZoneFill: thermal_gap: Named(float) = 0.508 thermal_bridge_width: Named(float) = 0.508 smoothing: ZoneSmoothing = None + radius: Named(float) = 0.125 island_removal_mode: Named(int) = None island_area_min: Named(float) = None hatch_thickness: Named(float) = None @@ -148,11 +149,23 @@ class FillSegment: class ZonePolygon: pts: ArcPointList = field(default_factory=list) + @sexp_type('placement') class ZonePlacement: enabled: Named(YesNoAtom()) = False sheetname: Named(str) = '' + +@sexp_type('teardrop') +class TeardropSpec: + type: Named(AtomChoice(Atom.padvia, Atom.pad_end)) = Atom.padvia + + +@sexp_type('attr') +class ZoneAttr: + teardrop: TeardropSpec = None + + @sexp_type('zone') class Zone: net: Named(int) = 0 @@ -164,6 +177,7 @@ class Zone: name: Named(str) = None hatch: Hatch = None priority: OmitDefault(Named(int)) = 0 + attr: ZoneAttr = None connect_pads: PadConnection = field(default_factory=PadConnection) min_thickness: Named(float) = 0.254 filled_areas_thickness: Named(YesNoAtom()) = True @@ -222,3 +236,31 @@ class Margins: bottom: float = 0.0 +@sexp_type('comment') +class TitleComment: + @classmethod + def __map__(kls, obj, parent=None, path=''): + lines = [] + for lineno, content in zip(obj[1::2], obj[2::2]): + while lineno > len(lines): + lines.append('') + lines[lineno-1] = content + + @classmethod + def __sexp__(kls, value): + l = [Atom.comment] + for i, line in enumerate(value.splitlines(), start=1): + l.append(i) + l.append(line.rstrip('\n')) + return l + + +@sexp_type('title_block') +class TitleBlock: + title: Named(str) = '' + date: Named(str) = '' + rev: Named(str) = '' + company: Named(str) = '' + comment: TitleComment = None + + diff --git a/src/gerbonara/cad/kicad/schematic.py b/src/gerbonara/cad/kicad/schematic.py index 48eadf4..4f487bd 100644 --- a/src/gerbonara/cad/kicad/schematic.py +++ b/src/gerbonara/cad/kicad/schematic.py @@ -602,30 +602,6 @@ class TextBox(TextMixin): yield from gr.TextBox.render(self, variables=variables) -@sexp_type('comment') -class TitleComment: - @classmethod - def __map__(kls, obj, parent=None, path=''): - return '\n'.join(obj[2::2]) - - @classmethod - def __sexp__(kls, value): - l = [Atom.comment] - for i, line in enumerate(value.splitlines(), start=1): - l.append(i) - l.append(line.rstrip('\n')) - return l - - -@sexp_type('title_block') -class TitleBlock: - title: Named(str) = '' - date: Named(str) = '' - rev: Named(str) = '' - company: Named(str) = '' - comment: TitleComment = None - - @sexp_type('lib_symbols') class LocalLibrary: symbols: List(Symbol) = field(default_factory=list)