Skip to content

Commit

Permalink
Update compatibility with nightly, support dimensions
Browse files Browse the repository at this point in the history
  • Loading branch information
qu1ck committed Mar 19, 2022
1 parent a329307 commit 50c790c
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 82 deletions.
8 changes: 8 additions & 0 deletions DATAFORMAT.md
Expand Up @@ -240,6 +240,14 @@ attribute.
// will not attempt to read character data from newstroke font and
// will draw the path as is. "thickness" will be used as stroke width.
"svgpath": svgpath,
// If polygons are specified then remaining attributes are ignored
"polygons": [
// Polygons are described as set of outlines.
[
[point1x, point1y], [point2x, point2y], ...
],
...
],
"height": height,
"width": width,
// -1: justify left/top
Expand Down
16 changes: 8 additions & 8 deletions InteractiveHtmlBom/dialog/dialog_base.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

###########################################################################
## Python code generated with wxFormBuilder (version Oct 26 2018)
## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.
bSizer39.Add( self.m_button41, 0, wx.ALL, 5 )


bSizer39.Add( ( 50, 0), 0, wx.EXPAND, 5 )
bSizer39.Add( ( 50, 0), 1, wx.EXPAND, 5 )

self.m_button42 = wx.Button( self, wx.ID_ANY, u"Generate BOM", wx.DefaultPosition, wx.DefaultSize, 0|wx.BORDER_DEFAULT )

Expand All @@ -61,7 +61,7 @@ def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.
bSizer39.Add( self.m_button43, 0, wx.ALL, 5 )


bSizer20.Add( bSizer39, 0, wx.ALIGN_CENTER, 5 )
bSizer20.Add( bSizer39, 0, wx.EXPAND, 5 )


self.SetSizer( bSizer20 )
Expand All @@ -76,7 +76,7 @@ def __del__( self ):
pass


# Virtual event handlers, overide them in your derived class
# Virtual event handlers, override them in your derived class
def OnSaveSettings( self, event ):
event.Skip()

Expand Down Expand Up @@ -191,7 +191,7 @@ def __del__( self ):
pass


# Virtual event handlers, overide them in your derived class
# Virtual event handlers, override them in your derived class
def OnBoardRotationSlider( self, event ):
event.Skip()

Expand Down Expand Up @@ -370,7 +370,7 @@ def __del__( self ):
pass


# Virtual event handlers, overide them in your derived class
# Virtual event handlers, override them in your derived class
def OnSize( self, event ):
event.Skip()

Expand Down Expand Up @@ -432,10 +432,10 @@ def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.
self.fieldsGrid.AutoSizeColumns()
self.fieldsGrid.EnableDragColMove( False )
self.fieldsGrid.EnableDragColSize( True )
self.fieldsGrid.SetColLabelSize( 30 )
self.fieldsGrid.SetColLabelValue( 0, u"Show" )
self.fieldsGrid.SetColLabelValue( 1, u"Group" )
self.fieldsGrid.SetColLabelValue( 2, u"Name" )
self.fieldsGrid.SetColLabelSize( 30 )
self.fieldsGrid.SetColLabelAlignment( wx.ALIGN_CENTER, wx.ALIGN_CENTER )

# Rows
Expand Down Expand Up @@ -552,7 +552,7 @@ def __del__( self ):
pass


# Virtual event handlers, overide them in your derived class
# Virtual event handlers, override them in your derived class
def OnSize( self, event ):
event.Skip()

Expand Down
2 changes: 1 addition & 1 deletion InteractiveHtmlBom/ecad/easyeda.py
Expand Up @@ -222,7 +222,7 @@ def parse_pad(self, shape):
"layers": pad_layers,
"pos": [x, y],
"size": [width, height],
"angle": -angle,
"angle": angle,
"shape": pad_shape,
"type": pad_type,
}
Expand Down
172 changes: 122 additions & 50 deletions InteractiveHtmlBom/ecad/kicad.py
Expand Up @@ -67,19 +67,25 @@ def extra_data_file_filter(self):

@staticmethod
def normalize(point):
return [point[0] * 1e-6, point[1] * 1e-6]
return [point.x * 1e-6, point.y * 1e-6]

@staticmethod
def get_arc_angles(d):
def normalize_angle(angle):
if isinstance(angle, int) or isinstance(angle, float):
return angle * 0.1
else:
return angle.AsDegrees()

