Compare commits
55 commits
v0.1.0
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
|
934aaaf354 | ||
|
7ee3ed89c6 | ||
|
b19c95cd2e | ||
|
205254a4f3 | ||
|
6137b50208 | ||
|
0a95b5529e | ||
|
a162b98000 | ||
|
c61020242d | ||
|
638fda2daf | ||
|
03fc42579f | ||
|
db3a25dbd2 | ||
|
984cb42b1c | ||
|
e1a0301e35 | ||
|
0e5f275b97 | ||
|
3a40276260 | ||
|
da98f7ba01 | ||
|
961b0b44f3 | ||
|
6d6ccadccf | ||
|
ac84562e1a | ||
|
a5dbdee3c8 | ||
|
5b73f2837a | ||
|
c4e8e79395 | ||
|
c1194ecee0 | ||
|
cfc3370ad8 | ||
|
613f959781 | ||
|
e81979ad84 | ||
|
ad679b9ae6 | ||
|
f5d3e26a0b | ||
|
d17d97f678 | ||
|
c66b554b46 | ||
|
f1d2800354 | ||
|
09a2daf427 | ||
|
527d493c1b | ||
|
b5a1e8c71d | ||
|
caa3225ef8 | ||
|
e05f3f6a3c | ||
|
8c41e66a1a | ||
|
915d4981bd | ||
|
29ded56e6a | ||
|
80871eb02b | ||
|
cd21370c90 | ||
|
f51bbad9b1 | ||
|
cfdbdd32bb | ||
|
da64ab6b6e | ||
|
c5d757c6bd | ||
|
ce25779b5d | ||
|
5f7a058706 | ||
|
c50e2363ba | ||
|
b02645a195 | ||
|
e5ec81b2dd | ||
|
a0941452ae | ||
|
7a3a9d8e16 | ||
|
c09306cbc8 | ||
|
b203270b54 | ||
|
ca9a0e5faa |
21 changed files with 1025 additions and 126 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
__pycache__
|
|
@ -2,14 +2,20 @@
|
|||
|
||||
KiCAD 9 API Workbench
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
208
freecad/kiconnect/board.py
Normal 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
|
||||
|
93
freecad/kiconnect/board_sketch.py
Normal file
93
freecad/kiconnect/board_sketch.py
Normal 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
|
46
freecad/kiconnect/commands/Syncable.py
Normal file
46
freecad/kiconnect/commands/Syncable.py
Normal 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()
|
24
freecad/kiconnect/commands/cmd_edit_prefs.py
Normal file
24
freecad/kiconnect/commands/cmd_edit_prefs.py
Normal 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())
|
|
@ -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}')
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
229
freecad/kiconnect/resources/icons/board.svg
Normal file
229
freecad/kiconnect/resources/icons/board.svg
Normal 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 |
158
freecad/kiconnect/resources/ui/preferences.ui
Normal file
158
freecad/kiconnect/resources/ui/preferences.ui
Normal 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>
|
|
@ -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
BIN
images/3_views.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
2
setup.py
2
setup.py
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue