Compare commits

...

55 commits

Author SHA1 Message Date
Morgan 'ARR\!' Allen
934aaaf354 more prefs 2025-06-10 22:12:07 -07:00
Morgan 'ARR\!' Allen
7ee3ed89c6 toolbar button to open preferences 2025-06-10 18:01:25 -07:00
Morgan 'ARR\!' Allen
b19c95cd2e added preferences
fixes #8
2025-06-10 17:53:30 -07:00
Morgan 'ARR\!' Allen
205254a4f3 make parts use sync_from 2025-05-19 21:23:11 -07:00
Morgan 'ARR\!' Allen
6137b50208 improving sync_from 2025-05-19 21:22:43 -07:00
Morgan 'ARR\!' Allen
0a95b5529e more migration to BoardSketch 2025-05-19 21:22:18 -07:00
Morgan 'ARR\!' Allen
a162b98000 Type doesnt want to be writable anymore 2025-05-19 21:21:50 -07:00
Morgan 'ARR\!' Allen
c61020242d make KiConnect::BoardSketch syncable 2025-05-19 21:20:25 -07:00
Morgan 'ARR\!' Allen
638fda2daf ensure doc is recompute after updates 2025-05-19 21:18:39 -07:00
Morgan 'ARR\!' Allen
03fc42579f move much of the board outline functionality to BoardSketch 2025-05-16 23:02:13 -07:00
Morgan 'ARR\!' Allen
db3a25dbd2 typo, get > getObject 2025-05-16 23:01:26 -07:00
Morgan 'ARR\!' Allen
984cb42b1c wip towards SketchObjectPython feature for board outline
This is helpful for preventing "sketch has been touhed after recompute type errors, which seem to occur because the sketch has been computed before the Board, which was doing the Sketch updates.
2025-05-16 22:43:39 -07:00
Morgan 'ARR\!' Allen
e1a0301e35 track board thickness on Board Feature 2025-05-16 18:05:46 -07:00
Morgan 'ARR\!' Allen
0e5f275b97 quickish fix to prevent board from jumping around when overall size changes 2025-05-16 18:05:34 -07:00
Morgan 'ARR\!' Allen
3a40276260 remove prints 2025-05-16 18:05:10 -07:00
Morgan 'ARR\!' Allen
da98f7ba01 part of eventual multiboard support 2025-05-16 17:52:59 -07:00
Morgan 'ARR\!' Allen
961b0b44f3 major flow rearrangement, better separation of syncing from KiCAD and FreeCAD executing/drawing. still more to go 2025-05-16 17:52:17 -07:00
Morgan 'ARR\!' Allen
6d6ccadccf now sync_from on init of all BaseObject classes 2025-05-16 17:51:18 -07:00
Morgan 'ARR\!' Allen
ac84562e1a use setup_properties method on Board 2025-05-16 17:45:24 -07:00
Morgan 'ARR\!' Allen
a5dbdee3c8 fixed type 2025-05-16 17:43:19 -07:00
Morgan 'ARR\!' Allen
5b73f2837a create the kicad api object earlier on 2025-05-16 17:43:02 -07:00
Morgan 'ARR\!' Allen
c4e8e79395 .gitignore 2025-05-01 18:47:30 -07:00
Morgan 'ARR\!' Allen
c1194ecee0 remove Activate() from Syncable subclasses 2025-04-30 20:27:04 -07:00
Morgan 'ARR\!' Allen
cfc3370ad8 make Syncables base class handle selection filtering and dispatching sync to sub Features 2025-04-30 20:25:38 -07:00
Morgan 'ARR\!' Allen
613f959781 move to specific sync_to and sync_from methods 2025-04-30 20:23:20 -07:00
Morgan 'ARR\!' Allen
e81979ad84 add KiConnect Type to Substrate (PartDesign::Body) 2025-04-30 20:23:00 -07:00
Morgan 'ARR\!' Allen
ad679b9ae6 add stub sync methods and isChildOf helper method 2025-04-30 20:21:01 -07:00
Morgan 'ARR\!' Allen
f5d3e26a0b add selection checking to sync buttons 2025-04-30 17:03:18 -07:00
Morgan 'ARR\!' Allen
d17d97f678 Board needs to call super on execute 2025-04-30 16:21:20 -07:00
Morgan 'ARR\!' Allen
c66b554b46 dont try to create Boards if there on no documents on the API 2025-04-30 13:51:56 -07:00
Morgan 'ARR\!' Allen
f1d2800354 add EXTENSIONS to BaseObject class 2025-04-30 13:51:19 -07:00
Morgan 'ARR\!' Allen
09a2daf427 also track out feature on the ViewProviders Proxy object 2025-04-30 13:50:42 -07:00
Morgan 'ARR\!' Allen
527d493c1b rename VIEWPROVIER_EXTENSIONS to just EXTENSIONS, its clear enough which class we're on 2025-04-30 13:50:03 -07:00
Morgan 'ARR\!' Allen
b5a1e8c71d better feedback on API connection status and Documnet availability 2025-04-30 13:29:23 -07:00
Morgan 'ARR\!' Allen
caa3225ef8 notes and minor changes to ping 2025-04-30 13:13:40 -07:00
Morgan 'ARR\!' Allen
e05f3f6a3c major rework of Board making bidirectional syncing more reliable 2025-04-29 10:25:16 -07:00
Morgan 'ARR\!' Allen
8c41e66a1a fix workbench switching for reload command 2025-04-23 14:33:19 -07:00
Morgan 'ARR\!' Allen
915d4981bd rename screenshot 2025-04-08 14:21:57 -07:00
Morgan 'ARR\!' Allen
29ded56e6a update to include image title 2025-04-08 14:21:08 -07:00
Morgan 'ARR\!' Allen
80871eb02b update readme to include screenshot 2025-04-08 14:20:29 -07:00
Morgan 'ARR\!' Allen
cd21370c90 missing board.py! (and icon) 2025-04-08 12:21:48 -07:00
Morgan 'ARR\!' Allen
f51bbad9b1 check if ICON is set 2025-04-08 12:21:07 -07:00
Morgan 'ARR\!' Allen
cfdbdd32bb cleanup and add get_api method 2025-04-08 12:20:55 -07:00
Morgan 'ARR\!' Allen
da64ab6b6e changed project workflow to use make* more in the FC Feature style. Board now creates Parts, as will layers, stubbed out copper for now 2025-04-08 12:19:33 -07:00
Morgan 'ARR\!' Allen
c5d757c6bd rework of API, added socket connection status, made more consistent with Feature style 2025-04-08 12:17:05 -07:00
Morgan 'ARR\!' Allen
ce25779b5d major rework of parts, somewhat handles updates from KiCAD via recompute, needs Live switch to enable/disable 2025-04-08 12:10:36 -07:00
Morgan 'ARR\!' Allen
5f7a058706 rework extensions like BaseObject and drop parent 2025-04-07 20:16:57 -07:00
Morgan 'ARR\!' Allen
c50e2363ba restore self.feature on document load 2025-04-07 20:03:39 -07:00
Morgan 'ARR\!' Allen
b02645a195 remove parent api, change Type handling and some setup reordering
unsure if the extensions is even going to get used
2025-04-07 20:02:05 -07:00
Morgan 'ARR\!' Allen
e5ec81b2dd move TYPE/ICON declarations to Object/ViewObject classes 2025-04-07 17:10:25 -07:00
Morgan 'ARR\!' Allen
a0941452ae handle missing footprint models 2025-04-07 16:38:13 -07:00
Morgan 'ARR\!' Allen
7a3a9d8e16 move icon/type handing to BaseViewProvider 2025-04-07 15:15:49 -07:00
Morgan 'ARR\!' Allen
c09306cbc8 update to latest kicad-python to support sync >to> KiCAD, needs KiCAD 9.0.2 (README update) 2025-03-31 12:50:09 -07:00
Morgan 'ARR\!' Allen
b203270b54 update to latest kicad-python to support sync >to> KiCAD, needs KiCAD 9.0.2 2025-03-31 12:47:27 -07:00
Morgan 'ARR\!' Allen
ca9a0e5faa preserve icon through doc restore 2025-03-29 00:12:03 -07:00
21 changed files with 1025 additions and 126 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__

