Skip to content

Commit b7ff6f2

Browse files
committed
pad: Add initial support for padstack
1 parent 921040e commit b7ff6f2

2 files changed

Lines changed: 200 additions & 1 deletion

File tree

src/kiutils/footprint.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from os import path
2323

2424
from kiutils.items.zones import Zone
25-
from kiutils.items.brditems import Teardrops
25+
from kiutils.items.brditems import Teardrops, PadStack
2626
from kiutils.items.common import Image, Coordinate, Net, Group, Font, EmbeddedFile
2727
from kiutils.items.dimensions import Dimension
2828
from kiutils.items.fpitems import *
@@ -579,6 +579,9 @@ class Pad:
579579
teardrops: Optional[Teardrops] = None
580580
"""The optional ``teardrops`` token defines the teardrop connections for the pad"""
581581

582+
padstack: Optional[PadStack] = None
583+
"""The optional ``padstack`` token defines pad pattern on different layers"""
584+
582585
@classmethod
583586
def from_sexpr(cls, exp: list) -> Pad:
584587
"""Convert the given S-Expresstion into a Pad object
@@ -661,6 +664,8 @@ def from_sexpr(cls, exp: list) -> Pad:
661664
object.thermalGap = item[1]
662665
elif item[0] == "options":
663666
object.customPadOptions = PadOptions().from_sexpr(item)
667+
elif item[0] == "padstack":
668+
object.padstack = PadStack.from_sexpr(item)
664669
elif item[0] == "primitives":
665670
for primitive in item[1:]:
666671
if primitive[0] == "gr_text":
@@ -811,6 +816,9 @@ def _to_sexpr_raw(self):
811816
if self.tstamp is not None:
812817
expr.append(["uuid", quote(self.tstamp)])
813818

819+
if self.padstack is not None:
820+
expr.append(self.padstack._to_sexpr_raw())
821+
814822
return expr
815823

816824

src/kiutils/items/brditems.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,3 +2090,194 @@ def _to_sexpr_raw(self):
20902090
expr.append(formatter(value))
20912091

20922092
return expr
2093+
2094+
2095+
@dataclass
2096+
class PadStackLayer:
2097+
name: str = ""
2098+
2099+
shape: str = ""
2100+
2101+
size: list[float] = field(default_factory=lambda: [0, 0])
2102+
2103+
rect_delta: list[float] = field(default_factory=lambda: [0, 0])
2104+
2105+
offset: list[float] = field(default_factory=lambda: [0, 0])
2106+
2107+
thermal_bridge_angle: Optional[float] = None
2108+
2109+
thermal_gap: Optional[int] = None
2110+
2111+
thermal_bridge_width: Optional[int] = None
2112+
2113+
clearance: Optional[str] = None
2114+
2115+
zone_connect: Optional[int] = None
2116+
2117+
@classmethod
2118+
def from_sexpr(cls, exp: list) -> PadStackLayer:
2119+
"""Convert the given S-Expresstion into a PadStackLayer object
2120+
2121+
Args:
2122+
- exp (list): Part of parsed S-Expression ``(layer ...)``
2123+
2124+
Raises:
2125+
- Exception: When given parameter's type is not a list
2126+
- Exception: When the first item of the list is not generator
2127+
2128+
Returns:
2129+
- PadStackLayer: Object of the class initialized with the given S-Expression
2130+
"""
2131+
if not isinstance(exp, list):
2132+
raise Exception("Expression does not have the correct type")
2133+
2134+
if exp[0] != "layer":
2135+
raise Exception("Expression does not have the correct type")
2136+
2137+
object = cls()
2138+
object.name = exp[1]
2139+
2140+
for item in exp[2:]:
2141+
if not isinstance(item, list):
2142+
raise ValueError(
2143+
f"Expected list property [key, value], got: {item}. Full expression: {exp}"
2144+
)
2145+
elif item[0] == "shape":
2146+
object.shape = item[1]
2147+
elif item[0] == "size":
2148+
object.size = item[1:]
2149+
elif item[0] == "rect_delta":
2150+
object.rect_delta = item[1:]
2151+
elif item[0] == "offset":
2152+
object.offset = item[1:]
2153+
elif item[0] == "thermal_bridge_angle":
2154+
object.thermal_bridge_angle = item[1]
2155+
elif item[0] == "thermal_gap":
2156+
object.thermal_gap = item[1]
2157+
elif item[0] == "thermal_bridge_width":
2158+
object.thermal_bridge_width = item[1]
2159+
elif item[0] == "clearance":
2160+
object.clearance = item[1]
2161+
elif item[0] == "zone_connect":
2162+
object.zone_connect = item[1]
2163+
elif item[0] == "options":
2164+
print("Padstack layer options are still unsupported")
2165+
continue
2166+
else:
2167+
raise ValueError(
2168+
f"Unrecognized property key: {item[0]}. Full expression: {item}"
2169+
)
2170+
2171+
return object
2172+
2173+
def to_sexpr(self, indent=2, newline=True) -> str:
2174+
"""Generate the S-Expression representing this object
2175+
2176+
Args:
2177+
- indent (int): Number of whitespaces used to indent the output. Defaults to 2.
2178+
- newline (bool): Adds a newline to the end of the output. Defaults to True.
2179+
2180+
Returns:
2181+
- str: S-Expression of this object
2182+
"""
2183+
raw_expr = self._to_sexpr_raw()
2184+
return sexp_to_string(raw_expr)
2185+
2186+
def _to_sexpr_raw(self):
2187+
expr = ["layer", escape_and_quote(self.name)]
2188+
2189+
expr.append(["shape", self.shape])
2190+
2191+
expr.append(["size", self.size[0], self.size[1]])
2192+
2193+
if self.rect_delta[0] != 0 or self.rect_delta[1] != 0:
2194+
expr.append(["rect_delta", self.rect_delta[0], self.rect_delta[1]])
2195+
2196+
if self.offset[0] != 0 or self.offset[1] != 0:
2197+
expr.append(["offset", self.offset[0], self.offset[1]])
2198+
2199+
# TODO options
2200+
2201+
if self.thermal_bridge_angle is not None:
2202+
expr.append(["thermal_bridge_angle", self.thermal_bridge_angle])
2203+
2204+
if self.thermal_gap is not None:
2205+
expr.append(["thermal_gap", self.thermal_gap])
2206+
2207+
if self.thermal_bridge_width is not None:
2208+
expr.append(["thermal_bridge_width", self.thermal_bridge_width])
2209+
2210+
if self.clearance is not None:
2211+
expr.append(["clearance", self.clearance])
2212+
2213+
if self.zone_connect is not None:
2214+
expr.append(["zone_connect", self.zone_connect])
2215+
2216+
return expr
2217+
2218+
2219+
@dataclass
2220+
class PadStack:
2221+
mode: str = ""
2222+
2223+
layers: dict[str, PadStackLayer] = field(default_factory=dict)
2224+
2225+
@classmethod
2226+
def from_sexpr(cls, exp: list) -> PadStack:
2227+
"""Convert the given S-Expresstion into a PadStack object
2228+
2229+
Args:
2230+
- exp (list): Part of parsed S-Expression ``(padstack ...)``
2231+
2232+
Raises:
2233+
- Exception: When given parameter's type is not a list
2234+
- Exception: When the first item of the list is not generator
2235+
2236+
Returns:
2237+
- PadStack: Object of the class initialized with the given S-Expression
2238+
"""
2239+
if not isinstance(exp, list):
2240+
raise Exception("Expression does not have the correct type")
2241+
2242+
if exp[0] != "padstack":
2243+
raise Exception("Expression does not have the correct type")
2244+
2245+
object = cls()
2246+
for item in exp[1:]:
2247+
if not isinstance(item, list):
2248+
raise ValueError(
2249+
f"Expected list property [key, value], got: {item}. Full expression: {exp}"
2250+
)
2251+
elif item[0] == "mode":
2252+
object.mode = item[1]
2253+
elif item[0] == "layer":
2254+
object.layers[item[1]] = PadStackLayer.from_sexpr(item[1])
2255+
else:
2256+
raise ValueError(
2257+
f"Unrecognized property key: {item[0]}. Full expression: {item}"
2258+
)
2259+
2260+
return object
2261+
2262+
def to_sexpr(self, indent=2, newline=True) -> str:
2263+
"""Generate the S-Expression representing this object
2264+
2265+
Args:
2266+
- indent (int): Number of whitespaces used to indent the output. Defaults to 2.
2267+
- newline (bool): Adds a newline to the end of the output. Defaults to True.
2268+
2269+
Returns:
2270+
- str: S-Expression of this object
2271+
"""
2272+
raw_expr = self._to_sexpr_raw()
2273+
return sexp_to_string(raw_expr)
2274+
2275+
def _to_sexpr_raw(self):
2276+
expr = ["padstack"]
2277+
2278+
expr.append(["mode", self.mode])
2279+
2280+
for layer in self.layers:
2281+
expr.append(layer._to_sexpr_raw())
2282+
2283+
return expr

0 commit comments

Comments
 (0)