0321 from macmini
This commit is contained in:
251
3rdparty/plugins/org_openscopeproject_InteractiveHtmlBom/ecad/common.py
vendored
Normal file
251
3rdparty/plugins/org_openscopeproject_InteractiveHtmlBom/ecad/common.py
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
import math
|
||||
|
||||
from .svgpath import parse_path
|
||||
|
||||
|
||||
class ExtraFieldData(object):
|
||||
def __init__(self, fields, fields_by_ref, fields_by_index=None):
|
||||
self.fields = fields
|
||||
self.fields_by_ref = fields_by_ref
|
||||
self.fields_by_index = fields_by_index
|
||||
|
||||
|
||||
class EcadParser(object):
|
||||
|
||||
def __init__(self, file_name, config, logger):
|
||||
"""
|
||||
:param file_name: path to file that should be parsed.
|
||||
:param config: Config instance
|
||||
:param logger: logging object.
|
||||
"""
|
||||
self.file_name = file_name
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Abstract method that should be overridden in implementations.
|
||||
Performs all the parsing and returns a tuple of
|
||||
(pcbdata, components)
|
||||
pcbdata is described in DATAFORMAT.md
|
||||
components is list of Component objects
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def normalize_field_names(data):
|
||||
# type: (ExtraFieldData) -> ExtraFieldData
|
||||
def remap(ref_fields):
|
||||
return {f.lower(): v for (f, v) in
|
||||
sorted(ref_fields.items(), reverse=True) if v}
|
||||
|
||||
by_ref = {r: remap(d) for (r, d) in data.fields_by_ref.items()}
|
||||
if data.fields_by_index:
|
||||
by_index = {i: remap(d) for (i, d) in data.fields_by_index.items()}
|
||||
print([a.get("blah", "") for a in by_index.values()])
|
||||
else:
|
||||
by_index = None
|
||||
|
||||
field_map = {f.lower(): f for f in sorted(data.fields, reverse=True)}
|
||||
return ExtraFieldData(field_map.values(), by_ref, by_index)
|
||||
|
||||
def get_extra_field_data(self, file_name):
|
||||
"""
|
||||
Abstract method that may be overridden in implementations that support
|
||||
extra field data.
|
||||
:return: ExtraFieldData
|
||||
"""
|
||||
return ExtraFieldData([], {})
|
||||
|
||||
def parse_extra_data(self, file_name, normalize_case):
|
||||
"""
|
||||
Parses the file and returns extra field data.
|
||||
:param file_name: path to file containing extra data
|
||||
:param normalize_case: if true, normalize case so that
|
||||
"mpn", "Mpn", "MPN" fields are combined
|
||||
:return:
|
||||
"""
|
||||
data = self.get_extra_field_data(file_name)
|
||||
if normalize_case:
|
||||
data = self.normalize_field_names(data)
|
||||
return ExtraFieldData(
|
||||
sorted(data.fields), data.fields_by_ref, data.fields_by_index)
|
||||
|
||||
def latest_extra_data(self, extra_dirs=None):
|
||||
"""
|
||||
Abstract method that may be overridden in implementations that support
|
||||
extra field data.
|
||||
:param extra_dirs: List of extra directories to search.
|
||||
:return: File name of most recent file with extra field data.
|
||||
"""
|
||||
return None
|
||||
|
||||
def extra_data_file_filter(self):
|
||||
"""
|
||||
Abstract method that may be overridden in implementations that support
|
||||
extra field data.
|
||||
:return: File open dialog filter string, eg:
|
||||
"Netlist and xml files (*.net; *.xml)|*.net;*.xml"
|
||||
"""
|
||||
return None
|
||||
|
||||
def add_drawing_bounding_box(self, drawing, bbox):
|
||||
# type: (dict, BoundingBox) -> None
|
||||
|
||||
def add_segment():
|
||||
bbox.add_segment(drawing['start'][0], drawing['start'][1],
|
||||
drawing['end'][0], drawing['end'][1],
|
||||
drawing['width'] / 2)
|
||||
|
||||
def add_circle():
|
||||
bbox.add_circle(drawing['start'][0], drawing['start'][1],
|
||||
drawing['radius'] + drawing['width'] / 2)
|
||||
|
||||
def add_svgpath():
|
||||
width = drawing.get('width', 0)
|
||||
bbox.add_svgpath(drawing['svgpath'], width, self.logger)
|
||||
|
||||
def add_polygon():
|
||||
if 'polygons' not in drawing:
|
||||
add_svgpath()
|
||||
return
|
||||
polygon = drawing['polygons'][0]
|
||||
for point in polygon:
|
||||
bbox.add_point(point[0], point[1])
|
||||
|
||||
def add_arc():
|
||||
if 'svgpath' in drawing:
|
||||
add_svgpath()
|
||||
else:
|
||||
width = drawing.get('width', 0)
|
||||
xc, yc = drawing['start'][:2]
|
||||
a1 = drawing['startangle']
|
||||
a2 = drawing['endangle']
|
||||
r = drawing['radius']
|
||||
x1 = xc + math.cos(math.radians(a1))
|
||||
y1 = xc + math.sin(math.radians(a1))
|
||||
x2 = xc + math.cos(math.radians(a2))
|
||||
y2 = xc + math.sin(math.radians(a2))
|
||||
da = a2 - a1 if a2 > a1 else a2 + 360 - a1
|
||||
la = 1 if da > 180 else 0
|
||||
svgpath = 'M %s %s A %s %s 0 %s 1 %s %s' % \
|
||||
(x1, y1, r, r, la, x2, y2)
|
||||
bbox.add_svgpath(svgpath, width, self.logger)
|
||||
|
||||
{
|
||||
'segment': add_segment,
|
||||
'rect': add_segment, # bbox of a rect and segment are the same
|
||||
'circle': add_circle,
|
||||
'arc': add_arc,
|
||||
'polygon': add_polygon,
|
||||
'text': lambda: None, # text is not really needed for bounding box
|
||||
}.get(drawing['type'])()
|
||||
|
||||
|
||||
class Component(object):
|
||||
"""Simple data object to store component data needed for bom table."""
|
||||
|
||||
def __init__(self, ref, val, footprint, layer, attr=None, extra_fields={}):
|
||||
self.ref = ref
|
||||
self.val = val
|
||||
self.footprint = footprint
|
||||
self.layer = layer
|
||||
self.attr = attr
|
||||
self.extra_fields = extra_fields
|
||||
|
||||
|
||||
class BoundingBox(object):
|
||||
"""Geometry util to calculate and combine bounding box of simple shapes."""
|
||||
|
||||
def __init__(self):
|
||||
self._x0 = None
|
||||
self._y0 = None
|
||||
self._x1 = None
|
||||
self._y1 = None
|
||||
|
||||
def to_dict(self):
|
||||
# type: () -> dict
|
||||
return {
|
||||
"minx": self._x0,
|
||||
"miny": self._y0,
|
||||
"maxx": self._x1,
|
||||
"maxy": self._y1,
|
||||
}
|
||||
|
||||
def to_component_dict(self):
|
||||
# type: () -> dict
|
||||
return {
|
||||
"pos": [self._x0, self._y0],
|
||||
"relpos": [0, 0],
|
||||
"size": [self._x1 - self._x0, self._y1 - self._y0],
|
||||
"angle": 0,
|
||||
}
|
||||
|
||||
def add(self, other):
|
||||
"""Add another bounding box.
|
||||
:type other: BoundingBox
|
||||
"""
|
||||
if other._x0 is not None:
|
||||
self.add_point(other._x0, other._y0)
|
||||
self.add_point(other._x1, other._y1)
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _rotate(x, y, rx, ry, angle):
|
||||
sin = math.sin(math.radians(angle))
|
||||
cos = math.cos(math.radians(angle))
|
||||
new_x = rx + (x - rx) * cos - (y - ry) * sin
|
||||
new_y = ry + (x - rx) * sin + (y - ry) * cos
|
||||
return new_x, new_y
|
||||
|
||||
def add_point(self, x, y, rx=0, ry=0, angle=0):
|
||||
x, y = self._rotate(x, y, rx, ry, angle)
|
||||
if self._x0 is None:
|
||||
self._x0 = x
|
||||
self._y0 = y
|
||||
self._x1 = x
|
||||
self._y1 = y
|
||||
else:
|
||||
self._x0 = min(self._x0, x)
|
||||
self._y0 = min(self._y0, y)
|
||||
self._x1 = max(self._x1, x)
|
||||
self._y1 = max(self._y1, y)
|
||||
return self
|
||||
|
||||
def add_segment(self, x0, y0, x1, y1, r):
|
||||
self.add_circle(x0, y0, r)
|
||||
self.add_circle(x1, y1, r)
|
||||
return self
|
||||
|
||||
def add_rectangle(self, x, y, w, h, angle=0):
|
||||
self.add_point(x - w / 2, y - h / 2, x, y, angle)
|
||||
self.add_point(x + w / 2, y - h / 2, x, y, angle)
|
||||
self.add_point(x - w / 2, y + h / 2, x, y, angle)
|
||||
self.add_point(x + w / 2, y + h / 2, x, y, angle)
|
||||
return self
|
||||
|
||||
def add_circle(self, x, y, r):
|
||||
self.add_point(x - r, y)
|
||||
self.add_point(x, y - r)
|
||||
self.add_point(x + r, y)
|
||||
self.add_point(x, y + r)
|
||||
return self
|
||||
|
||||
def add_svgpath(self, svgpath, width, logger):
|
||||
w = width / 2
|
||||
for segment in parse_path(svgpath, logger):
|
||||
x0, x1, y0, y1 = segment.bbox()
|
||||
self.add_point(x0 - w, y0 - w)
|
||||
self.add_point(x1 + w, y1 + w)
|
||||
|
||||
def pad(self, amount):
|
||||
"""Add small padding to the box."""
|
||||
if self._x0 is not None:
|
||||
self._x0 -= amount
|
||||
self._y0 -= amount
|
||||
self._x1 += amount
|
||||
self._y1 += amount
|
||||
|
||||
def initialized(self):
|
||||
return self._x0 is not None
|
Reference in New Issue
Block a user