def get_arc_angles(self, d):
# type: (pcbnew.PCB_SHAPE) -> tuple
a1 = d.GetArcAngleStart()
a1 = self.normalize_angle(d.GetArcAngleStart())
if hasattr(d, "GetAngle"):
a2 = a1 + d.GetAngle()
a2 = a1 + self.normalize_angle(d.GetAngle())
else:
a2 = a1 + d.GetArcAngle()
a2 = a1 + self.normalize_angle(d.GetArcAngle())
if a2 < a1:
a1, a2 = a2, a1
return round(a1 * 0.1, 2), round(a2 * 0.1, 2)
return round(a1, 2), round(a2, 2)

def parse_shape(self, d):
# type: (pcbnew.PCB_SHAPE) -> dict or None
Expand Down Expand Up @@ -151,7 +157,7 @@ def parse_shape(self, d):
else:
parent_footprint = d.GetParentFootprint()
if parent_footprint is not None:
angle = parent_footprint.GetOrientation() * 0.1,
angle = self.normalize_angle(parent_footprint.GetOrientation())
shape_dict = {
"type": shape,
"pos": start,
Expand All @@ -178,26 +184,34 @@ def parse_shape(self, d):
"width": d.GetWidth() * 1e-6
}

def parse_poly_set(self, polygon_set):
def parse_line_chain(self, shape):
# type: (pcbnew.SHAPE_LINE_CHAIN) -> list
result = []
if not hasattr(shape, "PointCount"):
self.logger.warn("No PointCount method on outline object. "
"Unpatched kicad version?")
return result

for point_index in range(shape.PointCount()):
result.append(
self.normalize(shape.CPoint(point_index)))

return result

def parse_poly_set(self, poly):
# type: (pcbnew.SHAPE_POLY_SET) -> list
result = []
for polygon_index in range(polygon_set.OutlineCount()):
outline = polygon_set.Outline(polygon_index)
if not hasattr(outline, "PointCount"):
self.logger.warn("No PointCount method on outline object. "
"Unpatched kicad version?")
return result
parsed_outline = []
for point_index in range(outline.PointCount()):
point = outline.CPoint(point_index)
parsed_outline.append(self.normalize([point.x, point.y]))
result.append(parsed_outline)

for i in range(poly.OutlineCount()):
result.append(self.parse_line_chain(poly.Outline(i)))

return result

def parse_text(self, d):
pos = self.normalize(d.GetPosition())
if not d.IsVisible() and d.GetClass() != "PTEXT":
# type: (pcbnew.PCB_TEXT) -> dict
if not d.IsVisible() and d.GetClass() not in ["PTEXT", "PCB_TEXT"]:
return None
pos = self.normalize(d.GetPosition())
if hasattr(d, "GetTextThickness"):
thickness = d.GetTextThickness() * 1e-6
else:
Expand All @@ -213,13 +227,38 @@ def parse_text(self, d):
"thickness": thickness,
"svgpath": create_path(lines)
}
elif hasattr(d, 'GetEffectiveTextShape'):
shape = d.GetEffectiveTextShape(
aTriangulate=False) # type: pcbnew.SHAPE_COMPOUND
segments = []
polygons = []
for s in shape.GetSubshapes():
if s.Type() == pcbnew.SH_LINE_CHAIN:
polygons.append(self.parse_line_chain(s))
elif s.Type() == pcbnew.SH_SEGMENT:
seg = s.GetSeg()
segments.append(
[self.normalize(seg.A), self.normalize(seg.B)])
else:
self.logger.warn(
"Unsupported subshape in text: %s" % s.Type())
if segments:
return {
"thickness": thickness,
"svgpath": create_path(segments)
}
else:
return {
"polygons": polygons
}

if d.GetClass() == "MTEXT":
angle = d.GetDrawRotation() * 0.1
angle = self.normalize_angle(d.GetDrawRotation())
else:
if hasattr(d, "GetTextAngle"):
angle = d.GetTextAngle() * 0.1
angle = self.normalize_angle(d.GetTextAngle())
else:
angle = d.GetOrientation() * 0.1
angle = self.normalize_angle(d.GetOrientation())
if hasattr(d, "GetTextHeight"):
height = d.GetTextHeight() * 1e-6
width = d.GetTextWidth() * 1e-6
Expand Down Expand Up @@ -250,15 +289,47 @@ def parse_text(self, d):
"angle": angle
}

def parse_dimension(self, d):
# type: (pcbnew.PCB_DIMENSION_BASE) -> dict
segments = []
circles = []
for s in d.GetShapes():
s = s.Cast()
if s.Type() == pcbnew.SH_SEGMENT:
seg = s.GetSeg()
segments.append(
[self.normalize(seg.A), self.normalize(seg.B)])
elif s.Type() == pcbnew.SH_CIRCLE:
circles.append(
[self.normalize(s.GetCenter()), s.GetRadius() * 1e-6])
else:
self.logger.info(
"Unsupported shape type in dimension object: %s", s.Type())

