Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,13 @@ target/

# Record of pcbnew module location
kigadgets/.path_to_pcbnew_module

# Virtual environments
venv/
.venv/

# OSx things
.DS_Store

# IDEs
.vscode/
9 changes: 9 additions & 0 deletions greeting-server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# saved as greeting-server.py
import Pyro5.api
import pathlib

daemon = Pyro5.api.Daemon() # make a Pyro daemon
uri = daemon.register(Pyro5.api.expose(pathlib.Path)) # register the greeting maker as a Pyro object

print("Ready. Object uri =", uri) # print the uri so we can use it in the client later
daemon.requestLoop() # start the event loop of the server to wait for calls
6 changes: 6 additions & 0 deletions kigadgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
from kigadgets.environment import get_pcbnew_module
from kigadgets.util import notify, query_user, kireload
from kigadgets.exceptions import put_import_warning_on_kicad
import logging
from pathlib import Path

logging.basicConfig(level=logging.DEBUG, handlers=[logging.FileHandler(Path("~/kigadgets.log").expanduser())])

log = logging.getLogger(__name__)

# Find SWIG pcbnew
try:
Expand Down
118 changes: 79 additions & 39 deletions kigadgets/board.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
from kigadgets import pcbnew_bare as pcbnew
from kigadgets import units, SWIGtype, instanceof
import logging
import tempfile
from typing import Iterable, Optional, Union

from Pyro5.api import expose

from kigadgets.drawing import wrap_drawing, Segment, Circle, Arc, TextPCB
from kigadgets import SWIGtype, instanceof
from kigadgets import pcbnew_bare as pcbnew
from kigadgets import units
from kigadgets.drawing import Arc, Circle, Segment, TextPCB, wrap_drawing
from kigadgets.module import Footprint
from kigadgets.track import Track
from kigadgets.util import register_return, register_yielded
from kigadgets.via import Via
from kigadgets.zone import Zone

log = logging.getLogger(__name__)


COPPER_TYPES = Union[Footprint, Track, Via, Zone]
DRAWING_TYPES = Union[Arc, Circle, Segment, TextPCB]

class _FootprintList(object):
@expose
class FootprintList(object):
"""Internal class to represent `Board.footprints`"""
def __init__(self, board):
self._board = board
Expand All @@ -34,27 +46,32 @@ def __len__(self):
return len(self._board._obj.GetFootprints())


@expose
class Board(object):
def __init__(self, wrap=None):
"""Board object"""
log.debug("Creating board from: %s", wrap)
if wrap:
self._obj = wrap
else:
self._obj = pcbnew.BOARD()
log.debug("Board object created: %s", self._obj)

self._fplist = _FootprintList(self)
self._fplist = FootprintList(self)
self._removed_elements = []

@property
def native_obj(self):
def native_obj(self) -> "pcbnew.BOARD":
return self._obj

@staticmethod
def wrap(instance):
@classmethod
@register_return
def wrap(cls, instance: "pcbnew.BOARD") -> "Board":
"""Wraps a C++/old api `BOARD` object, and returns a `Board`."""
return Board(wrap=instance)

def add(self, obj):
@register_return
def add(self, obj: COPPER_TYPES) -> COPPER_TYPES:
"""Adds an object to the Board.

Tracks, Drawings, Modules, etc...
Expand All @@ -63,28 +80,33 @@ def add(self, obj):
return obj

@property
def footprints(self):
@register_return
def footprints(self) -> Iterable[Footprint]:
"""Provides an iterator over the board Footprint objects."""
return self._fplist

def footprint_by_ref(self, ref):
@register_return
def footprint_by_ref(self, ref) -> Optional[Footprint]:
"""Returns the footprint that has the reference `ref`. Returns `None` if
there is no such footprint."""
found = self._obj.FindFootprintByReference(ref)
if found:
return Footprint.wrap(found)

@property
def modules(self):
@register_return
def modules(self) -> Iterable[Footprint]:
"""Alias footprint to module"""
return self.footprints

def module_by_ref(self, ref):
@register_return
def module_by_ref(self, ref) -> Optional[Footprint]:
"""Alias footprint to module"""
return self.footprintByRef(ref)

@property
def vias(self):
@register_yielded
def vias(self) -> Iterable[Via]:
"""An iterator over via objects"""
for t in self._obj.GetTracks():
if type(t) == SWIGtype.Via:
Expand All @@ -93,7 +115,8 @@ def vias(self):
continue

@property
def tracks(self):
@register_yielded
def tracks(self) -> Iterable[Track]:
"""An iterator over track objects"""
for t in self._obj.GetTracks():
if type(t) == SWIGtype.Track:
Expand All @@ -102,7 +125,8 @@ def tracks(self):
continue

@property
def zones(self):
@register_yielded
def zones(self) -> Iterable[Zone]:
""" An iterator over zone objects
Implementation note: The iterator breaks if zones are removed during the iteration,
so it is put in a list first, then yielded from that list.
Expand All @@ -118,13 +142,15 @@ def zones(self):
yield tt

@property
def drawings(self):
@register_yielded
def drawings(self) -> Iterable[DRAWING_TYPES]:
all_drawings = []
for drawing in self._obj.GetDrawings():
yield wrap_drawing(drawing)

@property
def items(self):
@register_yielded
def items(self) -> Iterable[Union[COPPER_TYPES, DRAWING_TYPES]]:
''' Everything on the board '''
for item in self.modules:
yield item
Expand All @@ -137,13 +163,15 @@ def items(self):
for item in self.drawings:
yield item

