Compare commits

...

18 commits

Author SHA1 Message Date
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
11 changed files with 584 additions and 100 deletions

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

@ -7,38 +7,57 @@ 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):
super(APIObject, self).__init__(feature)
APIObject(self, self.feature)
APIViewProvider(self, self.feature.ViewObject)
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')
self.onDocumentRestored(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
boards = [ board for board in parent.Group if hasattr(board, 'Type') and board.Type == 'KiConnect::Board' ]
@property
def is_connected(self):
return self._connected
return self.feature.Connected
def ping_connection(self):
def ping_connection(self, feature):
try:
self.kicad.ping()
self._connected = True
except:
self._connected = False
feature.Connected = True
feature.Label2 = 'Connected'
except Exception as e:
print(e)
feature.Connected = False
feature.Label2 = 'Disconnected'
pass
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,29 @@
class BaseObject:
def __init__(self, parent, feature):
print(self)
self.parent = parent
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
self.setup_properties()
self.setup_extensions()
feature.Type = self.TYPE
def execute(self, feature):
print('execute', feature.Label, self.Type)
# TODO this might not be the right move
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 onBeforeChange(self, feature, prop):
pass
@ -31,5 +31,16 @@ 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', 'Internatl KiConnect Type', read_only=True, hidden=True)
def __getstate__(self):
return None

View file

@ -5,22 +5,25 @@ from pivy import coin
from .. import settings
class BaseViewProvider:
def __init__(self, parent, viewprovider):
self.parent = parent
ICON = None
TYPE = None
VIEWPROVIDER_EXTENSIONS = []
def __init__(self, viewprovider):
self.viewprovider = viewprovider
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, 'VIEWPROVIDER_EXTENSIONS'):
for ext in self.VIEWPROVIDER_EXTENSIONS:
self.feature.addExtension(ext)
def attach(self, vobj):
@ -32,7 +35,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 +46,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])

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

@ -0,0 +1,189 @@
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
class BoardObject(BaseObject):
TYPE = 'KiConnect::Board'
def __init__(self, feature):
super(BoardObject, self).__init__(feature)
self.kicad_board = None
self.substrate_body = None
self.substrate_sketch = None
self.via_sketch = None
feature.addProperty('App::PropertyPlacement', 'BoardOffset', 'KiConnect', 'Internal offset for zeroing out Footprint offset', hidden=True, read_only=True)
def onDocumentRestored(self, feature):
if self.kicad_board is None:
self.kicad_board = self.get_api().kicad.get_board()
if self.substrate_body is None:
self.extrude_substrate(feature)
self.sketch_outline(feature)
if len(self.kicad_board.get_vias()) > 0 and self.via_sketch is None:
self.setup_vias()
self.pocket_vias()
def extrude_substrate(self, feature):
self.substrate_sketch = App.ActiveDocument.addObject('Sketcher::SketchObject', 'Sketch')
self.substrate_sketch.Visibility = False
self.substrate_body = App.ActiveDocument.addObject('PartDesign::Body', 'Substrate')
self.feature.addObject(self.substrate_body)
pad = self.substrate_body.newObject('PartDesign::Pad', 'Outline')
pad.Profile = self.substrate_sketch
pad.Length = 1.6
pad.Midplane = True
self.substrate_body.addObject(self.substrate_sketch)
def get_boardpoly(self):
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 sketch_outline(self, feature):
boardpoly = self.get_boardpoly()
poly = boardpoly.polygons[0]
# track KIID
# TODO FeaturePython objects interacting with kipy objects should inherit common parent class
self.feature.PolygonId = boardpoly.id.value
# this offset centers the board to 0,0
bb = boardpoly.bounding_box()
self.feature.BoardOffset.Base = (App.Vector(bb.pos.x, -bb.pos.y) + App.Vector(bb.size.x, -bb.size.y) / 2) / 1000000.0
begin = None
start = None
# reset Sketch Geometry
self.substrate_sketch.Geometry = []
# sketch outline
for segment in poly.outline:
if not start:
start = (App.Vector(segment.point.x, -segment.point.y)) / 1000000.0 - self.feature.BoardOffset.Base
# needs to remain to connect the last segment
begin = start
continue
end = (App.Vector(segment.point.x, -segment.point.y)) / 1000000.0 - self.feature.BoardOffset.Base
self.substrate_sketch.addGeometry(
Part.LineSegment(start, end)
)
start = end
# make final connection
self.substrate_sketch.addGeometry(
Part.LineSegment(start, begin)
)
def sync(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 __getstate__(self):
return None
class BoardViewProvider(BaseViewProvider):
TYPE = 'KiConnect::Board'
ICON = 'board.svg'
class Board:
def __init__(self, kicad_board, parent):
feature = App.ActiveDocument.addObject('App::DocumentObjectGroupPython', 'Board')
parent.addObject(feature)
self.feature = feature
BoardObject(feature)
BoardViewProvider(feature.ViewObject)
# TODO move into BoardObject
feature.addProperty('App::PropertyString', 'Doc', 'KiConnect', 'Doc in project to sync with', read_only=True).Doc = kicad_board.name
feature.addProperty('App::PropertyString', 'PolygonId', 'KiConnect', 'Polygon ID for the original outline', hidden=True, read_only=True)
parts.makeParts(feature)

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

@ -13,48 +13,73 @@ 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
feature = App.ActiveDocument.addObject('App::DocumentObjectGroupPython', 'Parts')
PartsObject(self, feature)
PartsViewProvider(self, feature.ViewObject)
self.feature = feature
def execute(self, feature):
super(PartsObject, self).execute(feature)
self.import_footprints()
def import_footprints(self):
for footprint in self.kicad_board.get_footprints():
kiconn_board = self.feature.getParentGroup()
kicad_board = self.get_api().kicad.get_board()
existing_footprints_by_ref = { part.KiCadRef: part for part in self.feature.Group }
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 .parts import Parts
class Project:
def __init__(self):
@ -21,23 +21,21 @@ 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:
kicad_board = self.API.Proxy.kicad.get_board()
self.board = Board(kicad_board)
self.feature.addObject(self.board.feature)
self.board = Board(kicad_board, self.feature)
self.parts = Parts(kicad_board, self.board)
self.board.feature.addObject(self.parts.feature)
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

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)