コード例 #1
0
 def pins(self, width: int, grid: float) -> List[Tuple[str, Position, Rotation]]:
     """
     Return all pins spaced with the specified grid size.
     """
     dx = (width + 2) * grid / 2
     return [(l[0], Position(-dx, l[1] * grid), Rotation(0.0)) for l in self.left] + \
         [(r[0], Position(dx, r[1] * grid), Rotation(180.0)) for r in self.right]
コード例 #2
0
def create_footprint() -> Footprint:
    footprint = Footprint('17b9f232-2b15-4281-a07d-ad0db5213f92',
                          Name('default'), Description(''))
    footprint.add_pad(
        FootprintPad('5c4d39d3-35cc-4836-a082-693143ee9135', Side.THT,
                     Shape.RECT, Position(0.0, 22.86), Rotation(0.0),
                     Size(2.54, 1.5875), Drill(1.0)))
    footprint.add_pad(
        FootprintPad('6100dd55-d3b3-4139-9085-d5a75e783c37', Side.THT,
                     Shape.ROUND, Position(0.0, 20.32), Rotation(0.0),
                     Size(2.54, 1.5875), Drill(1.0)))
    polygon = Polygon('5e18e4ea-5667-42b3-b60f-fcc91b0461d3',
                      Layer('top_placement'), Width(0.25), Fill(False),
                      GrabArea(True))
    polygon.add_vertex(Vertex(Position(-1.27, +24.36), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(+1.27, +24.36), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(+1.27, -24.36), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(-1.27, -24.36), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(-1.27, +24.36), Angle(0.0)))
    footprint.add_polygon(polygon)
    stroke_text = StrokeText('f16d1604-8a82-4688-bc58-be1c1375873f',
                             Layer('top_names'), Height(1.0), StrokeWidth(0.2),
                             LetterSpacing.AUTO, LineSpacing.AUTO,
                             Align('center bottom'), Position(0.0, 25.63),
                             Rotation(0.0), AutoRotate(True), Mirror(False),
                             Value('{{NAME}}'))
    footprint.add_text(stroke_text)
    return footprint
