diff --git a/README.md b/README.md index bbfa262..05a4828 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,22 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available, ## License This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details + +## Developer information / Preparing a release + +To create a release please follow this outline: + +* Do your development work in a separate branch. +* Write unit-tests (`tests/`) and/or integration tests (`examples/`) for your code. +* Once all local tests validate, and you have 100% code coverage, push to GitHub. + +### If you have commit access to the main repository + +* Once build hooks at Github/Sonarcloud/pyup etc have completed, tag a pre-release (`x.y.zrc0`) +* If that build completes, perform a PR into `master`, squashing the commit history. +* Tag the `master` branch with the new release version, ahdering to semantic versioning. +* Remove any `pre` artifacts from Docker Hub and PyPI. + +### If you don't have commit access to the main repository + +* Submit a PR towards the `master` branch of the [main repository](https://github.com/madworx/robotframework-kicadlibrary/). \ No newline at end of file diff --git a/src/KiCadLibrary/KiCadLibrary.py b/src/KiCadLibrary/KiCadLibrary.py index 673010b..13c1e33 100644 --- a/src/KiCadLibrary/KiCadLibrary.py +++ b/src/KiCadLibrary/KiCadLibrary.py @@ -344,14 +344,15 @@ def find_modules_by_value(self, regexp): def intersect_modules_by_reference(self, list1, list2): # pylint: disable=line-too-long - """*DEPRECATED!!* Use keyword `Find Modules` instead. - - Perform a set intersection of the two module lists ``list1`` and - ``list2`` based on the module ``references``. (I.e. returns a new - list containing only the modules that exist in both lists. + """Perform a set intersection of the two module lists ``list1`` + and ``list2`` based on the module ``references``. (I.e. returns + a new list containing only the modules that exist in both lists. If either of the two lists are equal to `None`, the other argument will be returned. + *You normally don't need to use this keyword, see documentation + on module selection.* + Examples: | `${connectors}=` | `Find Modules By Value` | `Conn_02x20_Odd_Even` | | `${connectors2}=` | `Find Modules By Reference` | `regexp=^J[0-9]+$` | @@ -366,6 +367,27 @@ def intersect_modules_by_reference(self, list1, list2): list1_ref_set = set(m.GetReference() for m in list1) return [m for m in list2 if m.GetReference() in list1_ref_set] + def complement_modules_by_reference(self, list1, list2): + # pylint: disable=line-too-long + """Perform a set complement operation on the two module lists + ``list1`` and ``list2`` based on the module ``references``. + (I.e. returns a new list containing only the modules that exist + in ``list1`` but not in ``list2``) + + Examples: + | `${connectors}=` | `Find Modules By Value` | `Conn_02x20_Odd_Even` | + | `${connectors2}=` | `Find Modules By Reference` | `regexp=^J[0-9]+$` | + | `${non_bus_connectors}=` | `Complement Modules By Reference` | `${connectors}` | `${connectors2}` | + """ + + if not bool(list1): + return [] + if not bool(list2): + return list1 + + list2_ref_set = set(m.GetReference() for m in list2) + return [m for m in list1 if m.GetReference() not in list2_ref_set] + def get_pad_netnames_for_module(self, module): """Return a _dict_ mapping pad names to the short form of netnames for the given module. @@ -457,6 +479,30 @@ def find_modules_by_pad_netname(self, regexp): ret.append(mod) return ret + # pylint: disable=too-many-arguments,line-too-long + def modules_should_have_values_matching(self, matching, modules=None, + value=None, reference=None, + pad_netname=None): + """Validate that the selected modules have values matching the + regular expression given in `matching`. + + This can be used, for example, to validate that all resistors + have been given a value which looks like an actual ohm value. + + Examples: + | `Modules Should Have Values Matching` | `[0-9]+(.[0-9]+)? *([kM])?$` | `reference=R[0-9]+` | + | `Modules Should Have Values Matching` | `[0-9]+(.[0-9]+)? *[munp]?F$` | `reference=C[0-9]+` | + """ + modlist = self.find_modules(modules, value, reference, pad_netname) + ret = True + for mod in modlist: + if not re.match(matching, mod.GetValue()): + logger.error("Module {0} has invalid value [{1}]." + .format(mod, mod.GetValue())) + ret = False + if not ret: + raise AssertionError("Modules with incorrect values detected.") + # pylint: disable=too-many-arguments def modules_should_have_orientation(self, orientation, modules=None, value=None, reference=None, @@ -473,14 +519,14 @@ def modules_should_have_orientation(self, orientation, modules=None, for mod in modlist: mod_orient = float(mod.GetOrientation()) / 10.0 if mod_orient != float(orientation): - logger.error("Orientation of component %s is %s, should be %s." + logger.error("Orientation of module %s is %s, should be %s." % (mod, mod_orient, orientation)) ret = False else: - logger.info("Orientation of component {0} is {1}". + logger.info("Orientation of module {0} is {1}". format(mod, mod_orient)) if not ret: - raise AssertionError("Component orientation(s) are wrong") + raise AssertionError("Module orientation(s) are wrong") def _get_all_edge_cuts(self): """Return a dict of all edge cuts""" diff --git a/src/tests/test_modules.py b/src/tests/test_modules.py index 8273e48..aaec62f 100644 --- a/src/tests/test_modules.py +++ b/src/tests/test_modules.py @@ -30,6 +30,21 @@ def test_module_intersection(): assert lib.intersect_modules_by_reference(list2, list1) == list1 assert lib.intersect_modules_by_reference(list1, list1) == list1 +def test_module_complement(): + list1 = lib.find_modules(reference="U.+") + list2 = lib.find_modules(reference="nonexistent") + list3 = lib.find_modules(reference="U3") + assert len(list1) == 3 + assert bool(list2) is False + assert len(list3) == 1 + assert lib.complement_modules_by_reference(list1, list2) == list1 + assert lib.complement_modules_by_reference(list2, list1) == [] + assert lib.complement_modules_by_reference(list1, list1) == [] + c = lib.complement_modules_by_reference(list1, list3) + assert len(c) == 2 + assert ((c[0].GetReference() == 'U2' and c[1].GetReference() == 'U1') + or (c[0].GetReference() == 'U1' and c[1].GetReference() == 'U2')) + def test_module_pads_should_have_same_netnames_should_fail(): with pytest.raises(AssertionError, match=r'have matching pad'): lib.matching_modules_should_have_same_pads_and_netnames(reference=r'U.*') @@ -57,8 +72,8 @@ def test_module_pads_should_be_on_grid_should_work(): lib.module_pads_should_be_on_grid("25mil", reference=r'.*') def test_modules_should_have_orientation_should_fail(): - with pytest.raises(AssertionError, match=r'Component orientation\(s\) are wrong'): - lib.modules_should_have_orientation(180, reference=r'.*') + with pytest.raises(AssertionError, match=r'Module orientation\(s\) are wrong'): + lib.modules_should_have_orientation(180, reference=r'[^R].*') def test_modules_should_have_orientation_should_work(): lib.modules_should_have_orientation(0, value='LM555') @@ -70,3 +85,10 @@ def test_internal_error_get_component_pins_for_module(): module.GetReference = mock.MagicMock(return_value="error") with pytest.raises(AssertionError, match=r"we couldn't find it\?!"): lib.get_component_pins_for_module(module) + +def test_modules_should_have_matching_values_should_work(): + lib.modules_should_have_values_matching(r'[0-9]+(.[0-9]+)? *([kM])?$', + reference=r'R[1-3]+') + with pytest.raises(AssertionError, match=r'incorrect values detected'): + lib.modules_should_have_values_matching(r'[0-9]+(.[0-9]+)? *([kM])?$', + reference=r'R4$') diff --git a/src/tests/test_modules/test_modules.kicad_pcb b/src/tests/test_modules/test_modules.kicad_pcb index a944c9b..1bc41a3 100644 --- a/src/tests/test_modules/test_modules.kicad_pcb +++ b/src/tests/test_modules/test_modules.kicad_pcb @@ -5,8 +5,8 @@ (drawings 4) (tracks 39) (zones 0) - (modules 3) - (nets 49) + (modules 7) + (nets 57) ) (page A4) @@ -138,6 +138,14 @@ (net 46 "Net-(U3-Pad39)") (net 47 "Net-(U3-Pad20)") (net 48 "Net-(U3-Pad40)") + (net 49 "Net-(R1-Pad1)") + (net 50 "Net-(R1-Pad2)") + (net 51 "Net-(R2-Pad2)") + (net 52 "Net-(R2-Pad1)") + (net 53 "Net-(R3-Pad1)") + (net 54 "Net-(R3-Pad2)") + (net 55 "Net-(R4-Pad2)") + (net 56 "Net-(R4-Pad1)") (net_class Default "This is the default net class." (clearance 0.2) @@ -153,6 +161,14 @@ (add_net /TR) (add_net /~R) (add_net GND) + (add_net "Net-(R1-Pad1)") + (add_net "Net-(R1-Pad2)") + (add_net "Net-(R2-Pad1)") + (add_net "Net-(R2-Pad2)") + (add_net "Net-(R3-Pad1)") + (add_net "Net-(R3-Pad2)") + (add_net "Net-(R4-Pad1)") + (add_net "Net-(R4-Pad2)") (add_net "Net-(U3-Pad1)") (add_net "Net-(U3-Pad10)") (add_net "Net-(U3-Pad11)") @@ -196,6 +212,138 @@ (add_net VCC) ) + (module Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical (layer F.Cu) (tedit 5AE5139B) (tstamp 5C3144D4) + (at 87.63 86.36) + (descr "Resistor, Axial_DIN0922 series, Axial, Vertical, pin pitch=7.62mm, 5W, length*diameter=20*9mm^2, http://www.vishay.com/docs/20128/wkxwrx.pdf") + (tags "Resistor Axial_DIN0922 series Axial Vertical pin pitch 7.62mm 5W length 20mm diameter 9mm") + (path /5C24FB98) + (fp_text reference R4 (at 3.81 -5.62) (layer F.SilkS) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text value 0.1uF (at 3.81 5.62) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text user %R (at 0 -1.7) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_line (start 9.07 -4.75) (end -4.75 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 4.75) (end 9.07 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 4.75) (end 9.07 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 -4.75) (end -4.75 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 4.62 0) (end 6.12 0) (layer F.SilkS) (width 0.12)) + (fp_line (start 0 0) (end 7.62 0) (layer F.Fab) (width 0.1)) + (fp_circle (center 0 0) (end 4.62 0) (layer F.SilkS) (width 0.12)) + (fp_circle (center 0 0) (end 4.5 0) (layer F.Fab) (width 0.1)) + (pad 2 thru_hole oval (at 7.62 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 55 "Net-(R4-Pad2)")) + (pad 1 thru_hole circle (at 0 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 56 "Net-(R4-Pad1)")) + (model ${KISYS3DMOD}/Resistor_THT.3dshapes/R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical.wrl + (at (xyz 0 0 0)) + (scale (xyz 1 1 1)) + (rotate (xyz 0 0 0)) + ) + ) + + (module Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical (layer F.Cu) (tedit 5AE5139B) (tstamp 5C3144C5) + (at 87.63 74.93) + (descr "Resistor, Axial_DIN0922 series, Axial, Vertical, pin pitch=7.62mm, 5W, length*diameter=20*9mm^2, http://www.vishay.com/docs/20128/wkxwrx.pdf") + (tags "Resistor Axial_DIN0922 series Axial Vertical pin pitch 7.62mm 5W length 20mm diameter 9mm") + (path /5C24FB07) + (fp_text reference R3 (at 3.81 -5.62) (layer F.SilkS) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text value "2 M" (at 3.81 5.62) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_circle (center 0 0) (end 4.5 0) (layer F.Fab) (width 0.1)) + (fp_circle (center 0 0) (end 4.62 0) (layer F.SilkS) (width 0.12)) + (fp_line (start 0 0) (end 7.62 0) (layer F.Fab) (width 0.1)) + (fp_line (start 4.62 0) (end 6.12 0) (layer F.SilkS) (width 0.12)) + (fp_line (start -4.75 -4.75) (end -4.75 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 4.75) (end 9.07 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 4.75) (end 9.07 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 -4.75) (end -4.75 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_text user %R (at 0 -1.7) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (pad 1 thru_hole circle (at 0 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 53 "Net-(R3-Pad1)")) + (pad 2 thru_hole oval (at 7.62 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 54 "Net-(R3-Pad2)")) + (model ${KISYS3DMOD}/Resistor_THT.3dshapes/R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical.wrl + (at (xyz 0 0 0)) + (scale (xyz 1 1 1)) + (rotate (xyz 0 0 0)) + ) + ) + + (module Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical (layer F.Cu) (tedit 5AE5139B) (tstamp 5C3144B6) + (at 71.12 86.36) + (descr "Resistor, Axial_DIN0922 series, Axial, Vertical, pin pitch=7.62mm, 5W, length*diameter=20*9mm^2, http://www.vishay.com/docs/20128/wkxwrx.pdf") + (tags "Resistor Axial_DIN0922 series Axial Vertical pin pitch 7.62mm 5W length 20mm diameter 9mm") + (path /5C24FAD5) + (fp_text reference R2 (at 3.81 -5.62) (layer F.SilkS) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text value 330 (at 3.81 5.62) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text user %R (at 0 -1.7) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_line (start 9.07 -4.75) (end -4.75 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 4.75) (end 9.07 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 4.75) (end 9.07 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 -4.75) (end -4.75 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 4.62 0) (end 6.12 0) (layer F.SilkS) (width 0.12)) + (fp_line (start 0 0) (end 7.62 0) (layer F.Fab) (width 0.1)) + (fp_circle (center 0 0) (end 4.62 0) (layer F.SilkS) (width 0.12)) + (fp_circle (center 0 0) (end 4.5 0) (layer F.Fab) (width 0.1)) + (pad 2 thru_hole oval (at 7.62 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 51 "Net-(R2-Pad2)")) + (pad 1 thru_hole circle (at 0 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 52 "Net-(R2-Pad1)")) + (model ${KISYS3DMOD}/Resistor_THT.3dshapes/R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical.wrl + (at (xyz 0 0 0)) + (scale (xyz 1 1 1)) + (rotate (xyz 0 0 0)) + ) + ) + + (module Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical (layer F.Cu) (tedit 5AE5139B) (tstamp 5C3144A7) + (at 71.12 74.93) + (descr "Resistor, Axial_DIN0922 series, Axial, Vertical, pin pitch=7.62mm, 5W, length*diameter=20*9mm^2, http://www.vishay.com/docs/20128/wkxwrx.pdf") + (tags "Resistor Axial_DIN0922 series Axial Vertical pin pitch 7.62mm 5W length 20mm diameter 9mm") + (path /5C24FA6F) + (fp_text reference R1 (at 3.81 -5.62) (layer F.SilkS) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_text value 10k (at 3.81 5.62) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (fp_circle (center 0 0) (end 4.5 0) (layer F.Fab) (width 0.1)) + (fp_circle (center 0 0) (end 4.62 0) (layer F.SilkS) (width 0.12)) + (fp_line (start 0 0) (end 7.62 0) (layer F.Fab) (width 0.1)) + (fp_line (start 4.62 0) (end 6.12 0) (layer F.SilkS) (width 0.12)) + (fp_line (start -4.75 -4.75) (end -4.75 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start -4.75 4.75) (end 9.07 4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 4.75) (end 9.07 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_line (start 9.07 -4.75) (end -4.75 -4.75) (layer F.CrtYd) (width 0.05)) + (fp_text user %R (at 0 -1.7) (layer F.Fab) + (effects (font (size 1 1) (thickness 0.15))) + ) + (pad 1 thru_hole circle (at 0 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 49 "Net-(R1-Pad1)")) + (pad 2 thru_hole oval (at 7.62 0) (size 2.4 2.4) (drill 1.2) (layers *.Cu *.Mask) + (net 50 "Net-(R1-Pad2)")) + (model ${KISYS3DMOD}/Resistor_THT.3dshapes/R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical.wrl + (at (xyz 0 0 0)) + (scale (xyz 1 1 1)) + (rotate (xyz 0 0 0)) + ) + ) + (module Package_DIP:DIP-40_W15.24mm (layer F.Cu) (tedit 5A02E8C5) (tstamp 5C1A0EB7) (at 109.22 59.69 90) (descr "40-lead though-hole mounted DIP package, row spacing 15.24 mm (600 mils)") diff --git a/src/tests/test_modules/test_modules.sch b/src/tests/test_modules/test_modules.sch index e3910ce..73dfad4 100644 --- a/src/tests/test_modules/test_modules.sch +++ b/src/tests/test_modules/test_modules.sch @@ -1,5 +1,4 @@ EESchema Schematic File Version 4 -LIBS:test_modules-cache EELAYER 26 0 EELAYER END $Descr A4 11693 8268 @@ -171,6 +170,50 @@ F 3 "http://aturing.umcs.maine.edu/~meadow/courses/cos335/Intel8255A.pdf" H 8500 1 8500 3700 1 0 0 -1 $EndComp +$Comp +L Device:R R1 +U 1 1 5C24FA6F +P 2800 4350 +F 0 "R1" H 2870 4396 50 0000 L CNN +F 1 "10k" H 2870 4305 50 0000 L CNN +F 2 "Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical" V 2730 4350 50 0001 C CNN +F 3 "~" H 2800 4350 50 0001 C CNN + 1 2800 4350 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R2 +U 1 1 5C24FAD5 +P 3100 4350 +F 0 "R2" H 3170 4396 50 0000 L CNN +F 1 "330" H 3170 4305 50 0000 L CNN +F 2 "Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical" V 3030 4350 50 0001 C CNN +F 3 "~" H 3100 4350 50 0001 C CNN + 1 3100 4350 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R3 +U 1 1 5C24FB07 +P 3400 4350 +F 0 "R3" H 3470 4396 50 0000 L CNN +F 1 "2 M" H 3470 4305 50 0000 L CNN +F 2 "Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical" V 3330 4350 50 0001 C CNN +F 3 "~" H 3400 4350 50 0001 C CNN + 1 3400 4350 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R4 +U 1 1 5C24FB98 +P 3700 4350 +F 0 "R4" H 3770 4396 50 0000 L CNN +F 1 "0.1uF" H 3770 4305 50 0000 L CNN +F 2 "Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical" V 3630 4350 50 0001 C CNN +F 3 "~" H 3700 4350 50 0001 C CNN + 1 3700 4350 + 1 0 0 -1 +$EndComp Wire Bus Line 3500 2350 3500 3050 Wire Bus Line