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