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

Export: KiCAD Schematic Transformer #68

Merged
merged 37 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4d0952d
Base from existing KiCAD PCB transformer
mawildoer Sep 13, 2024
2bb30f7
Sch Transformer WIP
mawildoer Sep 13, 2024
dfd6404
Library: Consolidate has_footprint implementations
mawildoer Sep 16, 2024
53842c7
Library: Add has_symbol traits
mawildoer Sep 16, 2024
5533314
Library: Add basis of has_kicad_symbol_...
mawildoer Sep 16, 2024
65170d4
Core: Cleanup PCB transformer to use F.xxx
mawildoer Sep 17, 2024
33303ea
WIP: sch transformer attachment written
mawildoer Sep 17, 2024
46417bf
Core: Add generic bounding_box method to PCB transformer
mawildoer Sep 17, 2024
2e80ac6
Core: Add schematic library parser
mawildoer Sep 17, 2024
f112c68
Copy in schematic exporter from Skidl
mawildoer Sep 17, 2024
4786aff
Remove some globalisation
mawildoer Sep 17, 2024
4c8acf9
Handle draw_ debug functions
mawildoer Sep 17, 2024
4c89f0f
Bulk auto-format things. Comments fucking suck for formatting.
mawildoer Sep 17, 2024
94c1965
Defer pygame imports
mawildoer Sep 17, 2024
d0676b0
Core: Add insert_symbol method to schematic transformer
mawildoer Sep 18, 2024
5a14bbc
repair schematic imports
mawildoer Sep 18, 2024
3b4fd72
Rename schematic file
mawildoer Sep 18, 2024
3eed958
Try singledispatch pattern to help with getters in the schematic tran…
mawildoer Sep 18, 2024
50c8a17
Add schematic layout traits
mawildoer Sep 18, 2024
95dcfc0
Core: Test schematic wire transformer
mawildoer Sep 18, 2024
997dfe4
Library: Remove old shim class for has_kicad_footprint_equal_ifs
mawildoer Sep 18, 2024
f6f37b0
Remove middle-ware for has_kicad_symbol_defined implementations
mawildoer Sep 18, 2024
ee69d0f
Core: insert symbols to schematic.
mawildoer Sep 19, 2024
6f63234
Progress with references on schematic layout
mawildoer Sep 22, 2024
8aa0ac5
Use new reference
mawildoer Sep 23, 2024
e321186
Remove unrelated changes from this branch
mawildoer Sep 23, 2024
86e2f89
Minimalalish transformer
mawildoer Sep 23, 2024
a3efda2
Minimal transformer
mawildoer Sep 23, 2024
f5b7bab
Remove crud for later
mawildoer Sep 23, 2024
01f7db8
Fix up traits to link modules to symbols
mawildoer Sep 23, 2024
7a09d0f
pre-commit
mawildoer Sep 23, 2024
f123dd1
Fix types in symbol
mawildoer Sep 23, 2024
b53db92
Add reference to L.py
mawildoer Sep 23, 2024
83e27a2
Remove lib_symbol_id
mawildoer Sep 23, 2024
c5382de
F.Electricals for symbols' pins
mawildoer Sep 23, 2024
79ca17f
pre-commit
mawildoer Sep 23, 2024
0ae1ec1
Naming consistency
mawildoer Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
443 changes: 443 additions & 0 deletions src/faebryk/exporters/schematic/kicad/transformer.py

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/faebryk/library/Symbol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import faebryk.library._F as F
from faebryk.core.module import Module
from faebryk.core.moduleinterface import ModuleInterface
from faebryk.core.reference import reference
from faebryk.core.trait import Trait


class Symbol(Module):
"""
Symbols represent a symbol instance and are bi-directionally
linked with the module they represent via the `has_linked` trait.
"""

class Pin(ModuleInterface):
represents = reference(F.Electrical)

class has_pin(F.has_reference.decless()):
"""
Attach to an ElectricalInterface to point back at the pin
"""

reference: "Symbol.Pin" = reference()

class TraitT(Trait): ...

class has_symbol(F.has_reference.decless()):
"""
Attach to an Module to point back at the pin
"""

reference: "Symbol" = reference()

class has_kicad_symbol(TraitT.decless()):
"""
If a symbol has this trait, then the symbol has a matching KiCAD symbol
:param symbol_name: The full name of the KiCAD symbol including the library name
"""