コード例 #3
0
def test_symbol() -> None:
    symbol = Symbol('01b03c10-7334-4bd5-b2bc-942c18325d2b', Name('Sym name'),
                    Description(r'A multiline description.\n\nDescription'),
                    Keywords('my, keywords'), Author('Test'), Version('0.2'),
                    Created('2018-10-17T19:13:41Z'),
                    Category('d0618c29-0436-42da-a388-fdadf7b23892'))
    symbol.add_pin(
        SymbolPin('6da06b2b-7806-4e68-bd0c-e9f18eb2f9d8', Name('1'),
                  Position(5.08, 20.32), Rotation(180.0), Length(3.81)))
    polygon = Polygon('743dbf3d-98e8-46f0-9a32-00e00d0e811f',
                      Layer('sym_outlines'), Width(0.25), Fill(False),
                      GrabArea(True))
    polygon.add_vertex(Vertex(Position(-2.54, 22.86), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(-2.54, -25.4), Angle(0.0)))
    polygon.add_vertex(Vertex(Position(-2.54, 22.86), Angle(0.0)))
    symbol.add_polygon(polygon)
    symbol.add_circle(
        Circle('b5599e68-ff6a-464b-9a40-c6ba8ef8daf5', Layer('sym_outlines'),
               Width(0.254), Fill(False), GrabArea(False), Diameter(1.27),
               Position(5.715, 0.0)))
    symbol.add_text(
        Text('b9c4aa19-0a46-400c-9c96-e8c3dfb8f83e', Layer('sym_names'),
             Value('{{NAME}}'), Align('center bottom'), Height(2.54),
             Position(0.0, 22.86), Rotation(0.0)))

    assert str(
        symbol) == """(librepcb_symbol 01b03c10-7334-4bd5-b2bc-942c18325d2b
コード例 #4
0
def test_component() -> None:
    component = Component(
        '00c36da8-e22b-43a1-9a87-c3a67e863f49', Name('Generic Connector 1x27'),
        Description(r'A 1x27 soldered wire connector.\n\nNext line'),
        Keywords('connector, 1x27'), Author('Test R.'), Version('0.2'),
        Created('2018-10-17T19:13:41Z'), Deprecated(False),
        Category('d0618c29-0436-42da-a388-fdadf7b23892'), SchematicOnly(False),
        DefaultValue(''), Prefix('J'))
    component.add_signal(
        Signal('f46a4643-fc68-4593-a889-3d987bfe3544', Name('1'), Role.PASSIVE,
               Required(False), Negated(False), Clock(False), ForcedNet('')))

    gate = Gate('c1e4b542-a1b1-44d5-bec3-070776143a29',
                SymbolUUID('8f1a97f2-4cdf-43da-b38d-b3787c47b5ad'),
                Position(0.0, 0.0), Rotation(0.0), Required(True), Suffix(''))
    gate.add_pin_signal_map(
        PinSignalMap('0189aafc-f88a-4e65-8fb4-09a047a3e334',
                     SignalUUID('46f7e0e2-74a6-442b-9a5c-1bd4ea3da59c'),
                     TextDesignator.SYMBOL_PIN_NAME))
    variant = Variant('abeeeed0-6e9a-4fdc-bc2b-e2c5b06bbe3a', Norm.EMPTY,
                      Name('default'), Description(''), gate)

    component.add_variant(variant)

    assert str(component
               ) == """(librepcb_component 00c36da8-e22b-43a1-9a87-c3a67e863f49
コード例 #5
0
def test_footprint_pad() -> None:
    footprint_pad = FootprintPad('5c4d39d3-35cc-4836-a082-693143ee9135',
                                 Side.THT, Shape.RECT, Position(0.0, 22.86),
                                 Rotation(0.0), Size(2.54, 1.5875), Drill(1.0))
    assert str(
        footprint_pad
    ) == """(pad 5c4d39d3-35cc-4836-a082-693143ee9135 (side tht) (shape rect)
コード例 #6
0
def test_text() -> None:
    text = str(
        Text('b9c4aa19-0a46-400c-9c96-e8c3dfb8f83e', Layer('sym_names'),
             Value('{{NAME}}'), Align('center bottom'), Height(2.54),
             Position(0.0, 22.86), Rotation(0.0)))
    assert text == '(text b9c4aa19-0a46-400c-9c96-e8c3dfb8f83e (layer sym_names) (value "{{NAME}}")\n' +\
        ' (align center bottom) (height 2.54) (position 0.0 22.86) (rotation 0.0)\n' +\
        ')'
コード例 #7
0
def test_symbol_pin() -> None:
    symbol_pin_s_exp = str(
        SymbolPin('my_uuid', Name('foo'), Position(1.0, 2.0), Rotation(180.0),
                  Length(3.81)))

    assert symbol_pin_s_exp == '(pin my_uuid (name "foo")\n' + \
        ' (position 1.0 2.0) (rotation 180.0) (length 3.81)\n' + \
        ')'
コード例 #8
0
def test_component_gate() -> None:
    gate = Gate('c1e4b542-a1b1-44d5-bec3-070776143a29',
                SymbolUUID('8f1a97f2-4cdf-43da-b38d-b3787c47b5ad'),
                Position(0.0, 0.0), Rotation(0.0), Required(True), Suffix(''))
    gate.add_pin_signal_map(
        PinSignalMap('0189aafc-f88a-4e65-8fb4-09a047a3e334',
                     SignalUUID('46f7e0e2-74a6-442b-9a5c-1bd4ea3da59c'),
                     TextDesignator.SYMBOL_PIN_NAME))
    assert str(gate) == """(gate c1e4b542-a1b1-44d5-bec3-070776143a29
コード例 #9
0
def test_stroke_text() -> None:
    stroke_text = StrokeText('f16d1604-8a82-4688-bc58-be1c1375873f',
                             Layer('top_names'), Height(1.0), StrokeWidth(0.2),
                             LetterSpacing.AUTO, LineSpacing.AUTO,
                             Align('center bottom'), Position(0.0, 25.63),
                             Rotation(0.0), AutoRotate(True), Mirror(False),
                             Value('{{NAME}}'))
    assert str(
        stroke_text
    ) == """(stroke_text f16d1604-8a82-4688-bc58-be1c1375873f (layer top_names)
コード例 #10
0
def test_component_variant() -> None:
    gate = Gate('c1e4b542-a1b1-44d5-bec3-070776143a29',
                SymbolUUID('8f1a97f2-4cdf-43da-b38d-b3787c47b5ad'),
                Position(0.0, 0.0), Rotation(0.0), Required(True), Suffix(''))
    gate.add_pin_signal_map(
        PinSignalMap('0189aafc-f88a-4e65-8fb4-09a047a3e334',
                     SignalUUID('46f7e0e2-74a6-442b-9a5c-1bd4ea3da59c'),
                     TextDesignator.SYMBOL_PIN_NAME))
    variant = Variant('abeeeed0-6e9a-4fdc-bc2b-e2c5b06bbe3a', Norm.EMPTY,
                      Name('default'), Description(''), gate)
    assert str(
        variant) == """(variant abeeeed0-6e9a-4fdc-bc2b-e2c5b06bbe3a (norm "")
コード例 #11
0
def generate_cmp(
    name: str,
    mcus: List[MCU],
    symbol_map: Dict[str, str],
    debug: bool = False,
) -> None:
    """
    When generating components, to reduce the number of components, they are
    merged as follows:

    - For every MCU, the "ref without flash" is calculated by replacing the
      11th character in the ref name with an `x` and cutting off everything
      after the package character.
    - MCUs that share the same pinout will be merged if their ref names without
      flash are the same
    - The name of the component will be generated as follows: STM32F429IEHx +
      STM32F429IGHx + STM32F429IIHx = STM32F429I[EGI]Hx.
    - To achieve a stable UUID even if new MCUs are added, the "ref without
      flash" is combined with the SHA1 hash of the pins.

    Because renaming the pinout might result in a different UUID, when
    upgrading the stm-pinout database, changes (but not additions) must be
    analyzed manually.

    """
    components = []
    for mcu in mcus:
        (placement, pin_mapping) = mcu.generate_placement_data(debug)

        cmp_version = '0.1'

        component = Component(
            uuid('cmp', mcu.component_identifier, 'cmp'),
            Name(name),
            Description(mcu.component_description),
            mcu.keywords,
            author,
            Version(cmp_version),
            Created('2020-01-30T20:55:23Z'),
            Deprecated(False),
            cmpcat,
            SchematicOnly(False),
            DefaultValue('{{PARTNUMBER or DEVICE or COMPONENT}}'),
            Prefix('U'),
        )

        # Add signals
        signals = sorted({pin.name for pin in mcu.pins}, key=human_sort_key)
        for signal in signals:
            component.add_signal(Signal(
                # Use original signal name, so that changing the cleanup function
                # does not influence the identifier.
                uuid('cmp', mcu.component_identifier, 'signal-{}'.format(signal)),
                # Use cleaned up signal name for name
                Name(signal),
                Role.PASSIVE,
                Required(False),
                Negated(False),
                Clock(False),
                ForcedNet(''),
            ))

        # Add symbol variant
        gate = Gate(
            uuid('cmp', mcu.component_identifier, 'variant-single-gate1'),
            SymbolUUID(uuid('sym', mcu.symbol_identifier, 'sym')),
            Position(0, 0),
            Rotation(0.0),
            Required(True),
            Suffix(''),
        )
        for generic, concrete in pin_mapping.items():
            gate.add_pin_signal_map(PinSignalMap(
                uuid('sym', mcu.symbol_identifier, 'pin-{}'.format(generic)),
                SignalUUID(uuid('cmp', mcu.component_identifier, 'signal-{}'.format(concrete))),
                TextDesignator.SIGNAL_NAME,
            ))
        component.add_variant(Variant(
            uuid('cmp', mcu.component_identifier, 'variant-single'),
            Norm.EMPTY,
            Name('single'),
            Description('Symbol with all MCU pins'),
            gate,
        ))
        components.append(component)

    # Make sure all grouped components are identical
    assert len(set([str(c) for c in components])) == 1

    components[0].serialize('out/stm_mcu/cmp')

    print('Wrote cmp {}'.format(name))
コード例 #12
0
def generate_sym(mcus: List[MCU], symbol_map: Dict[str, str], debug: bool = False) -> None:
    # Determine symbol width
    pin_mappings = [mcu.generate_placement_data()[1].values() for mcu in mcus]
    max_pin_name_len = max([max(map(len, pin_map)) for pin_map in pin_mappings])
    width = width_wide if max_pin_name_len >= width_wide_threshold else width_regular

    symbols = []
    for mcu in mcus:
        assert mcu.symbol_identifier not in symbol_map

        sym_version = '0.1'

        # Generate pin placement data
        (placement, pin_mapping) = mcu.generate_placement_data(debug)
        if debug:
            print(pin_mapping)

        uuid_sym = uuid('sym', mcu.symbol_identifier, 'sym')
        symbol = Symbol(
            uuid_sym,
            Name(mcu.symbol_name),
            Description(mcu.symbol_description),
            mcu.keywords,
            author,
            Version(sym_version),
            Created('2020-01-30T20:55:23Z'),
            cmpcat,
        )
        placement_pins = placement.pins(width, grid)
        placement_pins.sort(key=lambda x: (x[1].x, x[1].y))
        for pin_name, position, rotation in placement.pins(width, grid):
            symbol.add_pin(SymbolPin(
                uuid('sym', mcu.symbol_identifier, 'pin-{}'.format(pin_name)),
                Name(pin_name),
                position,
                rotation,
                Length(grid),
            ))
        polygon = Polygon(
            uuid('sym', mcu.symbol_identifier, 'polygon'),
            Layer('sym_outlines'),
            Width(line_width),
            Fill(False),
            GrabArea(True),
        )
        (max_y, min_y) = placement.maxmin_y(grid)
        dx = width * grid / 2
        polygon.add_vertex(Vertex(Position(-dx, max_y), Angle(0.0)))
        polygon.add_vertex(Vertex(Position( dx, max_y), Angle(0.0)))
        polygon.add_vertex(Vertex(Position( dx, min_y), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(-dx, min_y), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(-dx, max_y), Angle(0.0)))
        symbol.add_polygon(polygon)

        text_name = Text(
            uuid('sym', mcu.symbol_identifier, 'text-name'),
            Layer('sym_names'),
            Value('{{NAME}}'),
            Align('left bottom'),
            Height(text_height),
            Position(-dx, max_y),
            Rotation(0.0),
        )
        text_value = Text(
            uuid('sym', mcu.symbol_identifier, 'text-value'),
            Layer('sym_values'),
            Value('{{VALUE}}'),
            Align('left top'),
            Height(text_height),
            Position(-dx, min_y),
            Rotation(0.0),
        )
        symbol.add_text(text_name)
        symbol.add_text(text_value)

        symbols.append(symbol)

    # Make sure all grouped symbols are identical
    assert len(set([str(s) for s in symbols])) == 1

    dirpath = 'out/stm_mcu/sym'
    sym_dir_path = path.join(dirpath, symbols[0].uuid)
    if not (path.exists(sym_dir_path) and path.isdir(sym_dir_path)):
        makedirs(sym_dir_path)
    with open(path.join(sym_dir_path, '.librepcb-sym'), 'w') as f:
        f.write('0.1\n')
    with open(path.join(sym_dir_path, 'symbol.lp'), 'w') as f:
        f.write(str(symbols[0]))
        f.write('\n')

    symbol_map[mcus[0].symbol_identifier] = symbols[0].uuid

    print('Wrote sym {}'.format(symbols[0].name))
コード例 #13
0
def test_rotation() -> None:
    rotation_s_exp = str(Rotation(180.0))
    assert rotation_s_exp == '(rotation 180.0)'
コード例 #14
0
    def _create_footprint(footprint_identifier: str, name: str) -> Footprint:
        def _fpt_uuid(identifier: str) -> str:
            return _pkg_uuid(footprint_identifier + '-' + identifier)

        drill = LEAD_WIDTH_TO_DRILL[lead_width]
        restring = min(
            (0.4 if diameter >= 6.0 else 0.3),  # preferred restring
            (pitch - drill - 0.25) / 2)  # minimum required restring
        pad_diameter = drill + (2 * restring)  # outer diameter of pad
        courtyard_diameter = diameter + (1.0 if diameter >= 10.0 else 0.8)

        def _generate_fill_polygon(identifier: str, layer: str) -> Polygon:
            polygon = Polygon(
                uuid=_fpt_uuid(identifier),
                layer=Layer(layer),
                width=Width(0.0),
                fill=Fill(True),
                grab_area=GrabArea(False),
            )
            if ((pitch - pad_diameter) < 0.6):
                # not enough space, use a simplified polygon
                vertices = [
                    (0.0, (diameter / 2) - 0.2, 0.0),
                    (0.0, (pad_diameter / 2) + 0.2, 0.0),
                    (pitch / 2, (pad_diameter / 2) + 0.2, -180.0),
                    (pitch / 2, -(pad_diameter / 2) - 0.2, 0.0),
                    (0.0, -(pad_diameter / 2) - 0.2, 0.0),
                    (0.0, -(diameter / 2) + 0.2, 180.0),
                    (0.0, (diameter / 2) - 0.2, 0.0),
                ]
            else:
                vertices = [
                    (0.0, (diameter / 2) - 0.2, 0.0),
                    (0.0, 0.0, 0.0),
                    ((pitch / 2) - (pad_diameter / 2) - 0.2, 0.0, -180.0),
                    ((pitch / 2) + (pad_diameter / 2) + 0.2, 0.0, -180.0),
                    ((pitch / 2) - (pad_diameter / 2) - 0.2, 0.0, 0.0),
                    (0.0, 0.0, 0.0),
                    (0.0, -(diameter / 2) + 0.2, 180.0),
                    (0.0, (diameter / 2) - 0.2, 0.0),
                ]
            for vertex in vertices:
                polygon.add_vertex(
                    Vertex(Position(vertex[0], vertex[1]), Angle(vertex[2])))
            return polygon

        footprint = Footprint(
            uuid=_fpt_uuid('footprint'),
            name=Name(name),
            description=Description(''),
        )
        footprint.add_pad(
            FootprintPad(
                uuid=_pkg_uuid('pad-plus'),
                side=Side.THT,
                shape=Shape.RECT,
                position=Position(-pitch / 2, 0),
                rotation=Rotation(0),
                size=Size(pad_diameter, pad_diameter),
                drill=Drill(drill),
            ))
        footprint.add_pad(
            FootprintPad(
                uuid=_pkg_uuid('pad-minus'),
                side=Side.THT,
                shape=Shape.ROUND,
                position=Position(pitch / 2, 0),
                rotation=Rotation(0),
                size=Size(pad_diameter, pad_diameter),
                drill=Drill(drill),
            ))

        # placement
        footprint.add_circle(
            Circle(
                uuid=_fpt_uuid('circle-placement'),
                layer=Layer('top_placement'),
                width=Width(0.2),
                fill=Fill(False),
                grab_area=GrabArea(False),
                diameter=Diameter(diameter + 0.2),
                position=Position(0.0, 0.0),
            ))
        footprint.add_polygon(
            _generate_fill_polygon(
                identifier='polygon-placement-fill',
                layer='top_placement',
            ))

        # documentation
        footprint.add_circle(
            Circle(
                uuid=_fpt_uuid('circle-documentation'),
                layer=Layer('top_documentation'),
                width=Width(0.2),
                fill=Fill(False),
                grab_area=GrabArea(False),
                diameter=Diameter(diameter - 0.2),
                position=Position(0.0, 0.0),
            ))
        footprint.add_polygon(
            _generate_fill_polygon(
                identifier='polygon-documentation-fill',
                layer='top_documentation',
            ))

        # courtyard
        footprint.add_circle(
            Circle(
                uuid=_fpt_uuid('circle-courtyard'),
                layer=Layer('top_courtyard'),
                width=Width(0.2),
                fill=Fill(False),
                grab_area=GrabArea(False),
                diameter=Diameter(courtyard_diameter),
                position=Position(0.0, 0.0),
            ))

        # texts
        footprint.add_text(
            StrokeText(
                uuid=_fpt_uuid('text-name'),
                layer=Layer('top_names'),
                height=Height(1.0),
                stroke_width=StrokeWidth(0.2),
                letter_spacing=LetterSpacing.AUTO,
                line_spacing=LineSpacing.AUTO,
                align=Align('center bottom'),
                position=Position(0.0, (diameter / 2) + 0.8),
                rotation=Rotation(0.0),
                auto_rotate=AutoRotate(True),
                mirror=Mirror(False),
                value=Value('{{NAME}}'),
            ))
        footprint.add_text(
            StrokeText(
                uuid=_fpt_uuid('text-value'),
                layer=Layer('top_values'),
                height=Height(1.0),
                stroke_width=StrokeWidth(0.2),
                letter_spacing=LetterSpacing.AUTO,
                line_spacing=LineSpacing.AUTO,
                align=Align('center top'),
                position=Position(0.0, -(diameter / 2) - 0.8),
                rotation=Rotation(0.0),
                auto_rotate=AutoRotate(True),
                mirror=Mirror(False),
                value=Value('{{VALUE}}'),
            ))
        return footprint
コード例 #15
0
def generate_cmp(
    dirpath: str,
    author: str,
    name: str,
    name_lower: str,
    kind: str,
    cmpcat: str,
    keywords: str,
    default_value: str,
    rows: int,
    min_pads: int,
    max_pads: int,
    version: str,
    create_date: Optional[str],
) -> None:
    category = 'cmp'
    assert rows in [1, 2]
    for i in range(min_pads, max_pads + 1, rows):
        per_row = i // rows
        variant = '{}x{}'.format(rows, per_row)

        def _uuid(identifier: str) -> str:
            return uuid(category, kind, variant, identifier)

        uuid_cmp = _uuid('cmp')
        uuid_pins = [
            uuid('sym', kind, variant, 'pin-{}'.format(p)) for p in range(i)
        ]
        uuid_signals = [_uuid('signal-{}'.format(p)) for p in range(i)]
        uuid_variant = _uuid('variant-default')
        uuid_gate = _uuid('gate-default')
        uuid_symbol = uuid('sym', kind, variant, 'sym')

        # General info
        component = Component(
            uuid_cmp,
            Name('{} {}x{:02d}'.format(name, rows, per_row)),
            Description('A {}x{} {}.\\n\\n'
                        'Generated with {}'.format(rows, per_row, name_lower,
                                                   generator)),
            Keywords('connector, {}x{}, {}'.format(rows, per_row, keywords)),
            Author(author),
            Version(version),
            Created(create_date or now()),
            Deprecated(False),
            Category(cmpcat),
            SchematicOnly(False),
            DefaultValue(default_value),
            Prefix('J'),
        )

        for p in range(1, i + 1):
            component.add_signal(
                Signal(
                    uuid_signals[p - 1],
                    Name(str(p)),
                    Role.PASSIVE,
                    Required(False),
                    Negated(False),
                    Clock(False),
                    ForcedNet(''),
                ))

        gate = Gate(
            uuid_gate,
            SymbolUUID(uuid_symbol),
            Position(0.0, 0.0),
            Rotation(0.0),
            Required(True),
            Suffix(''),
        )
        for p in range(1, i + 1):
            gate.add_pin_signal_map(
                PinSignalMap(
                    uuid_pins[p - 1],
                    SignalUUID(uuid_signals[p - 1]),
                    TextDesignator.SYMBOL_PIN_NAME,
                ))

        component.add_variant(
            Variant(uuid_variant, Norm.EMPTY, Name('default'), Description(''),
                    gate))

        component.serialize(dirpath)

        print('{}x{} {}: Wrote component {}'.format(rows, per_row, kind,
                                                    uuid_cmp))
コード例 #16
0
def generate_sym(
    dirpath: str,
    author: str,
    name: str,
    name_lower: str,
    kind: str,
    cmpcat: str,
    keywords: str,
    rows: int,
    min_pads: int,
    max_pads: int,
    version: str,
    create_date: Optional[str],
) -> None:
    category = 'sym'
    assert rows in [1, 2]
    for i in range(min_pads, max_pads + 1, rows):
        per_row = i // rows
        w = width * rows  # Make double-row symbols wider!

        variant = '{}x{}'.format(rows, per_row)

        def _uuid(identifier: str) -> str:
            return uuid(category, kind, variant, identifier)

        uuid_sym = _uuid('sym')
        uuid_pins = [_uuid('pin-{}'.format(p)) for p in range(i)]
        uuid_polygon = _uuid('polygon-contour')
        uuid_decoration = _uuid('polygon-decoration')
        uuid_text_name = _uuid('text-name')
        uuid_text_value = _uuid('text-value')

        # General info
        symbol = Symbol(
            uuid_sym,
            Name('{} {}x{:02d}'.format(name, rows, per_row)),
            Description('A {}x{} {}.\\n\\n'
                        'Generated with {}'.format(rows, per_row, name_lower,
                                                   generator)),
            Keywords('connector, {}x{}, {}'.format(rows, per_row, keywords)),
            Author(author),
            Version(version),
            Created(create_date or now()),
            Category(cmpcat),
        )

        for p in range(1, i + 1):
            x_sign = 1 if (p % rows == 0) else -1
            pin = SymbolPin(
                uuid_pins[p - 1], Name(str(p)),
                Position((w + 2.54) * x_sign, get_y(p, i, rows, spacing,
                                                    True)),
                Rotation(180.0 if p % rows == 0 else 0), Length(3.81))
            symbol.add_pin(pin)

        # Polygons
        y_max, y_min = get_rectangle_bounds(i, rows, spacing, spacing, True)
        polygon = Polygon(uuid_polygon, Layer('sym_outlines'),
                          Width(line_width), Fill(False), GrabArea(True))
        polygon.add_vertex(Vertex(Position(-w, y_max), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(w, y_max), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(w, y_min), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(-w, y_min), Angle(0.0)))
        polygon.add_vertex(Vertex(Position(-w, y_max), Angle(0.0)))
        symbol.add_polygon(polygon)

        # Decorations
        if kind == KIND_HEADER:
            # Headers: Small rectangle
            for p in range(1, i + 1):
                x_sign = 1 if (p % rows == 0) else -1
                y = get_y(p, i, rows, spacing, True)
                dx = spacing / 8 * 1.5 * x_sign
                dy = spacing / 8 / 1.5
                x_offset = x_sign * (w - 1.27)
                polygon = Polygon(uuid_decoration, Layer('sym_outlines'),
                                  Width(line_width), Fill(True),
                                  GrabArea(True))
                polygon.add_vertex(
                    Vertex(Position(x_offset - dx, y + dy), Angle(0.0)))
                polygon.add_vertex(
                    Vertex(Position(x_offset + dx, y + dy), Angle(0.0)))
                polygon.add_vertex(
                    Vertex(Position(x_offset + dx, y - dy), Angle(0.0)))
                polygon.add_vertex(
                    Vertex(Position(x_offset - dx, y - dy), Angle(0.0)))
                polygon.add_vertex(
                    Vertex(Position(x_offset - dx, y + dy), Angle(0.0)))
                symbol.add_polygon(polygon)
        elif kind == KIND_SOCKET:
            # Sockets: Small semicircle
            for p in range(1, i + 1):
                x_sign = 1 if (p % rows == 0) else -1
                y = get_y(p, i, rows, spacing, True)
                dy = spacing / 4 * 0.75
                x_offset = x_sign * (w - 1.27 - dy * 0.75)
                polygon = Polygon(uuid_decoration, Layer('sym_outlines'),
                                  Width(line_width * 0.75), Fill(False),
                                  GrabArea(False))
                polygon.add_vertex(
                    Vertex(Position(x_offset, y - dy), Angle(x_sign * 135.0)))
                polygon.add_vertex(
                    Vertex(Position(x_offset, y + dy), Angle(0.0)))
                symbol.add_polygon(polygon)

        # Text
        y_max, y_min = get_rectangle_bounds(i, rows, spacing, spacing, True)
        text = Text(uuid_text_name, Layer('sym_names'), Value('{{NAME}}'),
                    Align('center bottom'), Height(sym_text_height),
                    Position(0.0, y_max), Rotation(0.0))
        symbol.add_text(text)

        text = Text(uuid_text_value, Layer('sym_values'), Value('{{VALUE}}'),
                    Align('center top'), Height(sym_text_height),
                    Position(0.0, y_min), Rotation(0.0))
        symbol.add_text(text)

        sym_dir_path = path.join(dirpath, uuid_sym)
        if not (path.exists(sym_dir_path) and path.isdir(sym_dir_path)):
            makedirs(sym_dir_path)
        with open(path.join(sym_dir_path, '.librepcb-sym'), 'w') as f:
            f.write('0.1\n')
        with open(path.join(sym_dir_path, 'symbol.lp'), 'w') as f:
            f.write(str(symbol))
            f.write('\n')

        print('{}x{} {}: Wrote symbol {}'.format(rows, per_row, kind,
                                                 uuid_sym))