View file

@ -2,14 +2,20 @@
KiCAD 9 API Workbench
![screenshot of FreeCAD, KiCAD and KiCAD 3d View](./images/3_views.png "KiConnect")
## What works
* Create board
* Add Vias
* Add footprint pads
* Import footprint 3d models
* Reload outline after changed in KiCAD
* Push outline back to KiCAD (requires KiCAD 9.0.2)
* Can be used in FreeCAD Assemblies
## Usage during development
## In Progress / To be ported
### Tracks

View file

@ -2,43 +2,94 @@ import FreeCAD as App
import FreeCADGui as Gui
from kipy import KiCad
from kipy.proto.common.types import DocumentType
from . import settings
from .bases import BaseObject, BaseViewProvider
class APIObject(BaseObject):
def __init__(self, parent, feature):
super(APIObject, self).__init__(parent, feature)
feature.addProperty('App::PropertyFile', 'Socket', 'KiConnect', 'Path to the KiCAD Socket File').Socket = '/tmp/kicad/api.lock'
def execute(self, feature):
self.parent.ping_connection()
class APIViewProvider(BaseViewProvider):
pass
class API():
ICON = 'icon_footprint_browser.svg'
TYPE = 'KiConnect::API'
def __init__(self):
self.feature = App.ActiveDocument.addObject('App::FeaturePython', 'API')
def __init__(self, feature):
self.kicad = KiCad()
APIObject(self, self.feature)
APIViewProvider(self, self.feature.ViewObject)
super(APIObject, self).__init__(feature)
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::PropertyInteger', 'DocumentCount', 'KiConnect', 'Count of open Documnets')
self.onDocumentRestored(feature)
self.ping_connection(feature)
def onDocumentRestored(self, feature):
super(APIObject, self).onDocumentRestored(feature)
self.kicad = KiCad()
self.ping_connection()
self.ping_connection(feature)
parent = feature.getParent()
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' ]
@property
def is_connected(self):
return self._connected
'''
Returns connection status
'''
return self.feature.Connected
def ping_connection(self, feature):
'''
Ping the KiCAD API to determine if it's connected
'''
connection_status = 'Disconnected'
document_status = 'No Documents'
def ping_connection(self):
try:
self.kicad.ping()
self._connected = True
except:
self._connected = False
feature.Connected = True
connection_status = 'Connected'
except Exception as e:
feature.Connected = False
connection_status = 'Disconnected'
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):
ICON = 'icon_footprint_browser.svg'
TYPE = 'KiConnect::API'
def __init__(self, viewprovider):
super(APIViewProvider, self).__init__(viewprovider)
def makeAPI(parent):
feature = App.ActiveDocument.addObject('App::FeaturePython', 'API')
parent.addObject(feature)
APIObject(feature)
APIViewProvider(feature.ViewObject)
return feature

