class Line(Node): def __init__(self, **kwargs): Node.__init__(self) self.start_pos = Point(kwargs['start']) self.end_pos = Point(kwargs['end']) self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): render_start_pos = self.getRealPosition(self.start_pos) render_end_pos = self.getRealPosition(self.end_pos) min_x = min([render_start_pos.x, render_end_pos.x]) min_y = min([render_start_pos.y, render_end_pos.y]) max_x = max([render_start_pos.x, render_end_pos.x]) max_y = max([render_start_pos.y, render_end_pos.y]) return Node.calculateBoundingBox({'min': Point(min_x, min_y), 'max': Point(max_x, max_y)}) def _getRenderTreeText(self): render_strings = ['fp_line'] render_strings.append(self.start_pos.render('(start {x} {y})')) render_strings.append(self.end_pos.render('(end {x} {y})')) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Line(Node): _width_default = 0.15 _layer_default = "F.SilkS" def __init__(self, **kwargs): Node.__init__(self) self.start_pos = Point(kwargs["start"], **kwargs) self.end_pos = Point(kwargs["end"], **kwargs) self.layer = kwargs.get("layer", "F.SilkS") self.width = kwargs.get("width") def calculateBoundingBox(self): render_start_pos = self.getRealPosition(self.start_pos) render_end_pos = self.getRealPosition(self.end_pos) min_x = min([render_start_pos.x, render_end_pos.x]) min_y = min([render_start_pos.y, render_end_pos.y]) max_x = max([render_start_pos.x, render_end_pos.x]) max_y = max([render_start_pos.y, render_end_pos.y]) return Node.calculateBoundingBox({"min": Point(min_x, min_y), "max": Point(max_x, max_y)}) def _getRenderTreeText(self): render_strings = ["fp_line"] render_strings.append(self.start_pos.render("(start {x} {y})")) render_strings.append(self.end_pos.render("(end {x} {y})")) render_strings.append("(layer {layer})".format(layer=self.layer)) render_strings.append("(width {width})".format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += " ({})".format(" ".join(render_strings)) return render_text
class Circle(Node): r"""Add a Circle to the render tree :param \**kwargs: See below :Keyword Arguments: * *center* (``Point``) -- center of the circle * *radius* (``float``) -- radius of the circle * *layer* (``str``) -- layer on which the circle is drawn (default: 'F.SilkS') * *width* (``float``) -- width of the circle line (default: None, which means auto detection) :Example: >>> from KicadModTree import * >>> Circle(center=[0, 0], radius=1.5, layer='F.SilkS') """ def __init__(self, **kwargs): Node.__init__(self) self.center_pos = Point(kwargs['center']) self.radius = kwargs['radius'] self.end_pos = Point( [self.center_pos.x + self.radius, self.center_pos.y]) self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): min_x = self.center_pos.x - self.radius min_y = self.center_pos.y - self.radius max_x = self.center_pos.x + self.radius max_y = self.center_pos.y + self.radius return Node.calculateBoundingBox({ 'min': ParseXY(min_x, min_y), 'max': ParseXY(max_x, max_y) }) def _getRenderTreeText(self): render_strings = ['fp_circle'] render_strings.append(self.center_pos.render('(center {x} {y})')) render_strings.append(self.end_pos.render('(end {x} {y})')) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Line(Node): r"""Add a Line to the render tree :param \**kwargs: See below :Keyword Arguments: * *start* (``Point``) -- start point of the line * *end* (``Point``) -- end point of the line * *layer* (``str``) -- layer on which the line is drawn (default: 'F.SilkS') * *width* (``float``) -- width of the line (default: None, which means auto detection) :Example: >>> from KicadModTree import * >>> Line(start=[1, 0], end=[-1, 0], layer='F.SilkS') """ def __init__(self, **kwargs): Node.__init__(self) self.start_pos = Point(kwargs['start']) self.end_pos = Point(kwargs['end']) self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): render_start_pos = self.getRealPosition(self.start_pos) render_end_pos = self.getRealPosition(self.end_pos) min_x = min([render_start_pos.x, render_end_pos.x]) min_y = min([render_start_pos.y, render_end_pos.y]) max_x = max([render_start_pos.x, render_end_pos.x]) max_y = max([render_start_pos.y, render_end_pos.y]) return Node.calculateBoundingBox({ 'min': Point(min_x, min_y), 'max': Point(max_x, max_y) }) def _getRenderTreeText(self): render_strings = ['fp_line'] render_strings.append(self.start_pos.render('(start {x} {y})')) render_strings.append(self.end_pos.render('(end {x} {y})')) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Circle(Node): def __init__(self, **kwargs): Node.__init__(self) self.center_pos = Point(kwargs['center']) self.radius = kwargs['radius'] self.end_pos = {'x': self.center_pos.x+self.radius, 'y': self.center_pos.y} self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): min_x = self.center_pos.x-self.radius min_y = self.center_pos.y-self.radius max_x = self.center_pos.x+self.radius max_y = self.center_pos.y+self.radius return Node.calculateBoundingBox({'min': ParseXY(min_x, min_y), 'max': ParseXY(max_x, max_y)}) def _getRenderTreeText(self): render_strings = ['fp_circle'] render_strings.append(self.center_pos.render('(center {x} {y})')) render_strings.append(self.end_pos.render('(end {x} {y})')) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Model(Node): r"""Add a 3D-Model to the render tree :param \**kwargs: See below :Keyword Arguments: * *filename* (``str``) -- name of the 3d-model file * *at* (``Point``) -- position of the model * *scale* (``Point``) -- scale of the model * *rotate* (``Point``) -- rotation of the model :Example: >>> from KicadModTree import * >>> Model(filename="example.3dshapes/example_footprint.wrl", ... at=[0, 0, 0], scale=[1, 1, 1], rotate=[0, 0, 0]) """ def __init__(self, **kwargs): Node.__init__(self) self.filename = kwargs['filename'] self.at = Point(kwargs['at']) self.scale = Point(kwargs['scale']) self.rotate = Point(kwargs['rotate']) def _getRenderTreeText(self): render_text = Node._getRenderTreeText(self) render_string = [ 'filename: {filename}'.format(filename=self.filename), 'at: {at}'.format(at=self.at.render('(xyz {x} {y} {z})')), 'scale: {scale}'.format( scale=self.scale.render('(xyz {x} {y} {z})')), 'rotate: {rotate}'.format( rotate=self.rotate.render('(xyz {x} {y} {z})')) ] render_text += " [{}]".format(", ".join(render_string)) return render_text
class Model(Node): def __init__(self, **kwargs): Node.__init__(self) self.filename = kwargs['filename'] self.at = Point(kwargs['at']) self.scale = Point(kwargs['scale']) self.rotate = Point(kwargs['rotate']) def _getRenderTreeText(self): render_text = Node._getRenderTreeText(self) render_string = ['filename: {filename}'.format(filename=self.filename), 'at: {at}'.format(at=self.at.render('(xyz {x} {y} {z})')), 'scale: {scale}'.format(scale=self.scale.render('(xyz {x} {y} {z})')), 'rotate: {rotate}'.format(rotate=self.rotate.render('(xyz {x} {y} {z})'))] render_text += " [{}]".format(", ".join(render_string)) return render_text
class Text(Node): def __init__(self, **kwargs): Node.__init__(self) self.type = kwargs['type'] self.text = kwargs['text'] self.at = Point(kwargs['at']) self.rotation = kwargs.get('rotation', 0) self.layer = kwargs['layer'] self.size = Point(kwargs.get('size', [1, 1])) self.thickness = kwargs.get('thickness', 0.15) self.hide = kwargs.get('hide', False) def calculateBoundingBox(self): width = len(self.text) * self.size['x'] height = self.size['y'] min_x = self.at[x] - width / 2. min_y = self.at[y] - height / 2. max_x = self.at[x] + width / 2. max_y = self.at[y] + height / 2. return Node.calculateBoundingBox({ 'min': Point(min_x, min_y), 'max': Point(max_x, max_y) }) def _getRenderTreeText(self): render_text = Node._getRenderTreeText(self) render_string = [ 'type: "{}"'.format(self.type), 'text: "{}"'.format(self.text), 'at: {}'.format(self.at.render('(at {x} {y})')), 'layer: {}'.format(self.layer), 'size: {}'.format(self.size.render('(size {x} {y})')), 'thickness: {}'.format(self.thickness) ] render_text += " [{}]".format(", ".join(render_string)) return render_text
class Model(Node): def __init__(self, **kwargs): Node.__init__(self) self.filename = kwargs['filename'] self.at = Point(kwargs.get('at', [0, 0, 0])) self.scale = Point(kwargs.get('scale', [1, 1, 1])) self.rotate = Point(kwargs.get('rotate', [0, 0, 0])) def _getRenderTreeText(self): render_text = Node._getRenderTreeText(self) render_string = [ 'filename: {filename}'.format(filename=self.filename), 'at: {at}'.format(at=self.at.render('(xyz {x} {y} {z})')), 'scale: {scale}'.format( scale=self.scale.render('(xyz {x} {y} {z})')), 'rotate: {rotate}'.format( rotate=self.rotate.render('(xyz {x} {y} {z})')) ] render_text += " [{}]".format(", ".join(render_string)) return render_text
class Arc(Node): def __init__(self, **kwargs): Node.__init__(self) self.center_pos = Point(kwargs['center']) self.start_pos = Point(kwargs['start']) self.angle = kwargs['angle'] self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): # TODO: finish implementation min_x = min(self.start_pos.x, self._calulateEndPos().x) min_y = min(self.start_pos.x, self._calulateEndPos().y) max_x = max(self.start_pos.x, self._calulateEndPos().x) max_y = max(self.start_pos.x, self._calulateEndPos().y) ''' for angle in range(4): float_angle = angle * math.pi/2. start_angle = _calculateStartAngle(self) end_angle = start_angle + math.radians(self.angle) # TODO: +- pi border if float_angle < start_angle: continue if float_angle > end_angle: continue print("TODO: add angle side: {1}".format(float_angle)) ''' return Node.calculateBoundingBox({'min': Point((min_x, min_y)), 'max': Point((max_x, max_y))}) def _calulateEndPos(self): radius = self._calculateRadius() angle = self._calculateStartAngle() + math.radians(self.angle) return Point(math.sin(angle)*radius, math.cos(angle)*radius) def _calculateRadius(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.sqrt(math.pow(x_size, 2) + math.pow(y_size, 2)) def _calculateStartAngle(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.atan2(y_size, x_size) def _getRenderTreeText(self): render_strings = ['fp_arc'] render_strings.append(self.center_pos.render('(center {x} {y})')) render_strings.append(self.start_pos.render('(start {x} {y})')) render_strings.append('(angle {angle})'.format(angle=self.angle)) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Text(Node): r"""Add a Line to the render tree :param \**kwargs: See below :Keyword Arguments: * *type* (``str``) -- type of text * *text* (``str``) -- text which is been visualized * *at* (``Point``) -- position of text * *rotation* (``float``) -- rotation of text * *layer* (``str``) -- layer on which the text is drawn * *size* (``Point``) -- size of the text * *thickness* (``float``) -- thickness of the text * *hide* (``bool``) -- hide text :Example: >>> from KicadModTree import * >>> Text(type='reference', text='REF**', at=[0, -3], layer='F.SilkS') >>> Text(type='value', text="footprint name", at=[0, 3], layer='F.Fab') """ def __init__(self, **kwargs): Node.__init__(self) self.type = kwargs['type'] self.text = kwargs['text'] self.at = Point(kwargs['at']) self.rotation = kwargs.get('rotation', 0) self.layer = kwargs['layer'] self.size = Point(kwargs.get('size', [1, 1])) self.thickness = kwargs.get('thickness', 0.15) self.hide = kwargs.get('hide', False) def calculateBoundingBox(self): width = len(self.text)*self.size['x'] height = self.size['y'] min_x = self.at[x]-width/2. min_y = self.at[y]-height/2. max_x = self.at[x]+width/2. max_y = self.at[y]+height/2. return Node.calculateBoundingBox({'min': Point(min_x, min_y), 'max': Point(max_x, max_y)}) def _getRenderTreeText(self): render_text = Node._getRenderTreeText(self) render_string = ['type: "{}"'.format(self.type), 'text: "{}"'.format(self.text), 'at: {}'.format(self.at.render('(at {x} {y})')), 'layer: {}'.format(self.layer), 'size: {}'.format(self.size.render('(size {x} {y})')), 'thickness: {}'.format(self.thickness)] render_text += " [{}]".format(", ".join(render_string)) return render_text
class Pad(Node): TYPE_THT = 'thru_hole' TYPE_SMT = 'smd' TYPE_CONNECT = 'connect' TYPE_NPTH = 'np_thru_hole' _TYPES = [TYPE_THT, TYPE_SMT, TYPE_CONNECT, TYPE_NPTH] SHAPE_CIRCLE = 'circle' SHAPE_OVAL = 'oval' SHAPE_RECT = 'rect' SHAPE_TRAPEZE = 'trapezoid' _SHAPES = [SHAPE_CIRCLE, SHAPE_OVAL, SHAPE_RECT, SHAPE_TRAPEZE] def __init__(self, **kwargs): Node.__init__(self) self._initNumber(**kwargs) self._initType(**kwargs) self._initShape(**kwargs) self._initPosition(**kwargs) self._initSize(**kwargs) self._initOffset(**kwargs) self._initDrill(**kwargs) # requires pad type and offset self._initSolderPasteMargin(**kwargs) self._initLayers(**kwargs) def _initNumber(self, **kwargs): self.number = kwargs.get('number') def _initType(self, **kwargs): if not kwargs.get('type'): raise KeyError('type not declared (like "type=Pad.TYPE_THT")') self.type = kwargs.get('type') if self.type not in Pad._TYPES: raise ValueError( '{type} is an invalid type for pads'.format(type=self.type)) def _initShape(self, **kwargs): if not kwargs.get('shape'): raise KeyError( 'shape not declared (like "shape=Pad.SHAPE_CIRCLE")') self.shape = kwargs.get('shape') if self.shape not in Pad._SHAPES: raise ValueError('{shape} is an invalid shape for pads'.format( shape=self.shape)) def _initPosition(self, **kwargs): if not kwargs.get('at'): raise KeyError('center position not declared (like "at=[0,0]")') self.at = Point(kwargs.get('at')) self.rotation = kwargs.get('rotation', 0) def _initSize(self, **kwargs): if not kwargs.get('size'): raise KeyError('pad size not declared (like "size=[1,1]")') if type(kwargs.get('size')) in [int, float]: # when the attribute is a simple number, use it for x and y self.size = Point([kwargs.get('size'), kwargs.get('size')]) else: self.size = Point(kwargs.get('size')) def _initOffset(self, **kwargs): self.offset = Point(kwargs.get('offset', [0, 0])) def _initDrill(self, **kwargs): if self.type in [Pad.TYPE_THT, Pad.TYPE_NPTH]: if not kwargs.get('drill'): raise KeyError('drill size required (like "drill=1")') if type(kwargs.get('drill')) in [int, float]: # when the attribute is a simple number, use it for x and y self.drill = Point([kwargs.get('drill'), kwargs.get('drill')]) else: self.drill = Point(kwargs.get('drill')) if self.drill.x < 0 or self.drill.y < 0: raise ValueError("negative drill size not allowed") else: self.drill = None if kwargs.get('drill'): pass # TODO: throw warning because drill is not supported def _initSolderPasteMargin(self, **kwargs): self.solder_paste_margin_ratio = kwargs.get( 'solder_paste_margin_ratio', 0) def _initLayers(self, **kwargs): if not kwargs.get('layers'): raise KeyError( 'layers not declared (like "layers=[\'*.Cu\', \'*.Mask\', \'F.SilkS\']")' ) self.layers = kwargs.get('layers') def calculateBoundingBox(self): return Node.calculateBoundingBox(self) def _getRenderTreeText(self): render_strings = ['pad'] render_strings.append(lispString(self.number)) render_strings.append(lispString(self.type)) render_strings.append(lispString(self.shape)) render_strings.append(self.at.render('(at {x} {y})')) render_strings.append(self.size.render('(size {x} {y})')) render_strings.append('(drill {})'.format(self.drill)) render_strings.append('(layers {})'.format(' '.join(self.layers))) render_text = Node._getRenderTreeText(self) render_text += '({})'.format(' '.join(render_strings)) return render_text
class Arc(Node): r"""Add an Arc to the render tree :param \**kwargs: See below :Keyword Arguments: * *center* (``Point``) -- center of arc * *start* (``Point``) -- start point of arc * *angle* (``float``) -- angle of arc * *layer* (``str``) -- layer on which the arc is drawn (default: 'F.SilkS') * *width* (``float``) -- width of the arc line (default: None, which means auto detection) :Example: >>> from KicadModTree import * >>> Arc(center=[0, 0], start=[-1, 0], angle=180, layer='F.SilkS') """ def __init__(self, **kwargs): Node.__init__(self) self.center_pos = Point(kwargs['center']) self.start_pos = Point(kwargs['start']) self.angle = kwargs['angle'] self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): # TODO: finish implementation min_x = min(self.start_pos.x, self._calulateEndPos().x) min_y = min(self.start_pos.x, self._calulateEndPos().y) max_x = max(self.start_pos.x, self._calulateEndPos().x) max_y = max(self.start_pos.x, self._calulateEndPos().y) ''' for angle in range(4): float_angle = angle * math.pi/2. start_angle = _calculateStartAngle(self) end_angle = start_angle + math.radians(self.angle) # TODO: +- pi border if float_angle < start_angle: continue if float_angle > end_angle: continue print("TODO: add angle side: {1}".format(float_angle)) ''' return Node.calculateBoundingBox({ 'min': Point((min_x, min_y)), 'max': Point((max_x, max_y)) }) def _calulateEndPos(self): radius = self._calculateRadius() angle = self._calculateStartAngle() + math.radians(self.angle) return Point(math.sin(angle) * radius, math.cos(angle) * radius) def _calculateRadius(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.sqrt(math.pow(x_size, 2) + math.pow(y_size, 2)) def _calculateStartAngle(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.atan2(y_size, x_size) def _getRenderTreeText(self): render_strings = ['fp_arc'] render_strings.append(self.center_pos.render('(center {x} {y})')) render_strings.append(self.start_pos.render('(start {x} {y})')) render_strings.append('(angle {angle})'.format(angle=self.angle)) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text
class Pad(Node): r"""Add a Pad to the render tree :param \**kwargs: See below :Keyword Arguments: * *number* (``int``, ``str``) -- number/name of the pad (default: \"\") * *type* (``Pad.TYPE_THT``, ``Pad.TYPE_SMT``, ``Pad.TYPE_CONNECT``, ``Pad.TYPE_NPTH``) -- type of the pad * *shape* (``Pad.SHAPE_CIRCLE``, ``Pad.SHAPE_OVAL``, ``Pad.SHAPE_RECT``, ``Pad.SHAPE_TRAPEZE``) -- shape of the pad * *at* (``Point``) -- center position of the pad * *rotation* (``float``) -- rotation of the pad * *size* (``float``, ``Point``) -- size of the pad * *offset* (``Point``) -- offset of the pad * *drill* (``float``, ``Point``) -- drill-size of the pad * *solder_paste_margin_ratio* (``float``) -- solder paste margin ratio of the pad (default: 0) * *layers* (``Pad.LAYERS_SMT``, ``Pad.LAYERS_THT``, ``Pad.LAYERS_NPTH``) -- layers on which are used for the pad :Example: >>> from KicadModTree import * >>> Pad(number=1, type=Pad.TYPE_THT, shape=Pad.SHAPE_RECT, ... at=[0, 0], size=[2, 2], drill=1.2, layers=Pad.LAYERS_THT) """ TYPE_THT = 'thru_hole' TYPE_SMT = 'smd' TYPE_CONNECT = 'connect' TYPE_NPTH = 'np_thru_hole' _TYPES = [TYPE_THT, TYPE_SMT, TYPE_CONNECT, TYPE_NPTH] SHAPE_CIRCLE = 'circle' SHAPE_OVAL = 'oval' SHAPE_RECT = 'rect' SHAPE_TRAPEZE = 'trapezoid' _SHAPES = [SHAPE_CIRCLE, SHAPE_OVAL, SHAPE_RECT, SHAPE_TRAPEZE] LAYERS_SMT = ['F.Cu', 'F.Mask', 'F.Paste'] LAYERS_THT = ['*.Cu', '*.Mask'] LAYERS_NPTH = ['*.Cu', '*.Mask'] def __init__(self, **kwargs): Node.__init__(self) self._initNumber(**kwargs) self._initType(**kwargs) self._initShape(**kwargs) self._initPosition(**kwargs) self._initSize(**kwargs) self._initOffset(**kwargs) self._initDrill(**kwargs) # requires pad type and offset self._initSolderPasteMargin(**kwargs) self._initLayers(**kwargs) def _initNumber(self, **kwargs): self.number = kwargs.get('number', "") # default to an un-numbered pad def _initType(self, **kwargs): if not kwargs.get('type'): raise KeyError('type not declared (like "type=Pad.TYPE_THT")') self.type = kwargs.get('type') if self.type not in Pad._TYPES: raise ValueError('{type} is an invalid type for pads'.format(type=self.type)) def _initShape(self, **kwargs): if not kwargs.get('shape'): raise KeyError('shape not declared (like "shape=Pad.SHAPE_CIRCLE")') self.shape = kwargs.get('shape') if self.shape not in Pad._SHAPES: raise ValueError('{shape} is an invalid shape for pads'.format(shape=self.shape)) def _initPosition(self, **kwargs): if not kwargs.get('at'): raise KeyError('center position not declared (like "at=[0,0]")') self.at = Point(kwargs.get('at')) self.rotation = kwargs.get('rotation', 0) def _initSize(self, **kwargs): if not kwargs.get('size'): raise KeyError('pad size not declared (like "size=[1,1]")') if type(kwargs.get('size')) in [int, float]: # when the attribute is a simple number, use it for x and y self.size = Point([kwargs.get('size'), kwargs.get('size')]) else: self.size = Point(kwargs.get('size')) def _initOffset(self, **kwargs): self.offset = Point(kwargs.get('offset', [0, 0])) def _initDrill(self, **kwargs): if self.type in [Pad.TYPE_THT, Pad.TYPE_NPTH]: if not kwargs.get('drill'): raise KeyError('drill size required (like "drill=1")') if type(kwargs.get('drill')) in [int, float]: # when the attribute is a simple number, use it for x and y self.drill = Point([kwargs.get('drill'), kwargs.get('drill')]) else: self.drill = Point(kwargs.get('drill')) if self.drill.x < 0 or self.drill.y < 0: raise ValueError("negative drill size not allowed") else: self.drill = None if kwargs.get('drill'): pass # TODO: throw warning because drill is not supported def _initSolderPasteMargin(self, **kwargs): self.solder_paste_margin_ratio = kwargs.get('solder_paste_margin_ratio', 0) def _initLayers(self, **kwargs): if not kwargs.get('layers'): raise KeyError('layers not declared (like "layers=[\'*.Cu\', \'*.Mask\', \'F.SilkS\']")') self.layers = kwargs.get('layers') # calculate the outline of a pad def calculateBoundingBox(self): return Node.calculateBoundingBox(self) def _getRenderTreeText(self): render_strings = ['padd'] render_strings.append(lispString(self.number)) render_strings.append(lispString(self.type)) render_strings.append(lispString(self.shape)) render_strings.append(self.at.render('(at {x} {y})')) render_strings.append(self.size.render('(size {x} {y})')) print self.offset if self.offset == [0, 0]: render_strings.append('(drill {})'.format(self.drill)) else: render_strings.append('(drill {} (offset {} {}))'.format(self.drill, self.offset[0], self.offset[1])) render_strings.append('(layers {})'.format(' '.join(self.layers))) render_text = Node._getRenderTreeText(self) render_text += '({})'.format(' '.join(render_strings)) return render_text
class Pad(Node): TYPE_THT = 'thru_hole' TYPE_SMT = 'smd' TYPE_CONNECT = 'connect' TYPE_NPTH = 'np_thru_hole' _TYPES = [TYPE_THT, TYPE_SMT, TYPE_CONNECT, TYPE_NPTH] SHAPE_CIRCLE = 'circle' SHAPE_OVAL = 'oval' SHAPE_RECT = 'rect' SHAPE_TRAPEZE = 'trapezoid' _SHAPES = [SHAPE_CIRCLE, SHAPE_OVAL, SHAPE_RECT, SHAPE_TRAPEZE] LAYERS_SMT = ['F.Cu','F.Mask','F.Paste'] LAYERS_THT = ['*.Cu','*.Mask'] LAYERS_NPTH = ['*.Cu'] def __init__(self, **kwargs): Node.__init__(self) self._initNumber(**kwargs) self._initType(**kwargs) self._initShape(**kwargs) self._initPosition(**kwargs) self._initSize(**kwargs) self._initOffset(**kwargs) self._initDrill(**kwargs) # requires pad type and offset self._initSolderPasteMargin(**kwargs) self._initLayers(**kwargs) def _initNumber(self, **kwargs): self.number = kwargs.get('number','""') #default to an un-numbered pad def _initType(self, **kwargs): if not kwargs.get('type'): raise KeyError('type not declared (like "type=Pad.TYPE_THT")') self.type = kwargs.get('type') if self.type not in Pad._TYPES: raise ValueError('{type} is an invalid type for pads'.format(type=self.type)) def _initShape(self, **kwargs): if not kwargs.get('shape'): raise KeyError('shape not declared (like "shape=Pad.SHAPE_CIRCLE")') self.shape = kwargs.get('shape') if self.shape not in Pad._SHAPES: raise ValueError('{shape} is an invalid shape for pads'.format(shape=self.shape)) def _initPosition(self, **kwargs): if not kwargs.get('at'): raise KeyError('center position not declared (like "at=[0,0]")') self.at = Point(kwargs.get('at')) self.rotation = kwargs.get('rotation', 0) def _initSize(self, **kwargs): if not kwargs.get('size'): raise KeyError('pad size not declared (like "size=[1,1]")') if type(kwargs.get('size')) in [int, float]: # when the attribute is a simple number, use it for x and y self.size = Point([kwargs.get('size'), kwargs.get('size')]) else: self.size = Point(kwargs.get('size')) def _initOffset(self, **kwargs): self.offset = Point(kwargs.get('offset', [0, 0])) def _initDrill(self, **kwargs): if self.type in [Pad.TYPE_THT, Pad.TYPE_NPTH]: if not kwargs.get('drill'): raise KeyError('drill size required (like "drill=1")') if type(kwargs.get('drill')) in [int, float]: # when the attribute is a simple number, use it for x and y self.drill = Point([kwargs.get('drill'), kwargs.get('drill')]) else: self.drill = Point(kwargs.get('drill')) if self.drill.x < 0 or self.drill.y < 0: raise ValueError("negative drill size not allowed") else: self.drill = None if kwargs.get('drill'): pass # TODO: throw warning because drill is not supported def _initSolderPasteMargin(self, **kwargs): self.solder_paste_margin_ratio = kwargs.get('solder_paste_margin_ratio', 0) def _initLayers(self, **kwargs): if not kwargs.get('layers'): raise KeyError('layers not declared (like "layers=[\'*.Cu\', \'*.Mask\', \'F.SilkS\']")') self.layers = kwargs.get('layers') #calculate the outline of a pad def calculateBoundingBox(self): return Node.calculateBoundingBox(self) def _getRenderTreeText(self): render_strings = ['pad'] render_strings.append(lispString(self.number)) render_strings.append(lispString(self.type)) render_strings.append(lispString(self.shape)) render_strings.append(self.at.render('(at {x} {y})')) render_strings.append(self.size.render('(size {x} {y})')) render_strings.append('(drill {})'.format(self.drill)) render_strings.append('(layers {})'.format(' '.join(self.layers))) render_text = Node._getRenderTreeText(self) render_text += '({})'.format(' '.join(render_strings)) return render_text
class Arc(Node): def __init__(self, **kwargs): Node.__init__(self) self.center_pos = Point(kwargs['center']) self.start_pos = Point(kwargs['start']) self.angle = kwargs['angle'] self.layer = kwargs.get('layer', 'F.SilkS') self.width = kwargs.get('width') def calculateBoundingBox(self): # TODO: finish implementation min_x = min(self.start_pos.x, self._calulateEndPos().x) min_y = min(self.start_pos.x, self._calulateEndPos().y) max_x = max(self.start_pos.x, self._calulateEndPos().x) max_y = max(self.start_pos.x, self._calulateEndPos().y) ''' for angle in range(4): float_angle = angle * math.pi/2. start_angle = _calculateStartAngle(self) end_angle = start_angle + math.radians(self.angle) # TODO: +- pi border if float_angle < start_angle: continue if float_angle > end_angle: continue print("TODO: add angle side: {1}".format(float_angle)) ''' return Node.calculateBoundingBox({ 'min': Point((min_x, min_y)), 'max': Point((max_x, max_y)) }) def _calulateEndPos(self): radius = self._calculateRadius() angle = self._calculateStartAngle() + math.radians(self.angle) return Point(math.sin(angle) * radius, math.cos(angle) * radius) def _calculateRadius(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.sqrt(math.pow(x_size, 2) + math.pow(y_size, 2)) def _calculateStartAngle(self): x_size = self.start_pos.x - self.center_pos.x y_size = self.start_pos.y - self.center_pos.y return math.atan2(y_size, x_size) def _getRenderTreeText(self): render_strings = ['fp_arc'] render_strings.append(self.center_pos.render('(center {x} {y})')) render_strings.append(self.start_pos.render('(start {x} {y})')) render_strings.append('(angle {angle})'.format(angle=self.angle)) render_strings.append('(layer {layer})'.format(layer=self.layer)) render_strings.append('(width {width})'.format(width=self.width)) render_text = Node._getRenderTreeText(self) render_text += ' ({})'.format(' '.join(render_strings)) return render_text