Compare commits
15 commits
915d4981bd
...
c1194ecee0
Author | SHA1 | Date | |
---|---|---|---|
|
c1194ecee0 | ||
|
cfc3370ad8 | ||
|
613f959781 | ||
|
e81979ad84 | ||
|
ad679b9ae6 | ||
|
f5d3e26a0b | ||
|
d17d97f678 | ||
|
c66b554b46 | ||
|
f1d2800354 | ||
|
09a2daf427 | ||
|
527d493c1b | ||
|
b5a1e8c71d | ||
|
caa3225ef8 | ||
|
e05f3f6a3c | ||
|
8c41e66a1a |
9 changed files with 175 additions and 48 deletions
|
@ -2,6 +2,7 @@ import FreeCAD as App
|
||||||
import FreeCADGui as Gui
|
import FreeCADGui as Gui
|
||||||
|
|
||||||
from kipy import KiCad
|
from kipy import KiCad
|
||||||
|
from kipy.proto.common.types import DocumentType
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .bases import BaseObject, BaseViewProvider
|
from .bases import BaseObject, BaseViewProvider
|
||||||
|
@ -14,6 +15,7 @@ class APIObject(BaseObject):
|
||||||
|
|
||||||
feature.addProperty('App::PropertyFile', 'Socket', 'KiConnect', 'Path to the KiCAD Socket File').Socket = '/tmp/kicad/api.lock'
|
feature.addProperty('App::PropertyFile', 'Socket', 'KiConnect', 'Path to the KiCAD Socket File').Socket = '/tmp/kicad/api.lock'
|
||||||
feature.addProperty('App::PropertyBool', 'Connected', 'KiConnect', 'Is socket connected')
|
feature.addProperty('App::PropertyBool', 'Connected', 'KiConnect', 'Is socket connected')
|
||||||
|
feature.addProperty('App::PropertyInteger', 'DocumentCount', 'KiConnect', 'Count of open Documnets')
|
||||||
|
|
||||||
self.onDocumentRestored(feature)
|
self.onDocumentRestored(feature)
|
||||||
|
|
||||||
|
@ -26,23 +28,49 @@ class APIObject(BaseObject):
|
||||||
parent = feature.getParent()
|
parent = feature.getParent()
|
||||||
if not parent: return
|
if not parent: return
|
||||||
|
|
||||||
|
# XXX This gets all of the KiConnect::Board features but then does nothing with them
|
||||||
|
# future multi-board support?
|
||||||
boards = [ board for board in parent.Group if hasattr(board, 'Type') and board.Type == 'KiConnect::Board' ]
|
boards = [ board for board in parent.Group if hasattr(board, 'Type') and board.Type == 'KiConnect::Board' ]
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
|
'''
|
||||||
|
Returns connection status
|
||||||
|
'''
|
||||||
|
|
||||||
return self.feature.Connected
|
return self.feature.Connected
|
||||||
|
|
||||||
def ping_connection(self, feature):
|
def ping_connection(self, feature):
|
||||||
|
'''
|
||||||
|
Ping the KiCAD API to determine if it's connected
|
||||||
|
'''
|
||||||
|
|
||||||
|
connection_status = 'Disconnected'
|
||||||
|
document_status = 'No Documents'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.kicad.ping()
|
self.kicad.ping()
|
||||||
|
|
||||||
feature.Connected = True
|
feature.Connected = True
|
||||||
feature.Label2 = 'Connected'
|
connection_status = 'Connected'
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
|
||||||
feature.Connected = False
|
feature.Connected = False
|
||||||
feature.Label2 = 'Disconnected'
|
connection_status = 'Disconnected'
|
||||||
pass
|
|
||||||
|
if feature.Connected:
|
||||||
|
try:
|
||||||
|
docs = self.kicad.get_open_documents(DocumentType.DOCTYPE_PCB)
|
||||||
|
document_status = f'{len(docs)} Documents'
|
||||||
|
feature.DocumentCount = len(docs)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
feature.DocumentCount = 0
|
||||||
|
|
||||||
|
feature.Label2 = f'{connection_status} ({document_status})'
|
||||||
|
|
||||||
|
return feature.Connected
|
||||||
|
|
||||||
|
|
||||||
class APIViewProvider(BaseViewProvider):
|
class APIViewProvider(BaseViewProvider):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class BaseObject:
|
class BaseObject:
|
||||||
|
EXTENSIONS = []
|
||||||
TYPE = None
|
TYPE = None
|
||||||
|
|
||||||
def __init__(self, feature):
|
def __init__(self, feature):
|
||||||
|
@ -13,7 +14,8 @@ class BaseObject:
|
||||||
|
|
||||||
def execute(self, feature):
|
def execute(self, feature):
|
||||||
# TODO this might not be the right move
|
# TODO this might not be the right move
|
||||||
self.onDocumentRestored(feature)
|
print(self, 'BaseObject.execute')
|
||||||
|
#self.onDocumentRestored(feature)
|
||||||
|
|
||||||
def get_api(self):
|
def get_api(self):
|
||||||
p = self.feature
|
p = self.feature
|
||||||
|
@ -25,6 +27,16 @@ class BaseObject:
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def isChildOf(self, parent):
|
||||||
|
p = self.feature
|
||||||
|
|
||||||
|
while p:
|
||||||
|
if p == parent:
|
||||||
|
return True
|
||||||
|
p = p.getParent()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def onBeforeChange(self, feature, prop):
|
def onBeforeChange(self, feature, prop):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -42,5 +54,11 @@ class BaseObject:
|
||||||
def setup_properties(self, feature):
|
def setup_properties(self, feature):
|
||||||
feature.addProperty('App::PropertyString', 'Type', 'KiConnect', 'Internatl KiConnect Type', read_only=True, hidden=True)
|
feature.addProperty('App::PropertyString', 'Type', 'KiConnect', 'Internatl KiConnect Type', read_only=True, hidden=True)
|
||||||
|
|
||||||
|
def sync_from(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sync_to(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -7,10 +7,11 @@ from .. import settings
|
||||||
class BaseViewProvider:
|
class BaseViewProvider:
|
||||||
ICON = None
|
ICON = None
|
||||||
TYPE = None
|
TYPE = None
|
||||||
VIEWPROVIDER_EXTENSIONS = []
|
EXTENSIONS = []
|
||||||
|
|
||||||
def __init__(self, viewprovider):
|
def __init__(self, viewprovider):
|
||||||
self.viewprovider = viewprovider
|
self.viewprovider = viewprovider
|
||||||
|
self.feature = viewprovider.Object.Proxy.feature
|
||||||
|
|
||||||
self.icon = ''
|
self.icon = ''
|
||||||
|
|
||||||
|
@ -22,8 +23,8 @@ class BaseViewProvider:
|
||||||
self.setup_extensions()
|
self.setup_extensions()
|
||||||
|
|
||||||
def setup_extensions(self):
|
def setup_extensions(self):
|
||||||
if hasattr(self, 'VIEWPROVIDER_EXTENSIONS'):
|
if hasattr(self, 'EXTENSIONS'):
|
||||||
for ext in self.VIEWPROVIDER_EXTENSIONS:
|
for ext in self.EXTENSIONS:
|
||||||
self.feature.addExtension(ext)
|
self.feature.addExtension(ext)
|
||||||
|
|
||||||
def attach(self, vobj):
|
def attach(self, vobj):
|
||||||
|
|
|
@ -19,23 +19,57 @@ class BoardObject(BaseObject):
|
||||||
super(BoardObject, self).__init__(feature)
|
super(BoardObject, self).__init__(feature)
|
||||||
|
|
||||||
self.kicad_board = None
|
self.kicad_board = None
|
||||||
self.substrate_body = None
|
|
||||||
self.substrate_sketch = None
|
|
||||||
self.via_sketch = None
|
self.via_sketch = None
|
||||||
|
|
||||||
feature.addProperty('App::PropertyPlacement', 'BoardOffset', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
|
feature.addProperty('App::PropertyPlacement', 'BoardOffset', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
|
||||||
|
|
||||||
def onDocumentRestored(self, feature):
|
|
||||||
|
def execute(self, feature):
|
||||||
|
super(BoardObject, self).execute(feature)
|
||||||
|
|
||||||
if self.kicad_board is None:
|
if self.kicad_board is None:
|
||||||
self.kicad_board = self.get_api().kicad.get_board()
|
self.kicad_board = self.get_api().kicad.get_board()
|
||||||
|
|
||||||
if self.substrate_body is None:
|
if not self.substrate_body:
|
||||||
self.extrude_substrate(feature)
|
self.create_substrate_body()
|
||||||
self.sketch_outline(feature)
|
|
||||||
|
if not self.substrate_sketch:
|
||||||
|
self.create_substrate_sketch()
|
||||||
|
self.sketch_outline()
|
||||||
|
|
||||||
|
def onDocumentRestored(self, feature):
|
||||||
|
super(BoardObject, self).onDocumentRestored(feature)
|
||||||
|
|
||||||
|
self.kicad_board = self.get_api().kicad.get_board()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def substrate_body(self):
|
||||||
|
return self.feature.getObject('Substrate')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def substrate_sketch(self):
|
||||||
|
return self.substrate_body.getObject('Sketch')
|
||||||
|
|
||||||
|
def create_substrate_body(self):
|
||||||
|
substrate_body = App.ActiveDocument.addObject('PartDesign::Body', 'Substrate')
|
||||||
|
|
||||||
|
substrate_body.addProperty('App::PropertyString', 'Type', 'KiConnect', 'KiConnect specific Type', read_only=True, hidden=True)
|
||||||
|
substrate_body.Type = 'KiConnect::BoardBody'
|
||||||
|
|
||||||
|
self.feature.addObject(substrate_body)
|
||||||
|
|
||||||
|
def create_substrate_sketch(self):
|
||||||
|
substrate_sketch = App.ActiveDocument.addObject('Sketcher::SketchObject', 'Sketch')
|
||||||
|
substrate_sketch.Visibility = False
|
||||||
|
|
||||||
|
self.substrate_body.addObject(substrate_sketch)
|
||||||
|
|
||||||
|
pad = self.substrate_body.newObject('PartDesign::Pad', 'Outline')
|
||||||
|
|
||||||
|
pad.Profile = substrate_sketch
|
||||||
|
pad.Length = 1.6
|
||||||
|
pad.Midplane = True
|
||||||
|
|
||||||
if len(self.kicad_board.get_vias()) > 0 and self.via_sketch is None:
|
|
||||||
self.setup_vias()
|
|
||||||
self.pocket_vias()
|
|
||||||
|
|
||||||
def extrude_substrate(self, feature):
|
def extrude_substrate(self, feature):
|
||||||
self.substrate_sketch = App.ActiveDocument.addObject('Sketcher::SketchObject', 'Sketch')
|
self.substrate_sketch = App.ActiveDocument.addObject('Sketcher::SketchObject', 'Sketch')
|
||||||
|
@ -95,7 +129,14 @@ class BoardObject(BaseObject):
|
||||||
via_pocket.Midplane = True
|
via_pocket.Midplane = True
|
||||||
via_pocket.Type = 1
|
via_pocket.Type = 1
|
||||||
|
|
||||||
def sketch_outline(self, feature):
|
def sketch_outline(self, do_offset=True):
|
||||||
|
'''
|
||||||
|
Draws the Board outline fetched from the API
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
do_offset (bool): If offset should be recalcualted, typically this is undesired after calculated the first time. (Default: True)
|
||||||
|
'''
|
||||||
|
|
||||||
boardpoly = self.get_boardpoly()
|
boardpoly = self.get_boardpoly()
|
||||||
poly = boardpoly.polygons[0]
|
poly = boardpoly.polygons[0]
|
||||||
|
|
||||||
|
@ -104,13 +145,15 @@ class BoardObject(BaseObject):
|
||||||
self.feature.PolygonId = boardpoly.id.value
|
self.feature.PolygonId = boardpoly.id.value
|
||||||
|
|
||||||
# this offset centers the board to 0,0
|
# this offset centers the board to 0,0
|
||||||
bb = boardpoly.bounding_box()
|
if do_offset:
|
||||||
self.feature.BoardOffset.Base = (App.Vector(bb.pos.x, -bb.pos.y) + App.Vector(bb.size.x, -bb.size.y) / 2) / 1000000.0
|
bb = boardpoly.bounding_box()
|
||||||
|
self.feature.BoardOffset.Base = (App.Vector(bb.pos.x, -bb.pos.y) + App.Vector(bb.size.x, -bb.size.y) / 2) / 1000000.0
|
||||||
|
|
||||||
begin = None
|
begin = None
|
||||||
start = None
|
start = None
|
||||||
|
|
||||||
# reset Sketch Geometry
|
# reset Sketch Constraints and Geometry
|
||||||
|
self.substrate_sketch.Constraints = []
|
||||||
self.substrate_sketch.Geometry = []
|
self.substrate_sketch.Geometry = []
|
||||||
|
|
||||||
# sketch outline
|
# sketch outline
|
||||||
|
@ -136,7 +179,11 @@ class BoardObject(BaseObject):
|
||||||
Part.LineSegment(start, begin)
|
Part.LineSegment(start, begin)
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync(self):
|
def sync_from(self):
|
||||||
|
self.sketch_outline()
|
||||||
|
self.substrate_body.recompute(True)
|
||||||
|
|
||||||
|
def sync_to(self):
|
||||||
board = self.kicad_board
|
board = self.kicad_board
|
||||||
|
|
||||||
commit = board.begin_commit()
|
commit = board.begin_commit()
|
||||||
|
|
43
freecad/kiconnect/commands/Syncable.py
Normal file
43
freecad/kiconnect/commands/Syncable.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
|
||||||
|
class Syncable:
|
||||||
|
SYNCABLES = [ 'KiConnect::Project', 'KiConnect::Board', 'KiConnect::Parts', 'KiConnect::BoardBody', ]
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
sel = Gui.Selection.getSelection()
|
||||||
|
|
||||||
|
if len(sel) == 0: return False
|
||||||
|
|
||||||
|
for obj in sel:
|
||||||
|
if obj.Type not in self.SYNCABLES:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
selection = [ sel for sel in Gui.Selection.getSelection() if sel.Type in self.SYNCABLES ]
|
||||||
|
syncables = []
|
||||||
|
|
||||||
|
if len(selection) == 1:
|
||||||
|
syncables = selection
|
||||||
|
else:
|
||||||
|
syncables = []
|
||||||
|
|
||||||
|
for i in selection:
|
||||||
|
for j in selection:
|
||||||
|
print(selection)
|
||||||
|
if i == j: continue
|
||||||
|
|
||||||
|
if i in j.OutList:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
syncables.append(i)
|
||||||
|
|
||||||
|
for s in syncables:
|
||||||
|
if not hasattr(s, 'Proxy'): continue
|
||||||
|
|
||||||
|
feature = s.Proxy
|
||||||
|
if hasattr(feature, self.method):
|
||||||
|
getattr(feature, self.method)()
|
||||||
|
|
||||||
|
s.recompute()
|
|
@ -8,7 +8,7 @@ from ..project import Project
|
||||||
|
|
||||||
class Reload:
|
class Reload:
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
tooltip = '<p>Reload KiConnect Workbench for development.</p>'
|
tooltip = '<p>Reload KiConnect Workbench for development.\nNOTE: Does not reload toolbars.</p>'
|
||||||
iconFile = os.path.join(settings.ICONPATH, 'kiconnect.svg')
|
iconFile = os.path.join(settings.ICONPATH, 'kiconnect.svg')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -19,15 +19,14 @@ class Reload:
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
try:
|
try:
|
||||||
Gui.activateWorkbench('Part')
|
Gui.activateWorkbench('PartWorkbench')
|
||||||
|
except:
|
||||||
print('failed to switch to Part WB')
|
print('failed to switch to Part WB')
|
||||||
except: pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Gui.removeWorkbench('KiConnect')
|
Gui.removeWorkbench('KiConnect')
|
||||||
except:
|
except:
|
||||||
print('failed to remove KiConnect')
|
print('failed to remove KiConnect')
|
||||||
pass
|
|
||||||
|
|
||||||
for mod in [mod for mod in sys.modules if 'kicon' in mod]:
|
for mod in [mod for mod in sys.modules if 'kicon' in mod]:
|
||||||
print(f'Reloading {mod}')
|
print(f'Reloading {mod}')
|
||||||
|
|
|
@ -7,7 +7,11 @@ import sys
|
||||||
from .. import settings
|
from .. import settings
|
||||||
from ..project import Project
|
from ..project import Project
|
||||||
|
|
||||||
class Sync:
|
from .Syncable import Syncable
|
||||||
|
|
||||||
|
class SyncFrom(Syncable):
|
||||||
|
method = 'sync_from'
|
||||||
|
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
tooltip = '<p>Reload Board from KiCAD.</p>'
|
tooltip = '<p>Reload Board from KiCAD.</p>'
|
||||||
iconFile = os.path.join(settings.ICONPATH, 'import_brd_file.svg')
|
iconFile = os.path.join(settings.ICONPATH, 'import_brd_file.svg')
|
||||||
|
@ -18,12 +22,4 @@ class Sync:
|
||||||
'Pixmap' : iconFile
|
'Pixmap' : iconFile
|
||||||
}
|
}
|
||||||
|
|
||||||
def Activated(self):
|
Gui.addCommand('kiconn_sync_from', SyncFrom())
|
||||||
boards = [ sel for sel in Gui.Selection.getSelection() if sel.Type == 'KiConnect::Board' ]
|
|
||||||
|
|
||||||
for board in boards:
|
|
||||||
board.KiConnBoard.sketch_outline()
|
|
||||||
|
|
||||||
App.ActiveDocument.recompute()
|
|
||||||
|
|
||||||
Gui.addCommand('kiconn_sync_from', Sync())
|
|
||||||
|
|
|
@ -6,7 +6,11 @@ import sys
|
||||||
from .. import settings
|
from .. import settings
|
||||||
from ..project import Project
|
from ..project import Project
|
||||||
|
|
||||||
class Sync:
|
from .Syncable import Syncable
|
||||||
|
|
||||||
|
class SyncTo(Syncable):
|
||||||
|
method = 'sync_to'
|
||||||
|
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
tooltip = '<p>Update Board in KiCAD.</p>'
|
tooltip = '<p>Update Board in KiCAD.</p>'
|
||||||
iconFile = os.path.join(settings.ICONPATH, 'export_to_pcbnew.svg')
|
iconFile = os.path.join(settings.ICONPATH, 'export_to_pcbnew.svg')
|
||||||
|
@ -17,13 +21,4 @@ class Sync:
|
||||||
'Pixmap' : iconFile
|
'Pixmap' : iconFile
|
||||||
}
|
}
|
||||||
|
|
||||||
def Activated(self):
|
Gui.addCommand('kiconn_sync_to', SyncTo())
|
||||||
boards = [ sel for sel in Gui.Selection.getSelection() if sel.Type == 'KiConnect::Board' ]
|
|
||||||
|
|
||||||
for board in boards:
|
|
||||||
board.KiConnBoard.sync()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Gui.addCommand('kiconn_sync_to', Sync())
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Project:
|
||||||
|
|
||||||
self.API = api.makeAPI(self.feature)
|
self.API = api.makeAPI(self.feature)
|
||||||
|
|
||||||
if self.API.Proxy.is_connected:
|
if self.API.Proxy.is_connected and self.API.DocumentCount > 0:
|
||||||
kicad_board = self.API.Proxy.kicad.get_board()
|
kicad_board = self.API.Proxy.kicad.get_board()
|
||||||
|
|
||||||
self.board = Board(kicad_board, self.feature)
|
self.board = Board(kicad_board, self.feature)
|
||||||
|
|
Loading…
Add table
Reference in a new issue