View file

@ -1,29 +1,43 @@
class BaseObject:
def __init__(self, parent, feature):
print(self)
self.parent = parent
EXTENSIONS = []
TYPE = None
def __init__(self, feature):
self.feature = feature
feature.Proxy = self
self.Type = ''
self.setup_properties(feature)
self.setup_extensions(feature)
if hasattr(parent.__class__, 'TYPE'):
self.Type = parent.__class__.TYPE
feature.Type = self.TYPE
self.setup_properties()
self.setup_extensions()
self.sync_from()
def execute(self, feature):
print('execute', feature.Label, self.Type)
# TODO this might not be the right move
print(self, 'BaseObject.execute')
#self.onDocumentRestored(feature)
def setup_properties(self):
pass
def get_api(self):
p = self.feature
def setup_extensions(self):
if hasattr(self.parent.__class__, 'EXTENSIONS'):
for ext in self.parent.__class__.EXTENSIONS:
self.feature.addExtension(ext)
while p:
if p.Type == 'KiConnect::Project':
return [ o for o in p.Group if hasattr(o, 'Type') and o.Type == 'KiConnect::API' ][0].Proxy
p = p.getParent()
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):
pass
@ -31,5 +45,22 @@ class BaseObject:
def onChanged(self, feature, prop):
pass
def onDocumentRestored(self, feature):
self.feature = feature
def setup_extensions(self, feature):
if hasattr(self, 'EXTENSIONS'):
for ext in self.EXTENSIONS:
self.feature.addExtension(ext)
def setup_properties(self, feature):
feature.addProperty('App::PropertyString', 'Type', 'KiConnect', 'Internal KiConnect Type', hidden=True)
def sync_from(self):
pass
def sync_to(self):
pass
def __getstate__(self):
return None

View file

@ -5,22 +5,26 @@ from pivy import coin
from .. import settings
class BaseViewProvider:
def __init__(self, parent, viewprovider):
self.parent = parent
ICON = None
TYPE = None
EXTENSIONS = []
def __init__(self, viewprovider):
self.viewprovider = viewprovider
self.feature = viewprovider.Object.Proxy.feature
self.icon = ''
if self.ICON:
self.icon = os.path.join(settings.ICONPATH, self.ICON)
viewprovider.Proxy = self
self.Type = ''
if hasattr(parent.__class__, 'TYPE'):
self.Type = parent.__class__.TYPE
self.setup_extensions()
def setup_extensions(self):
if hasattr(self.parent.__class__, 'VIEWPROVIDER_EXTENSIONS'):
for ext in self.parent.__getstate__.VIEWPROVIDER_EXTENSIONS:
if hasattr(self, 'EXTENSIONS'):
for ext in self.EXTENSIONS:
self.feature.addExtension(ext)
def attach(self, vobj):
@ -32,7 +36,7 @@ class BaseViewProvider:
Gui.Selection.clearSelection()
def getIcon(self):
return os.path.join(settings.ICONPATH, self.parent.__class__.ICON)
return self.icon
def getDisplayModes(self,obj):
'''Return a list of display modes.'''
@ -43,4 +47,10 @@ class BaseViewProvider:
return 'Standard'
def __getstate__(self):
return None
return {
'icon': self.icon
}
def __setstate__(self, props):
for prop in props:
setattr(self, prop, props[prop])

208
freecad/kiconnect/board.py Normal file
View file

