From 2629ff5142864dfaf1fb8a62d6fd921cbec544b9 Mon Sep 17 00:00:00 2001 From: Matthew Wildoer Date: Tue, 30 Jan 2024 16:07:09 -0800 Subject: [PATCH 1/5] checkpoint --- .gitignore | 7 +++++++ greeting-server.py | 9 +++++++++ kigadgets/__init__.py | 6 ++++++ kigadgets/board.py | 37 ++++++++++++++++++++++++++++--------- kigadgets/remote.py | 27 +++++++++++++++++++++++++++ kigadgets/util.py | 21 +++++++++++++++++++++ 6 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 greeting-server.py create mode 100644 kigadgets/remote.py diff --git a/.gitignore b/.gitignore index c0d9831..414ece0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,10 @@ target/ # Record of pcbnew module location kigadgets/.path_to_pcbnew_module + +# Virtual environments +venv/ +.venv/ + +# OSx things +.DS_Store diff --git a/greeting-server.py b/greeting-server.py new file mode 100644 index 0000000..f5223c7 --- /dev/null +++ b/greeting-server.py @@ -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 diff --git a/kigadgets/__init__.py b/kigadgets/__init__.py index 1563a0b..639a6f1 100644 --- a/kigadgets/__init__.py +++ b/kigadgets/__init__.py @@ -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: diff --git a/kigadgets/board.py b/kigadgets/board.py index cf8ed77..6460cf8 100644 --- a/kigadgets/board.py +++ b/kigadgets/board.py @@ -1,3 +1,6 @@ +from kigadgets.util import register_return +from Pyro5.api import expose + from kigadgets import pcbnew_bare as pcbnew from kigadgets import units, SWIGtype, instanceof import tempfile @@ -8,8 +11,13 @@ from kigadgets.via import Via from kigadgets.zone import Zone +import logging + +log = logging.getLogger(__name__) + -class _FootprintList(object): +@expose +class FootprintList(object): """Internal class to represent `Board.footprints`""" def __init__(self, board): self._board = board @@ -34,26 +42,30 @@ 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): return self._obj - @staticmethod - def wrap(instance): + @classmethod + def wrap(cls, instance): """Wraps a C++/old api `BOARD` object, and returns a `Board`.""" return Board(wrap=instance) + @register_return def add(self, obj): """Adds an object to the Board. @@ -63,10 +75,12 @@ def add(self, obj): return obj @property + @register_return def footprints(self): """Provides an iterator over the board Footprint objects.""" return self._fplist + @register_return def footprint_by_ref(self, ref): """Returns the footprint that has the reference `ref`. Returns `None` if there is no such footprint.""" @@ -75,10 +89,12 @@ def footprint_by_ref(self, ref): return Footprint.wrap(found) @property + @register_return def modules(self): """Alias footprint to module""" return self.footprints + @register_return def module_by_ref(self, ref): """Alias footprint to module""" return self.footprintByRef(ref) @@ -137,13 +153,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)) @@ -166,8 +184,9 @@ 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): diff --git a/kigadgets/remote.py b/kigadgets/remote.py new file mode 100644 index 0000000..3d04093 --- /dev/null +++ b/kigadgets/remote.py @@ -0,0 +1,27 @@ +""" +Start a server to remote control KiCAD. +""" + +import threading + +from Pyro5.api import serve +from .board import Board +import logging + +log = logging.getLogger(__name__) + + +def _run_server(): + log.info("Starting Pyro5 server") + serve( + { + Board: "kigadgets.Board", + } + ) + + +def start_server() -> threading.Thread: + """Start a pryo5 server to remote control KiCAD.""" + # NOTE: this seems to immediately kill KiCAD if + # run as a daemon thread + return threading.Thread(target=_run_server).start() diff --git a/kigadgets/util.py b/kigadgets/util.py index 4acd46a..c0fcae2 100644 --- a/kigadgets/util.py +++ b/kigadgets/util.py @@ -6,6 +6,8 @@ def run(self): import action_script # Only runs the first time during this instance of pcbnew, even if file changed kireload(action_script) # Forces reimport, rerunning, and any updates to source ''' +from functools import wraps + try: from importlib import reload as kireload except ImportError: @@ -65,3 +67,22 @@ def query_user(prompt=None, default=''): if sg != wx.ID_OK: return None return dialog.GetValue() + + +def has_pyro_id(obj_or_class) -> bool: + return ( + hasattr(obj_or_class, "_pyroId") and + obj_or_class._pyroId != "" + ) + +def register_return(method): + """Decorator to register the return value + of a method in the Pyro daemon.""" + @wraps(method) + def wrapper(self, *args, **kwargs): + result = method(self, *args, **kwargs) + if not has_pyro_id(result): + self._pyroDaemon.register(result) + return result + + return wrapper From bafe3c37149d9167ecd2c466a5bdaa242d567e9d Mon Sep 17 00:00:00 2001 From: Matthew Wildoer Date: Tue, 30 Jan 2024 17:34:29 -0800 Subject: [PATCH 2/5] Forceably register objects that aren't yet registered --- kigadgets/util.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/kigadgets/util.py b/kigadgets/util.py index c0fcae2..bd8aaf8 100644 --- a/kigadgets/util.py +++ b/kigadgets/util.py @@ -69,20 +69,18 @@ def query_user(prompt=None, default=''): return dialog.GetValue() -def has_pyro_id(obj_or_class) -> bool: - return ( - hasattr(obj_or_class, "_pyroId") and - obj_or_class._pyroId != "" - ) - def register_return(method): """Decorator to register the return value of a method in the Pyro daemon.""" @wraps(method) def wrapper(self, *args, **kwargs): + daemon = self._pyroDaemon result = method(self, *args, **kwargs) - if not has_pyro_id(result): - self._pyroDaemon.register(result) + if pyro_id := getattr(result, "_pyroId", None): + if daemon.objectsById[pyro_id] is not result: + daemon.register(result, force=True) + else: + daemon.register(result) return result return wrapper From d5927feabb759ced53660f7afcf6d5f43d28c269 Mon Sep 17 00:00:00 2001 From: Matthew Wildoer Date: Tue, 30 Jan 2024 22:49:58 -0800 Subject: [PATCH 3/5] Making progress - hitched on yielding things --- .gitignore | 3 +++ kigadgets/board.py | 3 ++- kigadgets/util.py | 29 ++++++++++++++++++++++++----- kigadgets/via.py | 4 ++++ scratchpad.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ server_scratchpad.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 scratchpad.py create mode 100644 server_scratchpad.py diff --git a/.gitignore b/.gitignore index 414ece0..ff2a2b9 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ venv/ # OSx things .DS_Store + +# IDEs +.vscode/ diff --git a/kigadgets/board.py b/kigadgets/board.py index 6460cf8..057cbb3 100644 --- a/kigadgets/board.py +++ b/kigadgets/board.py @@ -1,4 +1,4 @@ -from kigadgets.util import register_return +from kigadgets.util import register_return, register_yielded from Pyro5.api import expose from kigadgets import pcbnew_bare as pcbnew @@ -100,6 +100,7 @@ def module_by_ref(self, ref): return self.footprintByRef(ref) @property + @register_yielded def vias(self): """An iterator over via objects""" for t in self._obj.GetTracks(): diff --git a/kigadgets/util.py b/kigadgets/util.py index bd8aaf8..91e0c66 100644 --- a/kigadgets/util.py +++ b/kigadgets/util.py @@ -7,6 +7,7 @@ def run(self): kireload(action_script) # Forces reimport, rerunning, and any updates to source ''' from functools import wraps +from Pyro5.errors import DaemonError try: from importlib import reload as kireload @@ -69,6 +70,16 @@ def query_user(prompt=None, default=''): return dialog.GetValue() +def _do_register(daemon, result): + """Registers the result in the Pyro daemon + if it's not already there.""" + if pyro_id := getattr(result, "_pyroId", None): + if daemon.objectsById[pyro_id] is not result: + daemon.register(result, force=True) + else: + daemon.register(result) + + def register_return(method): """Decorator to register the return value of a method in the Pyro daemon.""" @@ -76,11 +87,19 @@ def register_return(method): def wrapper(self, *args, **kwargs): daemon = self._pyroDaemon result = method(self, *args, **kwargs) - if pyro_id := getattr(result, "_pyroId", None): - if daemon.objectsById[pyro_id] is not result: - daemon.register(result, force=True) - else: - daemon.register(result) + _do_register(daemon, result) return result return wrapper + + +def register_yielded(method): + """Decorator to register the return value + of a method in the Pyro daemon.""" + @wraps(method) + def wrapper(self, *args, **kwargs): + daemon = self._pyroDaemon + for result in method(self, *args, **kwargs): + _do_register(daemon, result) + yield result + return wrapper diff --git a/kigadgets/via.py b/kigadgets/via.py index d21d9e8..f0c3943 100644 --- a/kigadgets/via.py +++ b/kigadgets/via.py @@ -1,3 +1,6 @@ +from kigadgets.util import register_return +from Pyro5.api import expose + from kigadgets import pcbnew_bare as pcbnew from kigadgets import SWIGtype, SWIG_version, Point, DEFAULT_UNIT_IUS @@ -16,6 +19,7 @@ class ViaType(): Blind = pcbnew.VIA_BLIND_BURIED +@expose class Via(HasPosition, HasConnection, Selectable, BoardItem): ''' Careful setting top_layer, then getting top_layer may return different values if the new top_layer is below the existing bottom layer diff --git a/scratchpad.py b/scratchpad.py new file mode 100644 index 0000000..49ebcec --- /dev/null +++ b/scratchpad.py @@ -0,0 +1,44 @@ +# %% +import Pyro5.api +import Pyro5.errors +import sys + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from kigadgets.board import Board + +# %% +import Pyro5.api + + +uri = "PYRONAME:kigadgets.Board" + +board = Pyro5.api.Proxy(uri) + +# # %% +# try: +# for results in dummy.thingos: +# print(results) +# except Exception: +# print("Pyro traceback:") +# print("".join(Pyro5.errors.get_pyro_traceback())) + +# %% +try: + b: "Board" = board.from_editor() + for via in b.vias: + print(via) + # print(b.filename) +except Exception: + print("Pyro traceback:") + print("".join(Pyro5.errors.get_pyro_traceback())) + +# %% +uri = "PYRONAME:dummy" + +dummy = Pyro5.api.Proxy(uri) + +for results in dummy.thingos: + print(results.value) +# %% diff --git a/server_scratchpad.py b/server_scratchpad.py new file mode 100644 index 0000000..5d11c89 --- /dev/null +++ b/server_scratchpad.py @@ -0,0 +1,29 @@ +from Pyro5.api import expose, serve +from kigadgets.util import register_yielded + + +import debugpy +debugpy.listen(5678) +debugpy.wait_for_client() + + +@expose +class Returned(object): + def __init__(self, value) -> None: + self._value = value + + @property + def value(self): + return self._value + + +@expose +class Dummy(object): + @property + @register_yielded + def thingos(self): + for i in range(3): + yield Returned(i) + + +serve({Dummy: "dummy"}) From 0a28120b878bd3195ed392631c29770a71c7fa32 Mon Sep 17 00:00:00 2001 From: Matthew Wildoer Date: Wed, 31 Jan 2024 11:58:43 -0800 Subject: [PATCH 4/5] Can't get refresh working :( --- kigadgets/board.py | 90 +++++++++++++++++++++++++++----------------- kigadgets/drawing.py | 21 ++++++++--- kigadgets/point.py | 4 ++ kigadgets/remote.py | 32 ++++++++++++---- scratchpad.py | 35 ++++++++++------- server_scratchpad.py | 6 ++- 6 files changed, 127 insertions(+), 61 deletions(-) diff --git a/kigadgets/board.py b/kigadgets/board.py index 057cbb3..a61f292 100644 --- a/kigadgets/board.py +++ b/kigadgets/board.py @@ -1,21 +1,25 @@ -from kigadgets.util import register_return, register_yielded +import logging +import tempfile +from typing import Iterable, Optional, Union + from Pyro5.api import expose +from kigadgets import SWIGtype, instanceof from kigadgets import pcbnew_bare as pcbnew -from kigadgets import units, SWIGtype, instanceof -import tempfile - -from kigadgets.drawing import wrap_drawing, Segment, Circle, Arc, TextPCB +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 -import logging - log = logging.getLogger(__name__) +COPPER_TYPES = Union[Footprint, Track, Via, Zone] +DRAWING_TYPES = Union[Arc, Circle, Segment, TextPCB] + @expose class FootprintList(object): """Internal class to represent `Board.footprints`""" @@ -57,16 +61,17 @@ def __init__(self, wrap=None): self._removed_elements = [] @property - def native_obj(self): + def native_obj(self) -> "pcbnew.BOARD": return self._obj @classmethod - def wrap(cls, instance): + @register_return + def wrap(cls, instance: "pcbnew.BOARD") -> "Board": """Wraps a C++/old api `BOARD` object, and returns a `Board`.""" return Board(wrap=instance) @register_return - def add(self, obj): + def add(self, obj: COPPER_TYPES) -> COPPER_TYPES: """Adds an object to the Board. Tracks, Drawings, Modules, etc... @@ -76,12 +81,12 @@ def add(self, obj): @property @register_return - def footprints(self): + def footprints(self) -> Iterable[Footprint]: """Provides an iterator over the board Footprint objects.""" return self._fplist @register_return - def footprint_by_ref(self, ref): + 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) @@ -90,18 +95,18 @@ def footprint_by_ref(self, ref): @property @register_return - def modules(self): + def modules(self) -> Iterable[Footprint]: """Alias footprint to module""" return self.footprints @register_return - def module_by_ref(self, ref): + def module_by_ref(self, ref) -> Optional[Footprint]: """Alias footprint to module""" return self.footprintByRef(ref) @property @register_yielded - def vias(self): + def vias(self) -> Iterable[Via]: """An iterator over via objects""" for t in self._obj.GetTracks(): if type(t) == SWIGtype.Via: @@ -110,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: @@ -119,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. @@ -135,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 @@ -175,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 @@ -190,7 +200,7 @@ def filename(self) -> str: 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: @@ -201,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 @@ -239,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. @@ -252,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. @@ -278,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)) @@ -288,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)) @@ -323,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. diff --git a/kigadgets/drawing.py b/kigadgets/drawing.py index 5063edb..30d067b 100644 --- a/kigadgets/drawing.py +++ b/kigadgets/drawing.py @@ -1,11 +1,17 @@ -from kigadgets import pcbnew_bare as pcbnew - import cmath import math +from typing import Iterable, Optional, Union + +from Pyro5.api import expose -from kigadgets import units, Size, SWIGtype, SWIG_version, Point, instanceof +from kigadgets import Point, Size, SWIG_version, SWIGtype, instanceof +from kigadgets import pcbnew_bare as pcbnew +from kigadgets import units +from kigadgets.item import (BoardItem, HasLayer, HasPosition, HasWidth, + Selectable, TextEsque) from kigadgets.layer import get_board_layer_id -from kigadgets.item import HasLayer, Selectable, HasPosition, HasWidth, BoardItem, TextEsque +from kigadgets.util import register_return, register_yielded + class ShapeType(): Segment = pcbnew.S_SEGMENT @@ -48,11 +54,13 @@ def wrap_drawing(instance): raise TypeError('Unrecognized shape type on layer {}'.format(layer)) +@expose class Drawing(HasLayer, HasPosition, HasWidth, Selectable, BoardItem): ''' Base class of shape drawings, not including text ''' _wraps_native_cls = SWIGtype.Shape +@expose class Segment(Drawing): def __init__(self, start, end, layer='F.SilkS', width=0.15, board=None): line = SWIGtype.Shape(board and board.native_obj) @@ -89,6 +97,7 @@ def geohash(self): return mine + super().geohash() +@expose class Circle(Drawing): def __init__(self, center, radius, layer='F.SilkS', width=0.15, board=None): @@ -108,7 +117,8 @@ def __init__(self, center, radius, layer='F.SilkS', width=0.15, circle.SetArcStart(start_coord) @property - def center(self): + @register_return + def center(self) -> Point: return Point.wrap(self._obj.GetCenter()) @center.setter @@ -148,6 +158,7 @@ def geohash(self): # --- Logic for Arc changed a lot in version 6, so there are two classes +@expose class Arc_v5(Drawing): def __init__(self, center, radius, start_angle, stop_angle, layer='F.SilkS', width=0.15, board=None): diff --git a/kigadgets/point.py b/kigadgets/point.py index a0e7c0f..9518537 100644 --- a/kigadgets/point.py +++ b/kigadgets/point.py @@ -2,8 +2,12 @@ import kigadgets from kigadgets import units, SWIGtype +from typing import Iterable, Optional, Union +from Pyro5.api import expose +from kigadgets.util import register_return, register_yielded +@expose class Point(units.BaseUnitTuple): def __init__(self, x, y): diff --git a/kigadgets/remote.py b/kigadgets/remote.py index 3d04093..5fb51ee 100644 --- a/kigadgets/remote.py +++ b/kigadgets/remote.py @@ -2,22 +2,40 @@ Start a server to remote control KiCAD. """ +import logging import threading -from Pyro5.api import serve +import Pyro5.server +from Pyro5.api import expose, serve + +# from kigadgets import pcbnew_bare as pcbnew + from .board import Board -import logging log = logging.getLogger(__name__) +@expose +class Pcbnew: + @staticmethod + def refresh(): + """Refresh the board.""" + import pcbnew + pcbnew.Refresh() + + def _run_server(): log.info("Starting Pyro5 server") - serve( - { - Board: "kigadgets.Board", - } - ) + try: + serve( + { + Pcbnew: "kigadgets.Pcbnew", + Board: "kigadgets.Board", + } + ) + except Exception as ex: + log.exception("Pyro5 server failed with exception: %s", ex) + raise def start_server() -> threading.Thread: diff --git a/scratchpad.py b/scratchpad.py index 49ebcec..dca6aab 100644 --- a/scratchpad.py +++ b/scratchpad.py @@ -24,21 +24,30 @@ # print("Pyro traceback:") # print("".join(Pyro5.errors.get_pyro_traceback())) -# %% -try: - b: "Board" = board.from_editor() - for via in b.vias: - print(via) - # print(b.filename) -except Exception: - print("Pyro traceback:") - print("".join(Pyro5.errors.get_pyro_traceback())) +# # %% +# try: +# b: "Board" = board.from_editor() +# for via in b.vias: +# print(via.drill) +# # print(b.filename) +# except Exception: +# print("Pyro traceback:") +# print("".join(Pyro5.errors.get_pyro_traceback())) # %% -uri = "PYRONAME:dummy" +b: "Board" = board.from_editor() +for i in range(10): + b.add_via((50, 50 + i * 5)) -dummy = Pyro5.api.Proxy(uri) +# # %% +# len(list(b.vias)) +# # %% + +# b: "Board" = board.from_editor() +# b.add_via((101, 50)) + +# %% +with Pyro5.api.Proxy("PYRONAME:kigadgets.Pcbnew") as pcbnew: + pcbnew.refresh() -for results in dummy.thingos: - print(results.value) # %% diff --git a/server_scratchpad.py b/server_scratchpad.py index 5d11c89..bed504a 100644 --- a/server_scratchpad.py +++ b/server_scratchpad.py @@ -1,5 +1,5 @@ from Pyro5.api import expose, serve -from kigadgets.util import register_yielded +from kigadgets.util import register_yielded, register_return import debugpy @@ -25,5 +25,9 @@ def thingos(self): for i in range(3): yield Returned(i) + @register_return + def do_something(self, obj: Returned) -> Returned: + return obj + serve({Dummy: "dummy"}) From 915a39a7a71fb0e383c3807a1af3f09889037d4f Mon Sep 17 00:00:00 2001 From: Matthew Wildoer Date: Wed, 31 Jan 2024 12:18:03 -0800 Subject: [PATCH 5/5] Tidy scratch pads --- scratchpad.py | 38 ++++++-------------------------------- server_scratchpad.py | 33 --------------------------------- 2 files changed, 6 insertions(+), 65 deletions(-) delete mode 100644 server_scratchpad.py diff --git a/scratchpad.py b/scratchpad.py index dca6aab..55ccf2f 100644 --- a/scratchpad.py +++ b/scratchpad.py @@ -1,53 +1,27 @@ -# %% +#%% import Pyro5.api import Pyro5.errors -import sys from typing import TYPE_CHECKING +import Pyro5.api + if TYPE_CHECKING: from kigadgets.board import Board -# %% -import Pyro5.api - uri = "PYRONAME:kigadgets.Board" board = Pyro5.api.Proxy(uri) -# # %% -# try: -# for results in dummy.thingos: -# print(results) -# except Exception: -# print("Pyro traceback:") -# print("".join(Pyro5.errors.get_pyro_traceback())) - -# # %% -# try: -# b: "Board" = board.from_editor() -# for via in b.vias: -# print(via.drill) -# # print(b.filename) -# except Exception: -# print("Pyro traceback:") -# print("".join(Pyro5.errors.get_pyro_traceback())) - # %% +# The fun things! + b: "Board" = board.from_editor() for i in range(10): b.add_via((50, 50 + i * 5)) -# # %% -# len(list(b.vias)) -# # %% - -# b: "Board" = board.from_editor() -# b.add_via((101, 50)) +#%% -# %% with Pyro5.api.Proxy("PYRONAME:kigadgets.Pcbnew") as pcbnew: pcbnew.refresh() - -# %% diff --git a/server_scratchpad.py b/server_scratchpad.py deleted file mode 100644 index bed504a..0000000 --- a/server_scratchpad.py +++ /dev/null @@ -1,33 +0,0 @@ -from Pyro5.api import expose, serve -from kigadgets.util import register_yielded, register_return - - -import debugpy -debugpy.listen(5678) -debugpy.wait_for_client() - - -@expose -class Returned(object): - def __init__(self, value) -> None: - self._value = value - - @property - def value(self): - return self._value - - -@expose -class Dummy(object): - @property - @register_yielded - def thingos(self): - for i in range(3): - yield Returned(i) - - @register_return - def do_something(self, obj: Returned) -> Returned: - return obj - - -serve({Dummy: "dummy"})