Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Commit

Permalink
Core: KiCAD Sch Fileformat (#60)
Browse files Browse the repository at this point in the history
* Add KiCAD schematic fileformat support
* Make bool parsing equal to kicad

---------

Co-authored-by: iopapamanoglou <[email protected]>
  • Loading branch information
mawildoer and iopapamanoglou authored Sep 13, 2024
1 parent 024094a commit 9586bf9
Show file tree
Hide file tree
Showing 7 changed files with 586 additions and 162 deletions.
4 changes: 3 additions & 1 deletion src/faebryk/core/graphinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from faebryk.core.link import Link, LinkDirect, LinkNamedParent
from faebryk.libs.util import (
NotNone,
exceptions_to_log,
try_avoid_endless_recursion,
)

Expand Down Expand Up @@ -123,7 +124,8 @@ def connect(self, other: Self, linkcls=None) -> Self:
self.G.add_edge(self, other, link=link)

if logger.isEnabledFor(logging.DEBUG):
logger.debug(f"GIF connection: {link}")
with exceptions_to_log():
logger.debug(f"GIF connection: {link}")

return self

Expand Down
153 changes: 12 additions & 141 deletions src/faebryk/libs/kicad/fileformats.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import logging
import uuid
from dataclasses import dataclass, field
from enum import IntEnum, StrEnum, auto
from pathlib import Path
from typing import Any, Optional

from dataclasses_json import CatchAll, Undefined, dataclass_json

from faebryk.libs.kicad.fileformats_common import (
UUID,
C_effects,
C_pts,
C_stroke,
C_wh,
C_xy,
C_xyr,
C_xyz,
gen_uuid,
)
from faebryk.libs.sexp.dataclass_sexp import JSON_File, SEXP_File, SymEnum, sexp_field
from faebryk.libs.util import KeyErrorAmbiguous

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -474,90 +483,6 @@ class C_text_variables:
unknown: CatchAll = None


class UUID(str):
pass


def gen_uuid(mark: str = ""):
# format: d864cebe-263c-4d3f-bbd6-bb51c6d2a608
value = uuid.uuid4().hex

suffix = mark.encode().hex()
if suffix:
value = value[: -len(suffix)] + suffix

DASH_IDX = [8, 12, 16, 20]
formatted = value
for i, idx in enumerate(DASH_IDX):
formatted = formatted[: idx + i] + "-" + formatted[idx + i :]

return UUID(formatted)


@dataclass
class C_xy:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))

def __sub__(self, other: "C_xy") -> "C_xy":
return C_xy(x=self.x - other.x, y=self.y - other.y)

def __add__(self, other: "C_xy") -> "C_xy":
return C_xy(x=self.x + other.x, y=self.y + other.y)

def rotate(self, center: "C_xy", angle: float) -> "C_xy":
import math

angle = -angle # rotate kicad style counter-clockwise

# Translate point to origin
translated_x = self.x - center.x
translated_y = self.y - center.y

# Convert angle to radians
angle = math.radians(angle)

# Rotate
rotated_x = translated_x * math.cos(angle) - translated_y * math.sin(angle)
rotated_y = translated_x * math.sin(angle) + translated_y * math.cos(angle)

# Translate back
new_x = rotated_x + center.x
new_y = rotated_y + center.y

return C_xy(x=new_x, y=new_y)


@dataclass
class C_xyz:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))
z: float = field(**sexp_field(positional=True))


@dataclass
class C_xyr:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))
r: float = field(**sexp_field(positional=True), default=0)


@dataclass
class C_wh:
w: float = field(**sexp_field(positional=True))
h: Optional[float] = field(**sexp_field(positional=True), default=None)


@dataclass
class C_stroke:
class E_type(SymEnum):
solid = auto()
default = auto()

width: float
type: E_type


@dataclass
class C_text_layer:
class E_knockout(SymEnum):
Expand All @@ -567,56 +492,6 @@ class E_knockout(SymEnum):
knockout: Optional[E_knockout] = field(**sexp_field(positional=True), default=None)


@dataclass
class C_effects:
@dataclass
class C_font:
size: C_wh
thickness: Optional[float] = None

@dataclass
class C_justify:
class E_justify(SymEnum):
center_horizontal = ""
left = auto()
right = auto()
center_vertical = ""
bottom = auto()
top = auto()
normal = ""
mirror = auto()

justifys: list[E_justify] = field(
**sexp_field(positional=True), default_factory=list
)

font: C_font

# Legal:
# (justify mirror right)
# (justify bottom)
justifys: list[C_justify] = field(
**sexp_field(multidict=True), default_factory=list
)

def get_justifys(self) -> list[C_justify.E_justify]:
return [j_ for j in self.justifys for j_ in j.justifys]

