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
Beispiel #2
0
def generate_dev(
    dirpath: str,
    diameter: float,
    height: float,
    pitch: float,
    lead_width: float,
    author: str,
    version: str,
    create_date: Optional[str],
) -> None:
    name = 'Capacitor Radial ⌀{}x{}/{}mm'.format(diameter, height, pitch)
    variant = get_variant(diameter, height, pitch, lead_width)

    def _uuid(identifier: str) -> str:
        return uuid('dev', variant, identifier)

    device = Device(
        uuid=_uuid('dev'),
        name=Name(name),
        description=Description(
            'Generic polarized radial electrolytic capacitor.\\n\\n' +
            'Diameter: {} mm\\n'.format(diameter) +
            'Height: {} mm\\n'.format(height) +
            'Lead Spacing: {} mm\\n'.format(pitch) +
            'Max. Lead Diameter: {} mm\\n\\n'.format(lead_width) +
            'Generated with {}'.format(generator)),
        keywords=Keywords(
            'electrolytic,capacitor,polarized,radial,c,cap,cpol'),
        author=Author(author),
        version=Version(version),
        created=Created(create_date or now()),
        deprecated=Deprecated(False),
        category=Category('c011cc6b-b762-498e-8494-d1994f3043cf'),
        component_uuid=ComponentUUID('c54375c5-7149-4ded-95c5-7462f7301ee7'),
        package_uuid=PackageUUID(uuid('pkg', variant, 'pkg')),
    )
    device.add_pad(
        ComponentPad(
            uuid=uuid('pkg', variant, 'pad-plus'),
            signal=SignalUUID('e010ecbb-6210-4da3-9270-ebd58656dbf0'),
        ))
    device.add_pad(
        ComponentPad(
            uuid=uuid('pkg', variant, 'pad-minus'),
            signal=SignalUUID('af3ffca8-0085-4edb-a775-fcb759f63411'),
        ))

    # write files
    pkg_dir_path = path.join(dirpath, device.uuid)
    if not (path.exists(pkg_dir_path) and path.isdir(pkg_dir_path)):
        makedirs(pkg_dir_path)
    with open(path.join(pkg_dir_path, '.librepcb-dev'), 'w') as f:
        f.write('0.1\n')
    with open(path.join(pkg_dir_path, 'device.lp'), 'w') as f:
        f.write(str(device))
        f.write('\n')
    print('Wrote device {}'.format(name))
def generate_dev(mcu: MCU, symbol_map: Dict[str, str], base_lib_path: str, debug: bool = False) -> None:
    """
    A device will be generated for every MCU ref.
    """
    (placement, pin_mapping) = mcu.generate_placement_data(debug)

    name = mcu.ref
    dev_version = '0.1'

    package_uuid_mapping = {
        'LQFP32':  'd1944164-969d-421f-8b46-1e79fc368195',  # LQFP80P900X900X140-32
        'LQFP44':  'b373f788-8d26-4e3d-9256-89851d962373',  # LQFP80P1200X1200X140-44
        'LQFP48':  '584b7c26-5a8e-4a2b-807a-977edd1df991',  # LQFP50P900X900X140-48
        'LQFP64':  '54cc857c-3af1-4af3-82b0-fba7a121bcb1',  # LQFP50P1200X1200X140-64
        'LQFP80':  'fde7e4d0-0548-4c0a-aa3e-6f8ce25e751c',  # LQFP65P1600X1600X140-80
        'LQFP100': 'f74cdcb2-833d-4877-876f-56d4c15b5cb8',  # LQFP50P1600X1600X140-100
        'LQFP144': '2fc34b46-a86d-40e3-9dd1-def143ac3318',  # LQFP50P2200X2200X140-144
        'LQFP176': '43ab9eca-7912-433f-afaa-61d3ec6c84b2',  # LQFP50P2600X2600X140-176
        'LQFP208': '422600f0-a868-49b6-92f7-22c1874258bb',  # LQFP50P3000X3000X140-208
        'SO8':     'ffbf2bed-9155-45a9-b154-2f766c7f9019',  # SOIC127P600X175-8
        'SO8N':    'ffbf2bed-9155-45a9-b154-2f766c7f9019',  # SOIC127P600X175-8
        'TSSOP14': 'fb8c2dc2-9812-4383-a810-b2fdbd525b4e',  # TSSOP14P65_500X640X120L100X30
        'TSSOP20': 'a040fccc-54e5-4f95-a5db-20044d8b37a5',  # TSSOP20P65_650X640X120L100X30
    }
    if mcu.package not in package_uuid_mapping:
        print('Skipping dev {} (missing package {})'.format(name, mcu.package))
        return

    pad_uuid_mapping = common.get_pad_uuids(base_lib_path, package_uuid_mapping[mcu.package])

    device = Device(
        uuid('dev', mcu.ref, 'dev'),
        Name(mcu.ref),
        Description(mcu.description),
        mcu.keywords,
        author,
        Version(dev_version),
        Created('2020-03-01T01:55:20Z'),
        Deprecated(False),
        cmpcat,
        ComponentUUID(uuid('cmp', mcu.component_identifier, 'cmp')),
        PackageUUID(package_uuid_mapping[mcu.package]),
    )
    for pin in mcu.pins:
        pad_uuid = pad_uuid_mapping[pin.number]
        device.add_pad(ComponentPad(
            pad_uuid,
            SignalUUID(uuid('cmp', mcu.component_identifier, 'signal-{}'.format(pin.name))),
        ))

    device.serialize('out/stm_mcu/dev')

    print('Wrote dev {}'.format(name))