@staticmethod
def from_editor():
@classmethod
@register_return
def from_editor(cls):
"""Provides the board object from the editor."""
return Board.wrap(pcbnew.GetBoard())

@staticmethod
def load(filename):
@classmethod
@register_return
def load(cls, filename):
"""Loads a board file."""
return Board.wrap(pcbnew.LoadBoard(filename))

Expand All @@ -156,6 +184,7 @@ def save(self, filename=None):
filename = self._obj.GetFileName()
self._obj.Save(filename)

@register_return
def copy(self):
native = self._obj.Clone()
if native is None: # Clone not implemented in v7
Expand All @@ -166,11 +195,12 @@ def copy(self):

# TODO: add setter for Board.filename. For now, use brd.save(filename)
@property
def filename(self):
def filename(self) -> str:
"""Name of the board file."""
log.debug("repr(Board:self._obj): %s", self._obj)
return self._obj.GetFileName()

def geohash(self):
def geohash(self) -> int:
''' Geometric hash '''
item_hashes = []
for item in self.items:
Expand All @@ -181,29 +211,32 @@ def geohash(self):
item_hashes.sort()
return hash(tuple(item_hashes))

def add_footprint(self, ref, pos=(0, 0)):
@register_return
def add_footprint(self, ref, pos=(0, 0)) -> Footprint:
"""Create new module on the board"""
return Footprint(ref, pos, board=self)

def add_module(self, ref, pos=(0, 0)):
@register_return
def add_module(self, ref, pos=(0, 0)) -> Footprint:
"""Same as add_footprint"""
return Footprint(ref, pos, board=self)

@property
def default_width(self, width=None):
def default_width(self, width=None) -> float:
b = self._obj
return (
float(b.GetDesignSettings().GetCurrentTrackWidth()) /
units.DEFAULT_UNIT_IUS)

def add_track_segment(self, start, end, layer='F.Cu', width=None):
@register_return
def add_track_segment(self, start, end, layer='F.Cu', width=None) -> Track:
"""Create a track segment."""
track = Track(start, end, layer,
width or self.default_width, board=self)
self._obj.Add(track.native_obj)
return track

def get_layer_id(self, name):
def get_layer_id(self, name) -> int:
lid = self._obj.GetLayerID(name)
if lid == -1:
# Try to recover from silkscreen rename
Expand All @@ -219,10 +252,11 @@ def get_layer_id(self, name):
raise ValueError('Layer {} not found in this board'.format(name))
return lid

def get_layer_name(self, layer_id):
def get_layer_name(self, layer_id) -> str:
return self._obj.GetLayerName(layer_id)

def add_track(self, coords, layer='F.Cu', width=None):
@register_return
def add_track(self, coords, layer='F.Cu', width=None) -> Track:
"""Create a track polyline.

Create track segments from each coordinate to the next.
Expand All @@ -232,19 +266,20 @@ def add_track(self, coords, layer='F.Cu', width=None):
layer=layer, width=width)

@property
def default_via_size(self):
def default_via_size(self) -> float:
return (float(self._obj.GetDesignSettings().GetCurrentViaSize()) /
units.DEFAULT_UNIT_IUS)

@property
def default_via_drill(self):
def default_via_drill(self) -> float:
via_drill = self._obj.GetDesignSettings().GetCurrentViaDrill()
if via_drill > 0:
return (float(via_drill) / units.DEFAULT_UNIT_IUS)
else:
return 0.2

def add_via(self, coord, size=None, drill=None, layer_pair=None):
@register_return
def add_via(self, coord, size=None, drill=None, layer_pair=None) -> Via:
"""Create a via on the board.

:param coord: Position of the via.
Expand All @@ -258,7 +293,8 @@ def add_via(self, coord, size=None, drill=None, layer_pair=None):
Via(coord, size or self.default_via_size,
drill or self.default_via_drill, layer_pair, board=self))

def add_line(self, start, end, layer='F.SilkS', width=0.15):
@register_return
def add_line(self, start, end, layer='F.SilkS', width=0.15) -> Segment:
"""Create a graphic line on the board"""
return self.add(
Segment(start, end, layer, width, board=self))
Expand All @@ -268,19 +304,22 @@ def add_polyline(self, coords, layer='F.SilkS', width=0.15):
for n in range(len(coords) - 1):
self.add_line(coords[n], coords[n + 1], layer=layer, width=width)

def add_circle(self, center, radius, layer='F.SilkS', width=0.15):
@register_return
def add_circle(self, center, radius, layer='F.SilkS', width=0.15) -> Circle:
"""Create a graphic circle on the board"""
return self.add(
Circle(center, radius, layer, width, board=self))

@register_return
def add_arc(self, center, radius, start_angle, stop_angle,
layer='F.SilkS', width=0.15):
layer='F.SilkS', width=0.15) -> Arc:
"""Create a graphic arc on the board"""
return self.add(
Arc(center, radius, start_angle, stop_angle,
layer, width, board=self))

def add_text(self, position, text, layer='F.SilkS', size=1.0, thickness=0.15):
@register_return
def add_text(self, position, text, layer='F.SilkS', size=1.0, thickness=0.15) -> TextPCB:
return self.add(
TextPCB(position, text, layer, size, thickness, board=self))

Expand All @@ -303,7 +342,8 @@ def deselect_all(self):
self._obj.ClearSelected()

@property
def selected_items(self):
@register_yielded
def selected_items(self) -> Iterable[Union[COPPER_TYPES, DRAWING_TYPES]]:
''' This useful for duck typing in the interactive terminal
Suppose you want to set some drill radii. Iterating everything would cause attribute errors,
so it is easier to just select the vias you want, then use this method for convenience.
Expand Down
Loading