@ -0,0 +1,208 @@
import os
import FreeCAD as App
from . import settings
import Part
from kipy.board_types import Footprint3DModel, BoardPolygon, BoardSegment, PadStackShape
from kipy.geometry import PolygonWithHoles, PolyLine, PolyLineNode, Vector2
from kipy.proto.common.types import KiCadObjectType
from kipy.util.board_layer import BoardLayer
from .bases import BaseObject, BaseViewProvider
from . import parts as Parts
from . import board_sketch as BoardSketch
class BoardObject(BaseObject):
TYPE = 'KiConnect::Board'
def __init__(self, feature, kicad_board, board_polygon):
self.feature = feature
self.kicad_board = kicad_board
self.board_sketch = None
# TODO add this to FreeCAD Preferences and Property?
self.do_offset = True
# TODO needs to be resotred in onDocumentRestored
#self.board_polygon = board_polygon
self.polygon_id = board_polygon.id.value
self.via_sketch = None
super(BoardObject, self).__init__(feature)
self.feature.PolygonId = board_polygon.id.value
self.board_sketch = BoardSketch.makeBoardSketch(self.substrate_body, kicad_board, board_polygon)
self.create_substrate_pad()
@property
def board_polygon(self):
return [ poly for poly in extract_polygons(self.kicad_board) if poly.id.value == self.polygon_id ][0]
def onDocumentRestored(self, feature):
super(BoardObject, self).onDocumentRestored(feature)
self.board_sketch = self.feature.getObject('BoardSketch')
self.kicad_board = self.get_api().kicad.get_board()
@property
def substrate_body(self):
body = self.feature.getObject('Substrate')
if not body:
body = self.create_substrate_body()
return body
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)
return substrate_body
def create_substrate_pad(self):
pad = self.substrate_body.newObject('PartDesign::Pad', 'Outline')
pad.Profile = self.board_sketch
pad.Length = 1.6
pad.Midplane = True
return self.board_sketch
def get_polygon(self, kiid):
return [ s for s in self.kicad_board.get_shapes() if s.id.value == kiid ][0]
def get_boardpoly(self):
# TODO remove in favor of extract_polygons class method
board = self.kicad_board
# find polygons of Edge Cuts
edge_cuts = [ edge for edge in board.get_shapes() if edge.layer == BoardLayer.BL_Edge_Cuts ]
polygons = [ edge for edge in edge_cuts if isinstance(edge, BoardPolygon) ]
# XXX only single board supported at the moment
return polygons[0]
def pocket_vias(self):
board = self.kicad_board
self.via_sketch.Geometry = []
for via in board.get_vias():
stack = via.padstack
f_cu = stack.copper_layer(BoardLayer.BL_F_Cu)
self.via_sketch.addGeometry(Part.Circle((App.Vector(via.position.x, -via.position.y) / 1000000.0) - self.feature.BoardOffset.Base, App.Vector(0, 0, 1), via.drill_diameter / 1000000.0 / 2))
for pad in board.get_pads():
copper_layer = pad.padstack.copper_layer(BoardLayer.BL_F_Cu)
drill = pad.padstack.drill
if copper_layer.shape is not PadStackShape.PSS_CIRCLE: continue
center = (App.Vector(pad.position.x, -pad.position.y) / 1000000.0) - self.feature.BoardOffset.Base
if drill.diameter.x == drill.diameter.y:
rad = drill.diameter.x / 1000000.0 / 2
self.via_sketch.addGeometry(Part.Circle(center, App.Vector(0, 0, 1), rad))
def setup_vias(self):
self.via_sketch = App.ActiveDocument.addObject('Sketcher::SketchObject')
self.via_sketch.Visibility = False
self.substrate_body.addObject(self.via_sketch)
via_pocket = self.substrate_body.newObject('PartDesign::Pocket', 'Vias')
via_pocket.Profile = self.via_sketch
via_pocket.Midplane = True
via_pocket.Type = 1
def point_to_vector(self, point, offset=True):
return (
App.Vector(point.x,
-point.y
)) / 1000000.0 - (self.feature.BoardOffset.Base if offset else 0)
def sync_from(self):
'''
Pulls outline from KiCAD PolygonBoard and saves points as Vectors
'''
if self.board_sketch:
self.board_sketch.Proxy.sync_from()
def sync_to(self):
board = self.kicad_board
commit = board.begin_commit()
edge_cuts = [ edge for edge in board.get_shapes() if edge.layer == BoardLayer.BL_Edge_Cuts ]
polygons = [ edge for edge in edge_cuts if isinstance(edge, BoardPolygon) ]
# XXX only single board supported at the moment
boardpoly = polygons[0]
poly = boardpoly.polygons[0]
poly.outline.clear()
geom = self.feature.getObject('Substrate').getObject('Sketch').Geometry
segments = [l for l in geom if isinstance(l, Part.LineSegment)]
for line in segments:
start = (line.StartPoint + self.feature.BoardOffset.Base) * 1000000
start.y = -start.y
poly.outline.append(PolyLineNode.from_point(Vector2.from_xy(int(start.x), int(start.y))))
board.update_items(boardpoly)
board.push_commit(commit, "Updated board outline")
# if this is done, probably need to clear selection at the start
# maybe save the selection and restore if not empty, else select the poly as below
#board.add_to_selection(boardpoly)
def setup_properties(self, feature):
super(BoardObject, self).setup_properties(feature)
kicad_board = self.kicad_board
feature.addProperty('App::PropertyPlacement', 'BoardOffset', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
feature.addProperty('App::PropertyString', 'Doc', 'KiConnect', 'Doc in project to sync with', read_only=True)
feature.addProperty('App::PropertyString', 'PolygonId', 'KiConnect', 'Polygon ID for the original outline', hidden=True, read_only=True)
feature.addProperty('App::PropertyLength', 'Thickness', 'KiConnect', 'Thickness of PCB', hidden=True, read_only=True)
feature.addProperty('App::PropertyVectorList', 'Vectors', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True)
feature.Doc = kicad_board.name
if not feature.Thickness:
feature.Thickness = 1.6
def __getstate__(self):
return None
class BoardViewProvider(BaseViewProvider):
TYPE = 'KiConnect::Board'
ICON = 'board.svg'
def makeBoard(parent, kicad_board, polygon):
feature = App.ActiveDocument.addObject('App::DocumentObjectGroupPython', 'Board')
parent.addObject(feature)
BoardObject(feature, kicad_board, polygon)
BoardViewProvider(feature.ViewObject)
Parts.makeParts(feature)
return feature
def extract_polygons(board):
# find polygons of Edge Cuts
edge_cuts = [ edge for edge in board.get_shapes() if edge.layer == BoardLayer.BL_Edge_Cuts ]
polygons = [ edge for edge in edge_cuts if isinstance(edge, BoardPolygon) ]
# XXX only single board supported at the moment
return polygons

View file

@ -0,0 +1,93 @@
import os
import FreeCAD as App
from . import settings
import Part
from kipy.board_types import Footprint3DModel, BoardPolygon, BoardSegment, PadStackShape
from kipy.geometry import PolygonWithHoles, PolyLine, PolyLineNode, Vector2
from kipy.proto.common.types import KiCadObjectType
from kipy.util.board_layer import BoardLayer
from .bases import BaseObject, BaseViewProvider
class BoardSketchObject(BaseObject):
TYPE = 'KiConnect::BoardSketch'
def __init__(self, feature, kicad_board, board_polygon):
self.board_polygon = board_polygon
super(BoardSketchObject, self).__init__(feature)
#feature.Visibility = False
def execute(self, feature):
feature.recompute()
def get_parent_board(self):
try:
return self.feature.getParent().getParent().Proxy
except:
return None
def point_to_vector(self, point, offset=True):
return (
App.Vector(point.x,
-point.y
)) / 1000000.0 - (self.feature.BoardOffset.Base if offset else 0)
def setup_properties(self, feature):
super(BoardSketchObject, self).setup_properties(feature)
feature.addProperty('App::PropertyPlacement', 'BoardOffset', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
feature.addProperty('App::PropertyVectorList', 'Vectors', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True)
def sync_from(self):
feature = self.feature
vectors = []
board = self.get_parent_board()
if board:
self.board_polygon = board.get_polygon(board.polygon_id)
# board.get_shapes needs to be called to ensure polygons are actually up to date
for node in self.board_polygon.polygons[0].outline:
vectors.append(self.point_to_vector(node.point))
self.feature.Vectors = vectors
begin = None
start = None
# this probably needs a bit more control..
feature.Constraints = []
feature.Geometry = []
for vector in self.feature.Vectors:
if not start:
start = vector
begin = vector
continue
feature.addGeometry(Part.LineSegment(start, vector))
start = vector
feature.addGeometry(Part.LineSegment(start, begin))
feature.recompute()
def __getstate__(self):
return None
class BoardSketchViewProvider(BaseViewProvider):
TYPE = 'KiConnect::BoardSketch'
#ICON = 'board.svg'
def makeBoardSketch(parent, kicad_board, polygon):
feature = App.ActiveDocument.addObject('Sketcher::SketchObjectPython', 'BoardSketch')
parent.addObject(feature)
BoardSketchObject(feature, kicad_board, polygon)
BoardSketchViewProvider(feature.ViewObject)
return feature

View file

@ -0,0 +1,46 @@
import FreeCAD as App
import FreeCADGui as Gui
class Syncable:
SYNCABLES = [ 'KiConnect::Project', 'KiConnect::Board', 'KiConnect::Parts', 'KiConnect::BoardBody', 'KiConnect::BoardSketch' ]
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()
App.ActiveDocument.recompute()

View file

@ -0,0 +1,24 @@
import importlib
import FreeCADGui as Gui
import os
import sys
from .. import settings
from ..project import Project
class EditPrefs:
def GetResources(self):
tooltip = '<p>EditPrefs KiConnect Workbench for development.\nNOTE: Does not reload toolbars.</p>'
iconFile = 'preferences-system.svg'
return {
'MenuText': 'Edit Preferences',
'ToolTip': tooltip,
'Pixmap' : iconFile
}
def Activated(self):
Gui.showPreferences("KiConnect")
Gui.addCommand('cmd_edit_prefs', EditPrefs())

View file

@ -8,7 +8,7 @@ from ..project import Project
class Reload:
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')
return {
@ -19,15 +19,14 @@ class Reload:
def Activated(self):
try:
Gui.activateWorkbench('Part')
Gui.activateWorkbench('PartWorkbench')
except:
print('failed to switch to Part WB')
except: pass
try:
Gui.removeWorkbench('KiConnect')
except:
print('failed to remove KiConnect')
pass
for mod in [mod for mod in sys.modules if 'kicon' in mod]:
print(f'Reloading {mod}')

View file

@ -7,7 +7,11 @@ import sys
from .. import settings
from ..project import Project
class Sync:
from .Syncable import Syncable
class SyncFrom(Syncable):
method = 'sync_from'
def GetResources(self):
tooltip = '<p>Reload Board from KiCAD.</p>'
iconFile = os.path.join(settings.ICONPATH, 'import_brd_file.svg')
@ -18,12 +22,4 @@ class Sync:
'Pixmap' : iconFile
}
def Activated(self):
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())
Gui.addCommand('kiconn_sync_from', SyncFrom())

View file

@ -6,7 +6,11 @@ import sys
from .. import settings
from ..project import Project
class Sync:
from .Syncable import Syncable
class SyncTo(Syncable):
method = 'sync_to'
def GetResources(self):
tooltip = '<p>Update Board in KiCAD.</p>'
iconFile = os.path.join(settings.ICONPATH, 'export_to_pcbnew.svg')
@ -17,13 +21,4 @@ class Sync:
'Pixmap' : iconFile
}
def Activated(self):
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())
Gui.addCommand('kiconn_sync_to', SyncTo())

