Source code for KicadModTree.KicadFileHandler

# KicadModTree is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# KicadModTree is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kicad-footprint-generator. If not, see < http://www.gnu.org/licenses/ >.
#
# (C) 2016-2018 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>

from KicadModTree.FileHandler import FileHandler
from KicadModTree.util.kicad_util import *
from KicadModTree.nodes.base.Pad import Pad  # TODO: why .KicadModTree is not enough?
from KicadModTree.nodes.base.Arc import Arc
from KicadModTree.nodes.base.Circle import Circle
from KicadModTree.nodes.base.Line import Line
from KicadModTree.nodes.base.Polygon import Polygon


DEFAULT_LAYER_WIDTH = {'F.SilkS': 0.12,
                       'B.SilkS': 0.12,
                       'F.Fab': 0.10,
                       'B.Fab': 0.10,
                       'F.CrtYd': 0.05,
                       'B.CrtYd': 0.05}

DEFAULT_WIDTH_POLYGON_PAD = 0

DEFAULT_WIDTH = 0.15


def _get_layer_width(layer, width=None):
    if width is not None:
        return width
    else:
        return DEFAULT_LAYER_WIDTH.get(layer, DEFAULT_WIDTH)


[docs]class KicadFileHandler(FileHandler): r"""Implementation of the FileHandler for .kicad_mod files :param kicad_mod: Main object representing the footprint :type kicad_mod: ``KicadModTree.Footprint`` :Example: >>> from KicadModTree import * >>> kicad_mod = Footprint("example_footprint") >>> file_handler = KicadFileHandler(kicad_mod) >>> file_handler.writeFile('example_footprint.kicad_mod') """ def __init__(self, kicad_mod): FileHandler.__init__(self, kicad_mod)
[docs] def serialize(self, **kwargs): r"""Get a valid string representation of the footprint in the .kicad_mod format :Example: >>> from KicadModTree import * >>> kicad_mod = Footprint("example_footprint") >>> file_handler = KicadFileHandler(kicad_mod) >>> print(file_handler.serialize()) """ sexpr = ['module', self.kicad_mod.name, ['layer', 'F.Cu'], ['tedit', formatTimestamp(kwargs.get('timestamp'))], SexprSerializer.NEW_LINE ] # NOQA if self.kicad_mod.description: sexpr.append(['descr', self.kicad_mod.description]) sexpr.append(SexprSerializer.NEW_LINE) if self.kicad_mod.tags: sexpr.append(['tags', self.kicad_mod.tags]) sexpr.append(SexprSerializer.NEW_LINE) if self.kicad_mod.attribute: sexpr.append(['attr', self.kicad_mod.attribute]) sexpr.append(SexprSerializer.NEW_LINE) if self.kicad_mod.maskMargin: sexpr.append(['solder_mask_margin', self.kicad_mod.maskMargin]) sexpr.append(SexprSerializer.NEW_LINE) if self.kicad_mod.pasteMargin: sexpr.append(['solder_paste_margin', self.kicad_mod.pasteMargin]) sexpr.append(SexprSerializer.NEW_LINE) if self.kicad_mod.pasteMarginRatio: sexpr.append(['solder_paste_ratio', self.kicad_mod.pasteMarginRatio]) sexpr.append(SexprSerializer.NEW_LINE) sexpr.extend(self._serializeTree()) return str(SexprSerializer(sexpr))
def _serializeTree(self): nodes = self.kicad_mod.serialize() grouped_nodes = {} for single_node in nodes: node_type = single_node.__class__.__name__ current_nodes = grouped_nodes.get(node_type, []) current_nodes.append(single_node) grouped_nodes[node_type] = current_nodes sexpr = [] # serialize initial text nodes if 'Text' in grouped_nodes: reference_nodes = list(filter(lambda node: node.type == 'reference', grouped_nodes['Text'])) for node in reference_nodes: sexpr.append(self._serialize_Text(node)) sexpr.append(SexprSerializer.NEW_LINE) grouped_nodes['Text'].remove(node) value_nodes = list(filter(lambda node: node.type == 'value', grouped_nodes['Text'])) for node in value_nodes: sexpr.append(self._serialize_Text(node)) sexpr.append(SexprSerializer.NEW_LINE) grouped_nodes['Text'].remove(node) for key, value in sorted(grouped_nodes.items()): # check if key is a base node, except Model if key not in {'Arc', 'Circle', 'Line', 'Pad', 'Polygon', 'Text'}: continue # render base nodes for node in value: sexpr.append(self._callSerialize(node)) sexpr.append(SexprSerializer.NEW_LINE) # serialize 3D Models at the end if grouped_nodes.get('Model'): for node in grouped_nodes.get('Model'): sexpr.append(self._serialize_Model(node)) sexpr.append(SexprSerializer.NEW_LINE) return sexpr def _callSerialize(self, node): ''' call the corresponding method to serialize the node ''' method_type = node.__class__.__name__ method_name = "_serialize_{0}".format(method_type) if hasattr(self, method_name): return getattr(self, method_name)(node) else: exception_string = "{name} (node) not found, cannot serialized the node of type {type}" raise NotImplementedError(exception_string.format(name=method_name, type=method_type)) def _serialize_ArcPoints(self, node): # in KiCAD, some file attributes of Arc are named not in the way of their real meaning center_pos = node.getRealPosition(node.center_pos) end_pos = node.getRealPosition(node.start_pos) return [ ['start', center_pos.x, center_pos.y], ['end', end_pos.x, end_pos.y], ['angle', node.angle] ] def _serialize_Arc(self, node): sexpr = ['fp_arc'] sexpr += self._serialize_ArcPoints(node) sexpr += [ ['layer', node.layer], ['width', _get_layer_width(node.layer, node.width)] ] # NOQA return sexpr def _serialize_CirclePoints(self, node): center_pos = node.getRealPosition(node.center_pos) end_pos = node.getRealPosition(node.center_pos + (node.radius, 0)) return [ ['center', center_pos.x, center_pos.y], ['end', end_pos.x, end_pos.y] ] def _serialize_Circle(self, node): sexpr = ['fp_circle'] sexpr += self._serialize_CirclePoints(node) sexpr += [ ['layer', node.layer], ['width', _get_layer_width(node.layer, node.width)] ] # NOQA return sexpr def _serialize_LinePoints(self, node): start_pos = node.getRealPosition(node.start_pos) end_pos = node.getRealPosition(node.end_pos) return [ ['start', start_pos.x, start_pos.y], ['end', end_pos.x, end_pos.y] ] def _serialize_Line(self, node): start_pos = node.getRealPosition(node.start_pos) end_pos = node.getRealPosition(node.end_pos) sexpr = ['fp_line'] sexpr += self._serialize_LinePoints(node) sexpr += [ ['layer', node.layer], ['width', _get_layer_width(node.layer, node.width)] ] # NOQA return sexpr def _serialize_Text(self, node): sexpr = ['fp_text', node.type, node.text] position, rotation = node.getRealPosition(node.at, node.rotation) if rotation: sexpr.append(['at', position.x, position.y, rotation]) else: sexpr.append(['at', position.x, position.y]) sexpr.append(['layer', node.layer]) if node.hide: sexpr.append('hide') sexpr.append(SexprSerializer.NEW_LINE) effects = [ 'effects', ['font', ['size', node.size.x, node.size.y], ['thickness', node.thickness]]] if node.mirror: effects.append(['justify', 'mirror']) sexpr.append(effects) sexpr.append(SexprSerializer.NEW_LINE) return sexpr def _serialize_Model(self, node): sexpr = ['model', node.filename, SexprSerializer.NEW_LINE, ['at', ['xyz', node.at.x, node.at.y, node.at.z]], SexprSerializer.NEW_LINE, ['scale', ['xyz', node.scale.x, node.scale.y, node.scale.z]], SexprSerializer.NEW_LINE, ['rotate', ['xyz', node.rotate.x, node.rotate.y, node.rotate.z]], SexprSerializer.NEW_LINE ] # NOQA return sexpr def _serialize_CustomPadPrimitives(self, pad): all_primitives = [] for p in pad.primitives: all_primitives.extend(p.serialize()) grouped_nodes = {} for single_node in all_primitives: node_type = single_node.__class__.__name__ current_nodes = grouped_nodes.get(node_type, []) current_nodes.append(single_node) grouped_nodes[node_type] = current_nodes sexpr_primitives = [] for key, value in sorted(grouped_nodes.items()): # check if key is a base node, except Model if key not in {'Arc', 'Circle', 'Line', 'Pad', 'Polygon', 'Text'}: continue # render base nodes for p in value: if isinstance(p, Polygon): sp = ['gr_poly', self._serialize_PolygonPoints(p, newline_after_pts=True) ] # NOQA elif isinstance(p, Line): sp = ['gr_line'] + self._serialize_LinePoints(p) elif isinstance(p, Circle): sp = ['gr_circle'] + self._serialize_CirclePoints(p) elif isinstance(p, Arc): sp = ['gr_arc'] + self._serialize_ArcPoints(p) else: raise TypeError('Unsuported type of primitive for custom pad.') sp.append(['width', DEFAULT_WIDTH_POLYGON_PAD if p.width is None else p.width]) sexpr_primitives.append(sp) sexpr_primitives.append(SexprSerializer.NEW_LINE) return sexpr_primitives def _serialize_Pad(self, node): sexpr = ['pad', node.number, node.type, node.shape] position, rotation = node.getRealPosition(node.at, node.rotation) if not rotation % 360 == 0: sexpr.append(['at', position.x, position.y, rotation]) else: sexpr.append(['at', position.x, position.y]) sexpr.append(['size', node.size.x, node.size.y]) if node.type in [Pad.TYPE_THT, Pad.TYPE_NPTH]: if node.drill.x == node.drill.y: sexpr.append(['drill', node.drill.x]) else: sexpr.append(['drill', 'oval', node.drill.x, node.drill.y]) sexpr.append(['layers'] + node.layers) if node.shape == Pad.SHAPE_ROUNDRECT: sexpr.append(['roundrect_rratio', node.radius_ratio]) if node.shape == Pad.SHAPE_CUSTOM: # gr_line, gr_arc, gr_circle or gr_poly sexpr.append(SexprSerializer.NEW_LINE) sexpr.append(['options', ['clearance', node.shape_in_zone], ['anchor', node.anchor_shape] ]) # NOQA sexpr.append(SexprSerializer.NEW_LINE) sexpr_primitives = self._serialize_CustomPadPrimitives(node) sexpr.append(['primitives', SexprSerializer.NEW_LINE] + sexpr_primitives) if node.solder_paste_margin_ratio != 0 or node.solder_mask_margin != 0 or node.solder_paste_margin != 0: sexpr.append(SexprSerializer.NEW_LINE) if node.solder_mask_margin != 0: sexpr.append(['solder_mask_margin', node.solder_mask_margin]) if node.solder_paste_margin_ratio != 0: sexpr.append(['solder_paste_margin_ratio', node.solder_paste_margin_ratio]) if node.solder_paste_margin != 0: sexpr.append(['solder_paste_margin', node.solder_paste_margin]) return sexpr def _serialize_PolygonPoints(self, node, newline_after_pts=False): node_points = ['pts'] if newline_after_pts: node_points.append(SexprSerializer.NEW_LINE) points_appended = 0 for n in node.nodes: if points_appended >= 4: points_appended = 0 node_points.append(SexprSerializer.NEW_LINE) points_appended += 1 n_pos = node.getRealPosition(n) node_points.append(['xy', n_pos.x, n_pos.y]) return node_points def _serialize_Polygon(self, node): node_points = self._serialize_PolygonPoints(node) sexpr = ['fp_poly', node_points, ['layer', node.layer], ['width', _get_layer_width(node.layer, node.width)] ] # NOQA return sexpr