def __post_init__(self):
justifys = set(self.get_justifys())

J = C_effects.C_justify.E_justify

def _only_one_of(lst: list[J]):
dups = [j for j in justifys if j in lst]
if len(dups) > 1:
raise KeyErrorAmbiguous(dups)

_only_one_of([J.mirror, J.normal])
_only_one_of([J.left, J.right, J.center_horizontal])
_only_one_of([J.top, J.bottom, J.center_vertical])


class E_fill(SymEnum):
none = auto()
solid = auto()
Expand Down Expand Up @@ -687,10 +562,6 @@ class C_rect:

@dataclass
class C_polygon:
@dataclass
class C_pts:
xys: list[C_xy] = field(**sexp_field(multidict=True), default_factory=list)

pts: C_pts


Expand Down Expand Up @@ -1042,7 +913,7 @@ class C_filled_polygon:
island: Optional[bool] = field(
**sexp_field(positional=True), default=None
)
pts: C_polygon.C_pts
pts: C_pts

net: int
net_name: str
Expand Down
151 changes: 151 additions & 0 deletions src/faebryk/libs/kicad/fileformats_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import logging
import uuid
from dataclasses import dataclass, field
from enum import auto
from typing import Optional

from faebryk.libs.sexp.dataclass_sexp import SymEnum, sexp_field
from faebryk.libs.util import KeyErrorAmbiguous

logger = logging.getLogger(__name__)

# TODO find complete examples of the fileformats, maybe in the kicad repo


class UUID(str):
pass


@dataclass
class C_xy:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))

def __sub__(self, other: "C_xy") -> "C_xy":
return C_xy(x=self.x - other.x, y=self.y - other.y)

def __add__(self, other: "C_xy") -> "C_xy":
return C_xy(x=self.x + other.x, y=self.y + other.y)

def rotate(self, center: "C_xy", angle: float) -> "C_xy":
import math

angle = -angle # rotate kicad style counter-clockwise

# Translate point to origin
translated_x = self.x - center.x
translated_y = self.y - center.y

# Convert angle to radians
angle = math.radians(angle)

# Rotate
rotated_x = translated_x * math.cos(angle) - translated_y * math.sin(angle)
rotated_y = translated_x * math.sin(angle) + translated_y * math.cos(angle)

# Translate back
new_x = rotated_x + center.x
new_y = rotated_y + center.y

return C_xy(x=new_x, y=new_y)


@dataclass
class C_xyz:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))
z: float = field(**sexp_field(positional=True))


@dataclass
class C_xyr:
x: float = field(**sexp_field(positional=True))
y: float = field(**sexp_field(positional=True))
r: float = field(**sexp_field(positional=True), default=0)


@dataclass
class C_wh:
w: float = field(**sexp_field(positional=True))
h: Optional[float] = field(**sexp_field(positional=True), default=None)


@dataclass
class C_stroke:
class E_type(SymEnum):
solid = auto()
default = auto()

width: float
type: E_type


@dataclass
class C_effects:
@dataclass
class C_font:
size: C_wh
thickness: Optional[float] = None

@dataclass
class C_justify:
class E_justify(SymEnum):
center_horizontal = ""
left = auto()
right = auto()
center_vertical = ""
bottom = auto()
top = auto()
normal = ""
mirror = auto()

justifys: list[E_justify] = field(
**sexp_field(positional=True), default_factory=list
)

font: C_font

# Legal:
# (justify mirror right)
# (justify bottom)
justifys: list[C_justify] = field(
**sexp_field(multidict=True), default_factory=list
)

def get_justifys(self) -> list[C_justify.E_justify]:
return [j_ for j in self.justifys for j_ in j.justifys]

def __post_init__(self):
justifys = set(self.get_justifys())

J = C_effects.C_justify.E_justify

def _only_one_of(lst: list[J]):
dups = [j for j in justifys if j in lst]
if len(dups) > 1:
raise KeyErrorAmbiguous(dups)

_only_one_of([J.mirror, J.normal])
_only_one_of([J.left, J.right, J.center_horizontal])
_only_one_of([J.top, J.bottom, J.center_vertical])


@dataclass
class C_pts:
xys: list[C_xy] = field(**sexp_field(multidict=True), default_factory=list)


def gen_uuid(mark: str = ""):
# format: d864cebe-263c-4d3f-bbd6-bb51c6d2a608
value = uuid.uuid4().hex

suffix = mark.encode().hex()
if suffix:
value = value[: -len(suffix)] + suffix

DASH_IDX = [8, 12, 16, 20]
formatted = value
for i, idx in enumerate(DASH_IDX):
formatted = formatted[: idx + i] + "-" + formatted[idx + i :]

return UUID(formatted)
Loading

0 comments on commit 9586bf9

Please sign in to comment.