View file

@ -16,15 +16,13 @@ materials_manager = Materials.MaterialManager()
gold = materials_manager.Materials[gold_mat_uuid]
class CopperObject(BaseObject):
pass
TYPE = 'KiConnect::Copper'
class CopperViewProvider(BaseViewProvider):
pass
class Copper():
ICON = 'show_all_copper_layers.svg'
TYPE = 'KiConnect::Copper'
class Copper():
def __init__(self, kicad_board, kiconn_board):
self.nets = {}

View file

@ -7,7 +7,7 @@ sys.path.insert(1, os.path.join(os.path.dirname(os.path.realpath(__file__)), '..
import FreeCADGui as Gui
import FreeCAD as App
from .commands import cmd_new_pcb, cmd_reload, cmd_sync_from, cmd_sync_to
from .commands import cmd_edit_prefs, cmd_new_pcb, cmd_reload, cmd_sync_from, cmd_sync_to
from . import settings
translate=App.Qt.translate
@ -19,12 +19,18 @@ TRANSLATIONSPATH = os.path.join(os.path.dirname(__file__), "resources", "transla
Gui.addLanguagePath(TRANSLATIONSPATH)
Gui.updateLocale()
default_preferences = [
('Bool', 'debug_reload', True),
('Bool', 'prefs_toolbar', True),
('Float', 'default_thickness', 1.6),
]
class KiConnect(Gui.Workbench):
MenuText = translate("Workbench", "KiConnect")
ToolTip = translate("Workbench", "KiConnect PCB Workbench")
Icon = os.path.join(settings.ICONPATH, "kiconnect.svg")
toolbox = [ 'kiconn_new', 'kiconn_reload', 'kiconn_sync_to', 'kiconn_sync_from' ]
toolbox = [ 'kiconn_new', 'kiconn_sync_to', 'kiconn_sync_from' ]
def GetClassName(self):
return "Gui::PythonWorkbench"
@ -35,11 +41,27 @@ class KiConnect(Gui.Workbench):
here is the place to import all the commands
"""
# setup default preferences on first load
if settings.preferences.IsEmpty():
for pref in default_preferences:
print('setting pref: ', f'Set{pref[0]}', pref[1], pref[2])
getattr(settings.preferences, f'Set{pref[0]}')(pref[1], pref[2])
# add debug reload button if enabled
if settings.preferences.GetBool('debug_reload'):
self.toolbox.append('kiconn_reload')
if settings.preferences.GetBool('prefs_toolbar'):
self.toolbox.append('cmd_edit_prefs')
print('setting up toolbar')
# NOTE: Context for this commands must be "Workbench"
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "KiConnect"), self.toolbox)
self.appendMenu(QT_TRANSLATE_NOOP("Workbench", "KiConnect"), self.toolbox)
Gui.addPreferencePage(os.path.join(settings.UIPATH, 'preferences.ui'), 'KiConnect')
def Activated(self):
App.Console.PrintMessage(translate("Log", "Workbench KiConnect activated.") + "\n")

View file

@ -13,48 +13,74 @@ from . import settings
from .bases import BaseObject, BaseViewProvider
class PartsObject(BaseObject):
pass
class PartsViewProvider(BaseViewProvider):
pass
class Parts():
ICON = 'icon_footprint_browser.svg'
TYPE = 'KiConnect::Parts'
def __init__(self, kicad_board, kiconn_board):
self.kicad_board = kicad_board
self.kiconn_board = kiconn_board
def __init__(self, feature):
super(PartsObject, self).__init__(feature)
feature = App.ActiveDocument.addObject('App::DocumentObjectGroupPython', 'Parts')
def execute(self, feature):
super(PartsObject, self).execute(feature)
PartsObject(self, feature)
PartsViewProvider(self, feature.ViewObject)
def sync_from(self):
kiconn_board = self.feature.getParentGroup()
kicad_board = self.get_api().kicad.get_board()
self.feature = feature
existing_footprints_by_ref = { part.KiCadRef: part for part in self.feature.Group }
self.import_footprints()
def import_footprints(self):
for footprint in self.kicad_board.get_footprints():
for footprint in kicad_board.get_footprints():
# NOTE this doesn't handle footprints that have been removed
if App.ActiveDocument.getObjectsByLabel(footprint.reference_field.text.value): continue
reference = footprint.reference_field.text.value
if reference in existing_footprints_by_ref:
model = existing_footprints_by_ref[reference]
placement = App.Placement()
placement.Base.x = (footprint.position.x / 1000000.0) - kiconn_board.BoardOffset.Base.x
placement.Base.y = (-footprint.position.y / 1000000.0) - kiconn_board.BoardOffset.Base.y
# TODO get from kicad board settings
placement.Base.z = 0.8
placement.Rotation.Angle = footprint.orientation.to_radians()
if model.Placement != placement:
model.Placement = placement
continue
for item in [item for item in footprint.definition.items if isinstance(item, Footprint3DModel)]:
filename = item.filename.replace('${KICAD9_3DMODEL_DIR}', settings.KICAD9_3DMODEL_DIR).replace('wrl', 'step')
ImportGui.insert(filename, App.ActiveDocument.Name)
try:
ImportGui.insert(filename, App.ActiveDocument.Name)
# simply grabs the last object in the document, probably need to figure out a safer way to handle
model = App.ActiveDocument.findObjects()[-1]
model.Label = footprint.reference_field.text.value
# simply grabs the last object in the document, probably need to figure out a safer way to handle
model = App.ActiveDocument.findObjects()[-1]
model.Label = reference
model.Label2 = reference
model.addProperty('App::PropertyPlacement', 'BoardOffset', 'Base', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
self.feature.addObject(model)
model.addProperty('App::PropertyPlacement', 'BoardOffset', 'Base', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
model.addProperty('App::PropertyString', 'KiCadRef', 'KiConnect', 'Original KiCAD REF', hidden=True, read_only=True)
model.KiCadRef = reference
model.Placement.Base.x = (footprint.position.x / 1000000.0) - self.kiconn_board.offset.x
model.Placement.Base.y = (-footprint.position.y / 1000000.0) - self.kiconn_board.offset.y
model.Placement.Base.z = 0.8
model.Placement.Rotation.Angle = footprint.orientation.to_radians()
model.BoardOffset = model.Placement
self.feature.addObject(model)
model.Placement.Base.x = (footprint.position.x / 1000000.0) - kiconn_board.BoardOffset.Base.x
model.Placement.Base.y = (-footprint.position.y / 1000000.0) - kiconn_board.BoardOffset.Base.y
# TODO get from kicad board settings
model.Placement.Base.z = 0.8
model.Placement.Rotation.Angle = footprint.orientation.to_radians()
model.BoardOffset = model.Placement
except Exception as e:
print('failed to load', filename)
print(e)
class PartsViewProvider(BaseViewProvider):
ICON = 'icon_footprint_browser.svg'
TYPE = 'KiConnect::Parts'
def makeParts(parent):
feature = App.ActiveDocument.addObject('App::DocumentObjectGroupPython', 'Parts')
parent.addObject(feature)
PartsObject(feature)
PartsViewProvider(feature.ViewObject)
return feature

View file

@ -7,10 +7,10 @@ from kipy import KiCad
from . import settings
from .api import API
from . import api
from .copper import Copper
from .board import Board
from .parts import Parts
from . import board as Board
#from .parts import Parts
class Project:
def __init__(self):
@ -21,23 +21,24 @@ class Project:
self.viewprovider = None
feature = App.ActiveDocument.addObject('App::Part', 'KiConnect')
feature.Type = 'KiConnect::Project'
self.feature = feature
feature.addProperty('App::PropertyTime', 'ProcessTime', 'KiConnect', 'Time to process Project', hidden=True, read_only=True)
self.API = API()
self.feature.addObject(self.API.feature)
self.API = api.makeAPI(self.feature)
if self.API.is_connected:
kicad_board = self.API.kicad.get_board()
if self.API.Proxy.is_connected and self.API.DocumentCount > 0:
kicad_board = self.API.Proxy.kicad.get_board()
self.board = Board(kicad_board)
self.feature.addObject(self.board.feature)
polygons = Board.extract_polygons(kicad_board)
self.parts = Parts(kicad_board, self.board)
self.board.feature.addObject(self.parts.feature)
for polygon in polygons:
self.board = Board.makeBoard(self.feature, kicad_board, polygon)
self.copper = Copper(kicad_board, self.board)
self.board.feature.addObject(self.copper.feature)
#self.copper = Copper(kicad_board, self.board)
#self.board.feature.addObject(self.copper.feature)
feature.ProcessTime = time.time() - start_time
App.ActiveDocument.recompute()

View file

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Слой_1"
data-name="Слой 1"
viewBox="0 0 24 24"
version="1.1"
sodipodi:docname="board.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
inkscape:export-filename="/Users/jeff/kicad_dev/kicad/bitmaps_png/png_26/import_brd_file.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1368"
id="namedview30"
showgrid="true"
inkscape:zoom="21.709691"
inkscape:cx="6.0802339"
inkscape:cy="15.177554"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:current-layer="Слой_1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid_kicad"
spacingx="0.5"
spacingy="0.5"
color="#9999ff"
opacity="0.13"
empspacing="2"
originx="0"
originy="0"
units="px" />
</sodipodi:namedview>
<metadata
id="metadata43">
<rdf:RDF>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>import_brd_file</dc:title>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs25341">
<style
id="style25339">.cls-1{fill:#8f8f8f;}.cls-2{fill:#DED3DD;}.cls-3,.cls-6{fill:none;}.cls-3{stroke:#8f8f8f;stroke-miterlimit:10;stroke-width:0.9551px;}.cls-4{fill:#42B8EB;}.cls-5{fill:#f2647e;}.cls-6{stroke:#545454;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath884">
<rect
style="display:inline;fill:#f29100;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="rect886"
width="21"
height="21"
x="-33"
y="-2"
ry="3.4854646"
rx="3.4854646" />
</clipPath>
</defs>
<title
id="title25343">import_brd_file</title>
<g
id="g952"
clip-path="url(#clipPath884)"
transform="translate(33.00181,1.9988756)"
inkscape:label="g952">
<rect
class="cls-1"
x="1.4020385e-07"
y="0"
width="24"
height="24"
rx="0"
id="rect9"
style="fill:#489648;fill-opacity:1;stroke:none;stroke-width:0.571427;stroke-opacity:1"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-2)" />
<path
class="cls-2"
d="m 9.7142857,24 v -1.714286 l 4.0000003,-4 V 12.571429 L 14.285714,12 h 4 l 2.285715,1.714286 H 24 V 4.5714286 H 20.571429 L 18.285714,6.2857143 H 16.571429 L 14.5,4 V 0 C 9.9949374,-0.001605 2.029505,0.001245 -0.0020687,0.001285 0,3.25 0,24 0,24 c 1.5,-0.02777 4.0236148,0 9.7142857,0 z"
id="path11"
sodipodi:nodetypes="ccccccccccccccccc"
style="fill:#006400;fill-opacity:1;stroke-width:0.571427"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-2)" />
<path
style="fill:none;stroke:#489648;stroke-width:2.28571;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 20.571429,9.1428571 H 14.857143 L 11.428571,5.7142857 v -6.8571428"
id="path2253"
sodipodi:nodetypes="cccc"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-2)" />
<path
style="fill:none;stroke:#489648;stroke-width:3.23249;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 0,14.5 H 2"
id="path892"
sodipodi:nodetypes="cc"
clip-path="none"
transform="matrix(2.1875,0,0,0.875,-33,-2.0750619)" />
<path
style="fill:none;stroke:#489648;stroke-width:2.85714;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 5.2135977,25.142857 V 21.714286 L 9.7850263,17.142857 V 11.428571 L 5.2135977,6.8571429 v -7.42857147"
id="path2265"
sodipodi:nodetypes="cccccc"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-32.561898,-2)" />
<rect
style="fill:#006400;fill-opacity:1;stroke:none;stroke-width:2.65576;stroke-linecap:round;stroke-linejoin:round"
id="rect878"
width="6"
height="5"
x="17"
y="14"
rx="0"
ry="0.80000001"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<rect
style="fill:#f29100;fill-opacity:1;stroke:none;stroke-width:1.5333;stroke-linecap:round;stroke-linejoin:round"
id="rect880"
width="4"
height="3"
x="18"
y="15"
rx="0"
ry="1"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<rect
style="fill:#006400;fill-opacity:1;stroke:none;stroke-width:2.65576;stroke-linecap:round;stroke-linejoin:round"
id="rect882"
width="6"
height="5"
x="17"
y="20"
rx="0"
ry="0.80000001"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<rect
style="fill:#f29100;fill-opacity:1;stroke:none;stroke-width:1.5333;stroke-linecap:round;stroke-linejoin:round"
id="rect884"
width="4"
height="3"
x="18"
y="21"
rx="0"
ry="0.96360058"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<path
style="fill:none;stroke:#2cb22c;stroke-width:1.14285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 18,16.5 H 16"
id="path886"
sodipodi:nodetypes="cc"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<path
style="fill:none;stroke:#2cb22c;stroke-width:1.14285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 24,16.5 H 22"
id="path888"
sodipodi:nodetypes="cc"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<path
style="fill:none;stroke:#2cb22c;stroke-width:2.28571;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 20,14 v 1"
id="path890"
sodipodi:nodetypes="cc"
clip-path="none"
transform="matrix(0.875,0,0,0.875,-33,-1.25)" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:#dd8d15;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path936"
cx="-29.11735"
cy="10.58021"
r="1.6027977"
clip-path="none" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:#dd8d15;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle938"
cx="-14.137123"
cy="5.9967451"
r="1.6027977"
clip-path="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View file

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KiConnect::DlgSettingsKiConnect</class>
<widget class="QWidget" name="KiConnect::DlgSettingsKiConnect">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1151</width>
<height>663</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>1151</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>General</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="defaults_group">
<property name="title">
<string>Defaults</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Default Board Thickness</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefUnitSpinBox" name="spin_thickness">
<property name="unit" stdset="0">
<string>mm</string>
</property>
<property name="value">
<double>1.600000000000000</double>
</property>
<property name="prefEntry" stdset="0">
<cstring>default_thickness</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/KiConnect</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="check_load_parts">
<property name="text">
<string>Load Parts</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>parts_load</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/KiConnect</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="debug_group">
<property name="title">
<string>Debug and Development</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="Gui::PrefCheckBox" name="check_debug_reload">
<property name="font">
<font>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Enable Reload</string>
</property>
<property name="tristate">
<bool>false</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>debug_reload</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/KiConnect</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox">
<property name="text">
<string> Preferences from toolbar</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>Mod/KiConnect</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>prefs_toolbar</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QAbstractSpinBox</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefUnitSpinBox</class>
<extends>Gui::QuantitySpinBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -1,5 +1,10 @@
import os
import FreeCAD as App
BOARD_THICKNESS = 0.80
ICONPATH = os.path.join(os.path.dirname(__file__), "resources", 'icons')
UIPATH = os.path.join(os.path.dirname(__file__), "resources", 'ui')
KICAD9_3DMODEL_DIR = '/usr/share/kicad/3dmodels/'
PREF_PATH = 'User parameter:BaseApp/Preferences/Mod/KiConnect'
preferences = App.ParamGet(PREF_PATH)

BIN
images/3_views.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View file

@ -13,5 +13,5 @@ setup(name='freecad.kiconnect',
maintainer_email='morgan@oit.cloud',
url='https://git.oit.cloud/morgan/kiconnect',
description='Printed Circuit Workbench for interacting with KiCAD v9+ API',
install_requires=['git+https://gitlab.com/kicad/code/kicad-python.git@55feccb010d3c3d5e501133b87330bbdafef5fff',],
install_requires=['git+https://gitlab.com/kicad/code/kicad-python.git@dceca75cbb40dbd540039ea13d76414a62b74360',],
include_package_data=True)