svgpath = create_path(segments, circles=circles)

return {
"thickness": d.GetLineThickness() * 1e-6,
"svgpath": svgpath
}

def parse_drawing(self, d):
# type: (pcbnew.BOARD_ITEM) -> list
result = []
s = None
if d.GetClass() in ["DRAWSEGMENT", "MGRAPHIC", "PCB_SHAPE"]:
return self.parse_shape(d)
elif d.GetClass() in ["PTEXT", "MTEXT"]:
return self.parse_text(d)
s = self.parse_shape(d)
elif d.GetClass() in ["PTEXT", "MTEXT", "FP_TEXT", "PCB_TEXT"]:
s = self.parse_text(d)
elif d.GetClass().startswith("PCB_DIM"):
result.append(self.parse_dimension(d))
s = self.parse_text(d.Text())
else:
self.logger.info("Unsupported drawing class %s, skipping",
d.GetClass())
return None
if s:
result.append(s)
return result

def parse_edges(self, pcb):
edges = []
Expand All @@ -269,8 +340,7 @@ def parse_edges(self, pcb):
drawings.append(g)
for d in drawings:
if d.GetLayer() == pcbnew.Edge_Cuts:
parsed_drawing = self.parse_drawing(d)
if parsed_drawing:
for parsed_drawing in self.parse_drawing(d):
edges.append(parsed_drawing)
if bbox is None:
bbox = d.GetBoundingBox()
Expand All @@ -287,15 +357,13 @@ def parse_drawings_on_layers(self, drawings, f_layer, b_layer):
for d in drawings:
if d[1].GetLayer() not in [f_layer, b_layer]:
continue
drawing = self.parse_drawing(d[1])
if not drawing:
continue
if d[0] in ["ref", "val"]:
drawing[d[0]] = 1
if d[1].GetLayer() == f_layer:
front.append(drawing)
else:
back.append(drawing)
for drawing in self.parse_drawing(d[1]):
if d[0] in ["ref", "val"]:
drawing[d[0]] = 1
if d[1].GetLayer() == f_layer:
front.append(drawing)
else:
back.append(drawing)

return {
"F": front,
Expand All @@ -321,7 +389,7 @@ def parse_pad(self, pad):
layers.append("B")
pos = self.normalize(pad.GetPosition())
size = self.normalize(pad.GetSize())
angle = pad.GetOrientation() * -0.1
angle = self.normalize_angle(pad.GetOrientation())
shape_lookup = {
pcbnew.PAD_SHAPE_RECT: "rect",
pcbnew.PAD_SHAPE_OVAL: "oval",
Expand Down Expand Up @@ -401,8 +469,14 @@ def parse_footprints(self):
f_copy = pcbnew.MODULE(f)
else:
f_copy = pcbnew.FOOTPRINT(f)
f_copy.SetOrientation(0)
f_copy.SetPosition(pcbnew.wxPoint(0, 0))
try:
f_copy.SetOrientation(0)
except TypeError:
f_copy.SetOrientation(
pcbnew.EDA_ANGLE(0, pcbnew.TENTHS_OF_A_DEGREE_T))
pos = f_copy.GetPosition()
pos.x = pos.y = 0
f_copy.SetPosition(pos)
if hasattr(f_copy, 'GetFootprintRect'):
footprint_rect = f_copy.GetFootprintRect()
else:
Expand All @@ -411,7 +485,7 @@ def parse_footprints(self):
"pos": self.normalize(f.GetPosition()),
"relpos": self.normalize(footprint_rect.GetPosition()),
"size": self.normalize(footprint_rect.GetSize()),
"angle": f.GetOrientation() * 0.1,
"angle": self.normalize_angle(f.GetOrientation()),
}

# graphical drawings
Expand All @@ -420,13 +494,11 @@ def parse_footprints(self):
# we only care about copper ones, silkscreen is taken care of
if d.GetLayer() not in [pcbnew.F_Cu, pcbnew.B_Cu]:
continue
drawing = self.parse_drawing(d)
if not drawing:
continue
drawings.append({
"layer": "F" if d.GetLayer() == pcbnew.F_Cu else "B",
"drawing": drawing,
})
for drawing in self.parse_drawing(d):
drawings.append({
"layer": "F" if d.GetLayer() == pcbnew.F_Cu else "B",
"drawing": drawing,
})

# footprint pads
pads = []
Expand Down

0 comments on commit 50c790c

Please sign in to comment.