def test_package() -> None:
    package = Package(
        '009e35ef-1f50-4bf3-ab58-11eb85bf5503',
        Name('Soldered Wire Connector 1x19 ⌀1.0mm'),
        Description(
            'A 1x19 soldered wire connector with 2.54mm pin spacing and 1.0mm drill holes.\n\nGenerated with librepcb-parts-generator (generate_connectors.py)'
        ), Keywords('connector, 1x19, d1.0, connector, soldering, generic'),
        Author('Danilo B.'), Version('0.1'), Created('2018-10-17T19:13:41Z'),
        Deprecated(False), Category('56a5773f-eeb4-4b39-8cb9-274f3da26f4f'))

    package.add_pad(
        PackagePad('5c4d39d3-35cc-4836-a082-693143ee9135', Name('1')))
    package.add_pad(
        PackagePad('6100dd55-d3b3-4139-9085-d5a75e783c37', Name('2')))

    package.add_footprint(create_footprint())
    assert str(
        package) == """(librepcb_package 009e35ef-1f50-4bf3-ab58-11eb85bf5503
def test_device() -> None:
    device = Device(
        '00652f30-9f89-4027-91f5-7bd684eee751',
        Name('Foo'),
        Description('Bar'),
        Keywords('foo, bar'),
        Author('J. Rando'),
        Version('0.1'),
        Created('2018-10-17T19:13:41Z'),
        Deprecated(False),
        Category('ade6d8ff-3c4f-4dac-a939-cc540c87c280'),
        ComponentUUID('bc911fcc-8b5c-4728-b596-d644797c55da'),
        PackageUUID('b4e92c64-18c4-44a6-aa39-d1be3e8c29bd'),
    )
    device.add_pad(
        ComponentPad('aec3f475-28c4-4508-ab4f-e1b618a0d77d',
                     SignalUUID('726fd1ce-a01b-4287-bb61-e3ff165a0644')))
    device.add_pad(
        ComponentPad('67a7b034-b30b-4644-b8d3-d7a99606efdc',
                     SignalUUID('9bccea5e-e23f-4b88-9de1-4be00dc0c12a')))
    assert str(
        device) == """(librepcb_device 00652f30-9f89-4027-91f5-7bd684eee751
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))
Beispiel #7
0
def generate_pkg(
    dirpath: str,
    diameter: float,
    height: float,
    pitch: float,
    lead_width: float,
    author: str,
    version: str,
    create_date: Optional[str],
) -> None:
    # Name according IPC-7351 "Capacitor, Polarized Radial Diameter":
    # CAPPRD + Lead Spacing + W Lead Width + D Body Diameter + H Body Height
    name = 'CAPPRD{}W{}D{}H{}'.format(format_ipc_dimension(pitch),
                                      format_ipc_dimension(lead_width),
                                      format_ipc_dimension(diameter),
                                      format_ipc_dimension(height))
    variant = get_variant(diameter, height, pitch, lead_width)

    def _pkg_uuid(identifier: str) -> str:
        return uuid('pkg', variant, identifier)

    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

    # package
    package = Package(
        uuid=_pkg_uuid('pkg'),
        name=Name(name),
        description=Description(
            'Polarized radial electrolytic capacitor.\\n\\n' +
            'Diameter: {} mm\\n'.format(diameter) +
            'Height: {} mm\\n'.format(height) +
            'Lead Spacing: {} mm\\n'.format(pitch) +
            'Max. Lead Diameter: {} mm\\n\\n'.format(lead_width) +
            'Generated with {}'.format(generator)),
        keywords=Keywords(
            'electrolytic,capacitor,polarized,radial,c,cap,cpol'),
        author=Author(author),
        version=Version(version),
        created=Created(create_date or now()),
        deprecated=Deprecated(False),
        category=Category('ee75e31d-f231-41d9-8a3b-bea5114f41e3'),
    )
    package.add_pad(PackagePad(uuid=_pkg_uuid('pad-plus'), name=Name('+')))
    package.add_pad(PackagePad(uuid=_pkg_uuid('pad-minus'), name=Name('-')))
    package.add_footprint(
        _create_footprint(
            footprint_identifier='default',
            name='default',
        ))

    # write files
    pkg_dir_path = path.join(dirpath, package.uuid)
    if not (path.exists(pkg_dir_path) and path.isdir(pkg_dir_path)):
        makedirs(pkg_dir_path)
    with open(path.join(pkg_dir_path, '.librepcb-pkg'), 'w') as f:
        f.write('0.1\n')
    with open(path.join(pkg_dir_path, 'package.lp'), 'w') as f:
        f.write(str(package))
        f.write('\n')
    print('Wrote package {}'.format(name))
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))