def __init__(self, symbol_name: str):
super().__init__()
self.symbol_name = symbol_name

pins: dict[str, Pin]
represents = reference(Module)

@classmethod
def with_component(cls, component: Module, pin_map: dict[str, ModuleInterface]):
sym = cls()
sym.represents = component
component.add(cls.has_symbol(sym))

sym.pins = {}
for pin_name, e_pin in pin_map.items():
pin = cls.Pin()
pin.represents = e_pin
e_pin.add(cls.Pin.has_pin(pin))
sym.pins[pin_name] = pin

return sym
5 changes: 4 additions & 1 deletion src/faebryk/library/_F.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from faebryk.library.has_overriden_name import has_overriden_name
from faebryk.library.Operation import Operation
from faebryk.library.has_linked_pad import has_linked_pad
from faebryk.library.has_reference import has_reference
from faebryk.library.can_bridge import can_bridge
from faebryk.library.has_designator import has_designator
from faebryk.library.has_descriptive_properties import has_descriptive_properties
Expand All @@ -42,7 +43,6 @@
from faebryk.library.has_picker import has_picker
from faebryk.library.has_pcb_layout import has_pcb_layout
from faebryk.library.has_pcb_routing_strategy import has_pcb_routing_strategy
from faebryk.library.has_reference import has_reference
from faebryk.library.has_resistance import has_resistance
from faebryk.library.has_single_connection import has_single_connection
from faebryk.library.is_representable_by_single_value import is_representable_by_single_value
Expand Down Expand Up @@ -72,6 +72,7 @@
from faebryk.library.has_pcb_layout_defined import has_pcb_layout_defined
from faebryk.library.has_single_connection_impl import has_single_connection_impl
from faebryk.library.is_representable_by_single_value_defined import is_representable_by_single_value_defined
from faebryk.library.Symbol import Symbol
from faebryk.library.XtalIF import XtalIF
from faebryk.library.has_pin_association_heuristic import has_pin_association_heuristic
from faebryk.library.Common_Mode_Filter import Common_Mode_Filter
Expand All @@ -87,6 +88,7 @@
from faebryk.library.Pad import Pad
from faebryk.library.Button import Button
from faebryk.library.GDT import GDT
from faebryk.library.has_symbol_layout import has_symbol_layout
from faebryk.library.has_pin_association_heuristic_lookup_table import has_pin_association_heuristic_lookup_table
from faebryk.library.LogicGate import LogicGate
from faebryk.library.has_footprint_defined import has_footprint_defined
Expand All @@ -95,6 +97,7 @@
from faebryk.library.has_equal_pins import has_equal_pins
from faebryk.library.has_kicad_manual_footprint import has_kicad_manual_footprint
from faebryk.library.has_pcb_routing_strategy_greedy_direct_line import has_pcb_routing_strategy_greedy_direct_line
from faebryk.library.has_symbol_layout_defined import has_symbol_layout_defined
from faebryk.library.BJT import BJT
from faebryk.library.Diode import Diode
from faebryk.library.MOSFET import MOSFET
Expand Down
8 changes: 8 additions & 0 deletions src/faebryk/library/has_symbol_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file is part of the faebryk project
# SPDX-License-Identifier: MIT

import faebryk.library._F as F


class has_symbol_layout(F.Symbol.TraitT):
translations: str
10 changes: 10 additions & 0 deletions src/faebryk/library/has_symbol_layout_defined.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file is part of the faebryk project
# SPDX-License-Identifier: MIT

import faebryk.library._F as F


class has_symbol_layout_defined(F.has_symbol_layout.impl()):
def __init__(self, translations: str = ""):
super().__init__()
self.translations = translations
87 changes: 66 additions & 21 deletions src/faebryk/libs/kicad/fileformats_sch.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
"""
This module contains a dataclass representation of the KiCad file formats
for schematic files (.kicad_sch).

Rules:
- Defaults are only allowed only with the values KiCAD itself defaults to
"""

import logging
from dataclasses import dataclass, field
from enum import auto
from typing import Optional

from faebryk.exporters.pcb.kicad.transformer import gen_uuid
from faebryk.libs.kicad.fileformats_common import (
UUID,
C_effects,
Expand All @@ -17,6 +26,10 @@
# TODO find complete examples of the fileformats, maybe in the kicad repo


def uuid_field():
return field(default_factory=gen_uuid)


@dataclass
class C_property:
name: str = field(**sexp_field(positional=True))
Expand All @@ -33,19 +46,24 @@ class C_fill:
class E_type(SymEnum):
background = "background"
none = "none"
outline = "outline"

type: E_type = field(default=E_type.background)


@dataclass
class C_stroke:
"""
KiCAD default: (stroke (width 0) (type default) (color 0 0 0 0))
"""

class E_type(SymEnum):
solid = auto()
default = auto()

width: float
type: E_type
color: tuple[int, int, int, int]
width: float = 0
type: E_type = E_type.default
color: tuple[int, int, int, int] = (0, 0, 0, 0)


@dataclass(kw_only=True)
Expand Down Expand Up @@ -185,28 +203,34 @@ class C_power:
pin_names: Optional[C_pin_names] = None
in_bom: Optional[bool] = None
on_board: Optional[bool] = None
symbols: list[C_symbol] = field(
**sexp_field(multidict=True), default_factory=list
symbols: dict[str, C_symbol] = field(
**sexp_field(multidict=True, key=lambda x: x.name),
default_factory=dict,
)
convert: Optional[int] = None

symbol: dict[str, C_symbol] = field(
symbols: dict[str, C_symbol] = field(
**sexp_field(multidict=True, key=lambda x: x.name), default_factory=dict
)

@dataclass
class C_symbol_instance:
"""
TODO: Confusingly name, this isn't the class of
"symbol_instances" at the top-level of the .kicad_sch file
"""

@dataclass
class C_pin:
uuid: UUID
pin: str = field(**sexp_field(positional=True))
name: str = field(**sexp_field(positional=True))
uuid: UUID = uuid_field()

lib_id: str
uuid: UUID
at: C_xyr
unit: int
in_bom: bool
on_board: bool
in_bom: bool = False
on_board: bool = False
uuid: UUID = uuid_field()
fields_autoplaced: bool = True
propertys: dict[str, C_property] = field(
**sexp_field(multidict=True, key=lambda x: x.name),
Expand All @@ -222,20 +246,20 @@ class C_junction:
at: C_xy
diameter: float
color: tuple[int, int, int, int]
uuid: UUID
uuid: UUID = uuid_field()

@dataclass
class C_wire:
pts: C_pts
stroke: C_stroke
uuid: UUID
uuid: UUID = uuid_field()

@dataclass
class C_text:
at: C_xyr
effects: C_effects
uuid: UUID
text: str = field(**sexp_field(positional=True))
uuid: UUID = uuid_field()

@dataclass
class C_sheet:
Expand All @@ -251,15 +275,15 @@ class E_type(SymEnum):

at: C_xyr
effects: C_effects
uuid: UUID
name: str = field(**sexp_field(positional=True))
type: E_type = field(**sexp_field(positional=True))
uuid: UUID = uuid_field()

at: C_xy
size: C_xy
stroke: C_stroke
fill: C_fill
uuid: UUID
uuid: UUID = uuid_field()
fields_autoplaced: bool = True
propertys: dict[str, C_property] = field(
**sexp_field(multidict=True, key=lambda x: x.name),
Expand All @@ -286,8 +310,8 @@ class E_shape(SymEnum):
shape: E_shape
at: C_xyr
effects: C_effects
uuid: UUID
text: str = field(**sexp_field(positional=True))
uuid: UUID = uuid_field()
fields_autoplaced: bool = True
propertys: dict[str, C_property] = field(
**sexp_field(multidict=True, key=lambda x: x.name),
Expand All @@ -305,26 +329,26 @@ class E_shape(SymEnum):
class C_label:
at: C_xyr
effects: C_effects
uuid: UUID
text: str = field(**sexp_field(positional=True))
uuid: UUID = uuid_field()

@dataclass
class C_bus:
pts: C_pts
stroke: C_stroke
uuid: UUID
uuid: UUID = uuid_field()

@dataclass
class C_bus_entry:
at: C_xy
size: C_xy
stroke: C_stroke
uuid: UUID
uuid: UUID = uuid_field()

version: int = field(**sexp_field(assert_value=20211123))
generator: str
uuid: UUID
paper: str
uuid: UUID = uuid_field()
lib_symbols: C_lib_symbols = field(default_factory=C_lib_symbols)
title_block: C_title_block = field(default_factory=C_title_block)

Expand Down Expand Up @@ -354,4 +378,25 @@ class C_bus_entry:
**sexp_field(multidict=True), default_factory=list
)

# TODO: "symbol_instances" may or may not be required?
# It appears to be a list of all symbols used in the schematic
# But it also appears to be a translation of "symbols"

kicad_sch: C_kicad_sch


@dataclass
class C_kicad_sym_file(SEXP_File):
"""
When in doubt check: kicad/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp
"""

@dataclass
class C_kicad_symbol_lib:
version: int
generator: str
symbols: dict[str, C_kicad_sch_file.C_kicad_sch.C_lib_symbols.C_symbol] = field(
**sexp_field(multidict=True, key=lambda x: x.name), default_factory=dict
)

kicad_symbol_lib: C_kicad_symbol_lib
1 change: 1 addition & 0 deletions src/faebryk/libs/library/L.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
list_field,
rt_field,
)
from faebryk.core.reference import reference # noqa: F401


class AbstractclassError(Exception): ...
Expand Down
20 changes: 16 additions & 4 deletions src/faebryk/libs/picker/lcsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from easyeda2kicad.kicad.export_kicad_3d_model import Exporter3dModelKicad
from easyeda2kicad.kicad.export_kicad_footprint import ExporterFootprintKicad
from easyeda2kicad.kicad.export_kicad_symbol import ExporterSymbolKicad, KicadVersion

import faebryk.library._F as F
from faebryk.core.module import Module
Expand Down Expand Up @@ -112,17 +113,21 @@ def download_easyeda_info(partno: str, get_model: bool = True):
# paths -------------------------------------------------------------------
name = easyeda_footprint.info.name
out_base_path = LIB_FOLDER
fp_base_path = out_base_path.joinpath("footprints/lcsc.pretty")
fp_base_path = out_base_path / "footprints" / "lcsc.pretty"
sym_base_path = out_base_path / "lcsc.kicad_sym"
fp_base_path.mkdir(exist_ok=True, parents=True)
footprint_filename = f"{name}.kicad_mod"
footprint_filepath = fp_base_path.joinpath(footprint_filename)

model_base_path = out_base_path.joinpath("3dmodels/lcsc")
model_base_path_full = Path(model_base_path.as_posix() + ".3dshapes")
# The base_path has to be split from the full path, because the exporter
# will append .3dshapes to it
model_base_path = out_base_path / "3dmodels" / "lcsc"
model_base_path_full = model_base_path.with_suffix(".3dshapes")
model_base_path_full.mkdir(exist_ok=True, parents=True)

# export to kicad ---------------------------------------------------------
ki_footprint = ExporterFootprintKicad(easyeda_footprint)
ki_symbol = ExporterSymbolKicad(easyeda_symbol, KicadVersion.v6)

_fix_3d_model_offsets(ki_footprint)

Expand All @@ -135,7 +140,7 @@ def download_easyeda_info(partno: str, get_model: bool = True):
ki_model = Exporter3dModelKicad(easyeda_model)

if easyeda_model is not None:
model_path = model_base_path_full.joinpath(f"{easyeda_model.name}.wrl")
model_path = model_base_path_full / f"{easyeda_model.name}.wrl"
if get_model and not model_path.exists():
logger.debug(f"Downloading & Exporting 3dmodel {model_path}")
easyeda_model = Easyeda3dModelImporter(
Expand All @@ -162,6 +167,10 @@ def download_easyeda_info(partno: str, get_model: bool = True):
model_3d_path=kicad_model_path,
)

if not sym_base_path.exists():
logger.debug(f"Exporting symbol {sym_base_path}")
ki_symbol.export(str(sym_base_path))

return ki_footprint, ki_model, easyeda_footprint, easyeda_model, easyeda_symbol


Expand Down Expand Up @@ -194,6 +203,9 @@ def attach(component: Module, partno: str, get_model: bool = True):
raise LCSC_PinmapException(partno, f"Failed to get pinmap: {e}") from e
component.add(F.can_attach_to_footprint_via_pinmap(pinmap))

sym = F.Symbol.with_component(component, pinmap)
sym.add(F.Symbol.has_kicad_symbol(f"lcsc:{easyeda_footprint.info.name}"))

# footprint
fp = F.KicadFootprint(
f"lcsc:{easyeda_footprint.info.name}",
Expand Down
Loading
Loading