diff --git a/requirements.txt b/requirements.txt
index ece88e046512ba0e6abb71c2ea6520b6e3e212bc..aeaf1bc730c7ae583f7940b1bb2a73a4d230ab94 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,3 +12,5 @@ kaleido
 circlify
 shapely
 dash
+platformdirs
+importlib_metadata; python_version < '3.10'
diff --git a/rocolib/__init__.py b/rocolib/__init__.py
index 7a971c1f76035bbbc735c6da2f4a0dca1487ac70..49f4740578ac954616914f94dd1dad3e79874ed8 100644
--- a/rocolib/__init__.py
+++ b/rocolib/__init__.py
@@ -15,8 +15,3 @@ __author__ = 'UCLA LEMUR'
 __credits__ = 'The Laboratory for Embedded Machines and Ubiquitous Robots'
 __url__ = 'https://git.uclalemur.com/roco/rocolib'
 __description__ = 'Robot Compiler'
-
-ROCOLIB_DIR = dirname(realpath(__file__))
-
-def rocopath(f):
-    return relpath(f, ROCOLIB_DIR)
diff --git a/rocolib/__main__.py b/rocolib/__main__.py
index fc980a6f6d0ecd89ee182bfcf05298ad8d551d08..212d1c8f1d168d056906b7dbe659f1c1c2215b34 100644
--- a/rocolib/__main__.py
+++ b/rocolib/__main__.py
@@ -8,7 +8,7 @@ from os.path import basename
 from pprint import pprint
 from textwrap import shorten
 from dash import Dash
-from rocolib.library import getComponent, getComponentTree
+from rocolib.library import getComponent, getComponentTree, libDirs
 from rocolib.api.composables.graph.Joint import FingerJoint
 
 
@@ -58,30 +58,35 @@ def cli_argparse():
 
     parser = argparse.ArgumentParser()
 
+    parser.add_argument("--list", "-l", help="List known library components (use multiple times for more detail)",
+                        dest="component_list", action="append_const", const=1)
+    parser.add_argument("--plugins", "-L", help="List known plugins (use multiple times for more detail)",
+                        dest="plugin_list", action="append_const", const=1)
+
+    parser.add_argument("--file", "-f", type=str, help="Load component and parameters from file")
     parser.add_argument("component", nargs='?',
                         help="Name of the component you'd like to test")
-    parser.add_argument("--file", "-f", type=str, help="Load component and parameters from file")
-    parser.add_argument("--output", "-o", type=str, help="Output directory")
-    parser.add_argument("--list", "-l", action='store_true',
-                        help="List all known library components")
 
     parser.add_argument("--verbose", "-v", help="Increase logging level (can be used multiple times)",
                         dest="log_level", action="append_const", const=-1)
     parser.add_argument("--quiet", "-q", help="Decrease logging level (can be used multiple times)",
                         dest="log_level", action="append_const", const=1)
+
     parser.add_argument("-I", help="List component interfaces (use multiple times for more detail)",
                         dest="interface_list", action="append_const", const=1)
     parser.add_argument("-P", help="List component parameters (use multiple times for more detail)",
                         dest="param_list", action="append_const", const=1)
-    parser.add_argument("-t", type=float, help="Thickness (i.e. making with wood)")
-    if not rocoview:
-        parser.add_argument("-d", action='store_true', help="Display visualizations")
-    parser.add_argument("-s", type=str, action='append', help="Select specific outputs to save")
     parser.add_argument("-S", help="List composable outputs (use multiple times for more detail)",
                         dest="output_list", action="append_const", const=1)
+
     parser.add_argument("-p", nargs=2, action='append',
         metavar=("NAME", "VALUE"),
         help="Set component parameter NAME to value VALUE")
+    parser.add_argument("-t", type=float, help="Thickness (i.e. making with wood)")
+    parser.add_argument("--output", "-o", type=str, help="Output directory")
+    parser.add_argument("-s", type=str, action='append', help="Select specific outputs to save")
+    if not rocoview:
+        parser.add_argument("-d", action='store_true', help="Display visualizations")
 
     if len(sys.argv)==1:
         parser.print_help(sys.stderr)
@@ -99,13 +104,23 @@ def cli_argparse():
     log_level_name = LOG_LEVELS[log_level]
     logging.basicConfig(level=getattr(logging, log_level_name))
 
+    def printList(arg, lsfn, f=None):
+        if not arg:
+            return False
+        ls = lsfn(f)
+        ind = min(len(arg), len(ls)) - 1
+        pprint(ls[ind])
+        return True
+
     acted = False
-    if args.list:
-        t = getComponentTree()
-        for i, cs in enumerate(t):
-            chunks = [cs[x:x+4] for x in range(0, len(cs), 4)]
-            print(f"{i:2}: " + "\n    ".join((" ".join(x) for x in chunks)))
-        acted = True
+
+    acted |= printList(args.component_list, lambda c : (
+        dict(enumerate(getComponentTree())),
+    ) )
+    acted |= printList(args.plugin_list, lambda c : (
+        list(libDirs.keys()),
+        libDirs,
+    ) )
 
     if args.file:
         with open(args.file) as f:
@@ -125,38 +140,31 @@ def cli_argparse():
         outdir = not rocoview and "output" or None
 
     if args.component:
-        def printList(arg, lsfn, make=False):
-            if not arg:
-                return
-            f = getComponent(args.component)
-            if make:
-                f.make()
-            ls = lsfn(f)
-            ind = min(len(arg), len(ls)) - 1
-            pprint(ls[ind])
-            if not args.p or args.t or args.d:
-                exit(0)
-
-        printList(args.interface_list, lambda c : (lambda info : (
+        f = getComponent(args.component)
+
+        acted |= printList(args.interface_list, lambda c : (lambda info : (
             list(info.keys()),
             {k: v["port"] for k, v in info.items()},
             info,
-        ))(c.getInterfaceInfo()))
+        ))(c.getInterfaceInfo()), f)
 
-        printList(args.param_list, lambda c : (lambda info : (
+        acted |= printList(args.param_list, lambda c : (lambda info : (
             list(info.keys()),
             {k: v["defaultValue"] for k, v in info.items()},
             {k: {x: v[x] for x in ('defaultValue', 'spec')} for k,v in info.items()},
             info,
-        ))(c.getParameterInfo()))
+        ))(c.getParameterInfo()), f)
+
+        f.make()
 
-        printList(args.output_list, lambda c : (lambda info : (
+        acted |= printList(args.output_list, lambda c : (lambda info : (
             {composable: list(outputs.keys()) for composable, outputs in info.items()},
             {composable: {k: {x: v[x] for x in ('stub', 'widget')} for k, v in outputs.items()} for composable, outputs in info.items()},
             info,
-        ))(c.listOutputs()), make=True)
+        ))(c.listOutputs()), f)
 
-        test(args.component, args.p, thickness=args.t, outdir=outdir, display=args.d, outputs=args.s)
+        if args.p or args.t or args.d or args.s or args.output or not acted:
+            test(args.component, args.p, thickness=args.t, outdir=outdir, display=args.d, outputs=args.s)
         acted = True
 
     if not acted:
diff --git a/rocolib/api/components/Component.py b/rocolib/api/components/Component.py
index dd253459dbec4306292b4352b7e593f29b3767c9..9776246ff74f691f781c02462fa8dba858c1e484 100644
--- a/rocolib/api/components/Component.py
+++ b/rocolib/api/components/Component.py
@@ -4,39 +4,28 @@ import sys
 import yaml
 import logging
 import networkx as nx
+import inspect
 from circlify import circlify
 
-from rocolib import ROCOLIB_DIR
 from rocolib.api.composables.ComponentComposable import ComponentComposable
 from rocolib.api.Parameterized import Parameterized
 from rocolib.api.Function import YamlFunction
 from rocolib.utils.utils import prefix as prefixString
 from rocolib.utils.utils import tryImport
-from rocolib.utils.io import load_yaml
 from rocolib.utils.nx2go import GraphVisualization as gv
+from rocolib.library import getComponent
 from dash import html, dcc
 
 
 log = logging.getLogger(__name__)
-# XXX circular import: 
-# from rocolib.library import ROCOLIB_LIBRARY
-# instead: 
-ROCOLIB_LIBRARY = os.path.join(ROCOLIB_DIR, "library")
-
-def getSubcomponentObject(component, name=None):
-    try:
-        obj = tryImport(component, component)
-        # XXX hack to get around derived components not having name parameter in their __init__
-        c = obj()
-        c.name = name
-        return c
-    except ImportError:
-        return newComponent(name, component)
-
-def newComponent(name, yamlFile=None):
-    class C(Component):
+
+def newComponent(cName, name=None, yamlFile=None):
+    class _C(Component):
+        def __init__(self):
+            Component.__init__(self, name, yamlFile)
+            self.cName = cName
         def define(self): pass
-    return C(name, yamlFile=yamlFile)
+    return _C()
 
 class Component(Parameterized):
     @classmethod
@@ -49,29 +38,31 @@ class Component(Parameterized):
 
     def __init__(self, name=None, yamlFile=None):
         Parameterized.__init__(self, name)
-        self.cName = type(self).__name__
+        self.cName = self.__class__.__name__
         self.reset()
-        yf = yamlFile
+
         if not yamlFile:
-            yf = type(self).__name__ + ".yaml"
+            filepath = inspect.getmodule(self.__class__).__file__
+            base, ext = os.path.splitext(filepath)
+            yf = base + ".yaml"
+            if os.path.isfile(yf):
+                yamlFile = yf
+                log.debug(f"Found implicit yamlFile at {yamlFile}")
         else:
-            self.cName = yf
-        for fn in (yf, 
-                   os.path.join(ROCOLIB_DIR, yf), 
-                   os.path.join(ROCOLIB_LIBRARY, yf)):
-            try:
-                self.fromYaml(fn)
-                break
-            except IOError:
-                pass
-            if yamlFile:
-                raise ValueError(f"No suitable yamlfile found for {yamlFile}")
+            self.cName, _ = os.path.splitext(os.path.basename(yamlFile))
+        log.debug(f"Creating new Component with cName {self.cName}")
+
+        if yamlFile:
+            log.debug(f"Loading details from {yamlFile}")
+            self.fromYaml(yamlFile)
+
         self.composables['component'] = ComponentComposable(self)
         self.predefine()
         self.define()
 
     def fromYaml(self, filename):
-        definition = load_yaml(filename)
+        with open(filename, 'r') as fd:
+            definition = yaml.safe_load(fd)
 
         # keys are (parameters, subcomponents, connections, interfaces)
         if "parameters" in definition:
@@ -136,8 +127,9 @@ class Component(Parameterized):
                     should be python file/class or yaml name
         :type  classname: str or unicode
         '''
+
         # XXX will silently fail if subcomponent name is already taken?
-        obj = getSubcomponentObject(classname, self.name + '.' + name)
+        obj = getComponent(classname, name = self.name + '.' + name)
         sc = {"classname": classname, "parameters": {}, "kwargs": kwargs, "object": obj}
         self.subcomponents.setdefault(name, sc)
 
@@ -291,16 +283,11 @@ class Component(Parameterized):
     def delParameter(self, name):
     '''
 
-    def toLibrary(self):
-        # XXX TODO: Check for collisions!  
-        # if collision:
-        #   flag to allow rebuilding, and fail otherwise?  
-        #   if no flag, check if source matches and rebuild if so, fail otherwise?
-        return self.toYaml(ROCOLIB_LIBRARY, self.name + ".yaml")
-
-    def toYaml(self, basedir, filename):
+    def toYaml(self, basedir, filename, reldir=None):
+        if reldir is None:
+            reldir = basedir
         filepath = os.path.join(basedir, filename)
-        source = os.path.relpath(sys.argv[0], basedir).replace(os.sep, posixpath.sep)
+        source = os.path.relpath(sys.argv[0], reldir).replace(os.sep, posixpath.sep)
 
         parameters = {}
         for k, v in self._parameters.items():
@@ -559,12 +546,7 @@ class Component(Parameterized):
             self.make(ka.pop("useDefaultParameters", True))
             log.debug(f"... done making {self.name}.")
 
-        # XXX: Is this the right way to do it?
-        import os
-        try:
-            filedir and os.makedirs(filedir)
-        except:
-            pass
+        filedir and os.makedirs(filedir, exist_ok=True)
 
         rets = {}
         for (name, composable) in self.composables.items():
diff --git a/rocolib/builders/ChairBackBuilder.py b/rocolib/builders/ChairBackBuilder.py
deleted file mode 100644
index 9c6792dc195a2635b97fc5b59cb1ae93dac85aa9..0000000000000000000000000000000000000000
--- a/rocolib/builders/ChairBackBuilder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("ChairBack")
-
-c.addSubcomponent("panel","ChairPanel", inherit=("width", "thickness"), prefix=None)
-
-c.addParameter("gapheight", 20, paramType="length")
-c.addParameter("backheight", 40, paramType="length")
-
-c.addConstraint(("panel","depth"), "backheight")
-
-c.addSubcomponent("sider","Rectangle")
-c.addConstraint(("sider","l"), "thickness")
-c.addConstraint(("sider","w"), "gapheight")
-c.addConnection(("panel","tr"),("sider","t"), angle=0)
-c.inheritInterface("right", ("sider", "b"))
-
-c.addSubcomponent("sidel","Rectangle")
-c.addConstraint(("sidel","l"), "thickness")
-c.addConstraint(("sidel","w"), "gapheight")
-c.addConnection(("panel","tl"),("sidel","t"), angle=0)
-c.inheritInterface("left", ("sidel", "b"))
-
-c.toLibrary()
diff --git a/rocolib/builders/ChairPanelBuilder.py b/rocolib/builders/ChairPanelBuilder.py
deleted file mode 100644
index 099b78008b1133e13b8851afd772cee723729ceb..0000000000000000000000000000000000000000
--- a/rocolib/builders/ChairPanelBuilder.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("ChairPanel")
-
-c.addSubcomponent("back","Rectangle", root=True)
-c.addSubcomponent("sidel","Rectangle")
-c.addSubcomponent("sider","Rectangle")
-
-c.addParameter("depth", 50, paramType="length")
-c.addParameter("width", 70, paramType="length")
-c.addParameter("thickness", 10, paramType="length")
-
-c.addConstraint(("back","l"), "width")
-c.addConstraint(("back","w"), "depth")
-
-c.addConstraint(("sidel","l"), "thickness")
-c.addConstraint(("sidel","w"), "depth")
-c.addConstraint(("sider","l"), "thickness")
-c.addConstraint(("sider","w"), "depth")
-
-c.addConnection(("sidel","r"),("back","l"), angle=90)
-c.addConnection(("back","r"),("sider","l"), angle=90)
-
-c.inheritInterface("tr", ("sider", "t"))
-c.inheritInterface("tl", ("sidel", "t"))
-c.inheritInterface("br", ("sider", "b"))
-c.inheritInterface("bl", ("sidel", "b"))
-c.inheritInterface("right", ("sider", "r"))
-c.inheritInterface("left", ("sidel", "l"))
-
-c.toLibrary()
diff --git a/rocolib/builders/ChairSeatBuilder.py b/rocolib/builders/ChairSeatBuilder.py
deleted file mode 100644
index 6cd1524d41ba0bb17df5cb15d6e8a26dd8a77c07..0000000000000000000000000000000000000000
--- a/rocolib/builders/ChairSeatBuilder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("ChairSeat")
-
-c.addSubcomponent("seat","ChairPanel", inherit=True, prefix=None, root=True)
-c.addSubcomponent("back","ChairBack", inherit=True, prefix=None)
-c.addSubcomponent("kitel","Kite", inherit="thickness", prefix=None)
-c.addSubcomponent("kiter","Kite", inherit="thickness", prefix=None)
-
-c.addParameter("recline", 110, paramType="angle")
-
-c.addConstraint(("kitel","angle"), "recline", "180 - x")
-c.addConstraint(("kiter","angle"), "recline", "180 - x")
-
-c.addConnection(("back","left"),("kitel","t"), angle=0)
-c.addConnection(("seat","bl"),("kitel","b"), angle=0)
-
-c.addConnection(("back","right"),("kiter","b"), angle=0)
-c.addConnection(("seat","br"),("kiter","t"), angle=0)
-
-c.inheritInterface("right", ("seat", "right"))
-c.inheritInterface("left", ("seat", "left"))
-
-c.toLibrary()
diff --git a/rocolib/builders/ESPSegBuilder.py b/rocolib/builders/ESPSegBuilder.py
index 60207d7e41aa1a6bbca292240f29e88fe6736dd1..f7899bc49bd2dbc296b0b1c3c26c4d95b0ed6c87 100644
--- a/rocolib/builders/ESPSegBuilder.py
+++ b/rocolib/builders/ESPSegBuilder.py
@@ -1,5 +1,6 @@
 from rocolib.library import getComponent
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 from rocolib.api.Function import Function
 
 c = newComponent("ESPSeg")
@@ -115,4 +116,4 @@ c.addConnection(("left", "face0"),
 c.addConnection(("brain", "face1"),
                 ("sheath", "face1"), copyDecorations=True, transform=False)
 
-c.toLibrary()
+save(c, True)
diff --git a/rocolib/builders/MountedBrainsBuilder.py b/rocolib/builders/MountedBrainsBuilder.py
index aa8beeb249da735a28a9a5c265746527301ac81d..5983744f121694b71a2b3e8d21bb3ec9433ae6a7 100644
--- a/rocolib/builders/MountedBrainsBuilder.py
+++ b/rocolib/builders/MountedBrainsBuilder.py
@@ -1,29 +1,30 @@
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 from rocolib.api.Function import Function
 
 
-self = newComponent("MountedBrains")
+c = newComponent("MountedBrains")
 
-self.addSubcomponent("beam", "SimpleRectBeam")
-self.addSubcomponent("brain", "Brains", inherit="brain", prefix=None)
+c.addSubcomponent("beam", "SimpleRectBeam")
+c.addSubcomponent("brain", "Brains", inherit="brain", prefix=None)
 
-self.addParameter("length", 90, paramType="length")
-self.addParameter("width", 50, paramType="length")
-self.addParameter("depth", 20, paramType="length")
-self.addParameter("offset", (0,0))
+c.addParameter("length", 90, paramType="length")
+c.addParameter("width", 50, paramType="length")
+c.addParameter("depth", 20, paramType="length")
+c.addParameter("offset", (0,0))
 
-self.addConstraint(("beam", "width"), "depth")
-self.addConstraint(("beam", "depth"), "width")
-self.addConstraint(("beam", "length"), "length")
+c.addConstraint(("beam", "width"), "depth")
+c.addConstraint(("beam", "depth"), "width")
+c.addConstraint(("beam", "length"), "length")
 
-self.addConstraint(("beam", "width#minValue"), "brain", 'x.get("height")')
-self.addConstraint(("beam", "depth#minValue"), "brain", 'x.get("width")')
-self.addConstraint(("beam", "length#minValue"), "brain", 'x.get("length")')
+c.addConstraint(("beam", "width#minValue"), "brain", 'x.get("height")')
+c.addConstraint(("beam", "depth#minValue"), "brain", 'x.get("width")')
+c.addConstraint(("beam", "length#minValue"), "brain", 'x.get("length")')
 
-self.addConnection(("beam", "face1"),
+c.addConnection(("beam", "face1"),
                    ("brain", "decoration"),
                    mode="hole", offset=Function(params=("offset")))
 
-self.inheritAllInterfaces("beam", prefix=None)
+c.inheritAllInterfaces("beam", prefix=None)
 
-self.toLibrary()
+save(c, True)
diff --git a/rocolib/builders/MountedServoBuilder.py b/rocolib/builders/MountedServoBuilder.py
index 2b667d30b5d46e65dfcba3a824d618f7ceea4d84..82e784d492ff2bbc96fba645476db2d09bf84f83 100644
--- a/rocolib/builders/MountedServoBuilder.py
+++ b/rocolib/builders/MountedServoBuilder.py
@@ -1,4 +1,5 @@
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 from rocolib.api.Function import Function
 
 c = newComponent("MountedServo")
@@ -11,4 +12,4 @@ c.inheritAllInterfaces("servo", prefix=None)
 c.addConnection(("mount", "mount.decoration"),
                 ("servo", "mount"))
 
-c.toLibrary()
+save(c, True)
diff --git a/rocolib/builders/PaperbotBuilder.py b/rocolib/builders/PaperbotBuilder.py
index 92e08209cbcb73ccf4c340be6e6ecbcfcd685db6..85e9f6c4b94ce3879d104fd5a5eb28bf80dfa8dc 100644
--- a/rocolib/builders/PaperbotBuilder.py
+++ b/rocolib/builders/PaperbotBuilder.py
@@ -1,4 +1,5 @@
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 
 
 c = newComponent("Paperbot")
@@ -8,4 +9,4 @@ c.addParameter("height", 25, paramType="length", minValue=20)
 
 c.addSubcomponent("paperbot", "ESPSeg", inherit="length width height battery tire_thickness".split(), prefix=None)
 
-c.toLibrary()
+save(c, True)
diff --git a/rocolib/builders/RockerChairBuilder.py b/rocolib/builders/RockerChairBuilder.py
deleted file mode 100644
index be7dc6302d320f6455c3b830b15804dbfa5a96fe..0000000000000000000000000000000000000000
--- a/rocolib/builders/RockerChairBuilder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("RockerChair")
-
-c.addSubcomponent("seat","ChairSeat", inherit=True, prefix=None, root=True)
-c.addSubcomponent("legl","RockerLeg", inherit=True, prefix=None, mirror=True)
-c.addSubcomponent("legr","RockerLeg", inherit=True, prefix=None)
-c.addSubcomponent("crossbar","Rectangle")
-
-c.addConstraint(("crossbar","l"), "width")
-c.addConstraint(("crossbar","w"), ("height", "rocker"), "x[0] * np.sin(np.deg2rad(x[1]))")
-
-c.addConnection(("seat","left"),("legl","topedge"), angle=0)
-c.addConnection(("seat","right"),("legr","topedge"), angle=0)
-
-c.addConnection(("crossbar","l"),("legl","crossbar"), angle=90)
-c.addConnection(("crossbar","r"),("legr","crossbar"), angle=90)
-
-c.toLibrary()
diff --git a/rocolib/builders/RockerLegBuilder.py b/rocolib/builders/RockerLegBuilder.py
deleted file mode 100644
index 2e7d63b99e424a1735e300b484b57ed3614c8f24..0000000000000000000000000000000000000000
--- a/rocolib/builders/RockerLegBuilder.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from numbers import Number
-
-from rocolib.api.components.Component import newComponent
-
-
-c = newComponent("RockerLeg")
-
-c.addParameter("height", 40, paramType="length")
-c.addParameter("depth", 50, paramType="length")
-c.addParameter("thickness", 10, paramType="length")
-c.addParameter("rocker", 10, paramType="angle")
-
-l = [
-    ["depth"],
-    ["height"],
-    ["depth"],
-    ["height"],
-    (("height", "rocker"), "x[0] * np.sin(np.deg2rad(x[1]))"),
-    (("height", "rocker"), "x[0] * np.sin(np.deg2rad(x[1]))"),
-    ]
-
-a = [
-    90,
-    90,
-    ["rocker", "90-(x*2)"],
-    ["rocker", "90+x"],
-    0,
-    ["rocker"],
-    ]
-
-n = len(l)
-
-for i in range(n):
-    c.addSubcomponent("beam%d" % i, "Rectangle")
-    c.addSubcomponent("kite%d" % i, "Kite", inherit="thickness", prefix=None)
-
-    c.addConstraint(("beam%d" % i, "w"), *l[i])
-    c.addConstraint(("beam%d" % i, "l"), "thickness")
-    if isinstance(a[i], Number):
-        c.addConstConstraint(("kite%d" % i, "angle"), a[i])
-    else:
-        c.addConstraint(("kite%d" % i, "angle"), *a[i])
-
-    c.addConnection(("beam%d" % i,"t"),("kite%d" % i,"b"), angle=0)
-    if i:
-        c.addConnection(("beam%d" % i,"b"),("kite%d" % ((i - 1) % n),"t"), angle=0)
-
-c.addConnection(("beam0","b"),("kite%d" % (n - 1),"t"), angle=0)
-
-
-c.inheritInterface("topedge", ("beam2", "r"))
-c.inheritInterface("crossbar", ("beam5", "l"))
-
-c.toLibrary()
diff --git a/rocolib/builders/ServoMountBuilder.py b/rocolib/builders/ServoMountBuilder.py
index 0640496730cd357bfb9b4e247767791120f9a22d..6bce1cc6cd61fb74376c8a8a167641a6ca61d42c 100644
--- a/rocolib/builders/ServoMountBuilder.py
+++ b/rocolib/builders/ServoMountBuilder.py
@@ -1,5 +1,6 @@
 from rocolib.library import getComponent
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 from rocolib.api.Function import Function
 
 c = newComponent("ServoMount")
@@ -28,4 +29,4 @@ c.addConnection(("beam", "face0"),
                 ("mount", "decoration"),
                 mode="hole", offset=Function(params="offset"))
 
-c.toLibrary()
+save(c, True)
diff --git a/rocolib/builders/SimpleChairBuilder.py b/rocolib/builders/SimpleChairBuilder.py
deleted file mode 100644
index 5feeff319a65a3618216bab4766a0763fd01b865..0000000000000000000000000000000000000000
--- a/rocolib/builders/SimpleChairBuilder.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("SimpleChair")
-
-c.addSubcomponent("seat","ChairSeat", inherit=True, prefix=None, root=True)
-c.addSubcomponent("legl","VLeg", inherit=True, prefix=None)
-c.addSubcomponent("legr","VLeg", inherit=True, prefix=None)
-
-c.addConstraint(("legl","width"), "depth")
-c.addConstraint(("legr","width"), "depth")
-
-c.addConnection(("seat","left"),("legl","topedge"), angle=0)
-c.addConnection(("seat","right"),("legr","topedge"), angle=0)
-
-c.toLibrary()
diff --git a/rocolib/builders/SimpleTableBuilder.py b/rocolib/builders/SimpleTableBuilder.py
deleted file mode 100644
index 745c988866bffb3b00c528822b1ae1e55f8934b2..0000000000000000000000000000000000000000
--- a/rocolib/builders/SimpleTableBuilder.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from rocolib.api.components.Component import newComponent
-
-c = newComponent("SimpleTable")
-
-c.addSubcomponent("top","Rectangle", root=True)
-c.addSubcomponent("legl","VLeg", inherit=True, prefix=None)
-c.addSubcomponent("legr","VLeg", inherit=True, prefix=None)
-c.addSubcomponent("legt","VLeg", inherit=True, prefix=None)
-c.addSubcomponent("legb","VLeg", inherit=True, prefix=None)
-
-c.addParameter("length", 70, paramType="length")
-
-c.addConstraint(("top","l"), "length")
-c.addConstraint(("top","w"), "width")
-
-c.addConstraint(("legt","width"), "length")
-c.addConstraint(("legb","width"), "length")
-
-c.addConnection(("top","l"),("legl","topedge"), angle=90)
-c.addConnection(("top","r"),("legr","topedge"), angle=90)
-c.addConnection(("top","t"),("legt","topedge"), angle=90)
-c.addConnection(("top","b"),("legb","topedge"), angle=90)
-
-c.addConnection(("legl","rightedge"),("legb","leftedge"), angle=90)
-c.addConnection(("legb","rightedge"),("legr","leftedge"), angle=90)
-c.addConnection(("legr","rightedge"),("legt","leftedge"), angle=90)
-#c.addConnection(("legt","rightedge"),("legl","leftedge"), angle=90)
-c.addConnection(("legl","leftedge"),("legt","rightedge"), angle=90)
-
-c.toLibrary()
diff --git a/rocolib/builders/WheelBuilder.py b/rocolib/builders/WheelBuilder.py
index 01c9b9a91b80169e9dde634738d410c3cd69335f..e735fd092d0f521066f59856e56e446d2f777395 100644
--- a/rocolib/builders/WheelBuilder.py
+++ b/rocolib/builders/WheelBuilder.py
@@ -1,4 +1,5 @@
 from rocolib.api.components.Component import newComponent
+from rocolib.library import save
 from rocolib.api.Function import Function
 
 c = newComponent("Wheel")
@@ -11,4 +12,4 @@ c.inheritAllInterfaces("drive", prefix=None)
 c.addConnection(("drive", "horn"),
                 ("tire", "face"), copyDecorations=True)
 
-c.toLibrary()
+save(c, True)
diff --git a/rocolib/library/BoatPoint.py b/rocolib/library/BoatPoint.py
deleted file mode 100644
index 788d0eb4344b48f9c1fa7a4de638ae13ce96011b..0000000000000000000000000000000000000000
--- a/rocolib/library/BoatPoint.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from rocolib.api.components import FoldedComponent
-from rocolib.api.composables.graph.Face import Face, Rectangle, RightTriangle
-import rocolib.utils.numsym as math
-
-
-def pyramid(l,w,h):
-    def baseangle(x, y):
-        x2 = x/2.
-        hx = math.sqrt(h*h+x2*x2)
-        xa = math.rad2deg(math.arctan2(h,x2))
-        ba = math.rad2deg(math.arctan2(hx,y/2.))
-        return x2, hx, xa, ba
-
-    l2, hl, la, bla = baseangle(l, w)
-    w2, hw, wa, bwa = baseangle(w, l)
-    diag = math.sqrt(h*h + w2*w2 + l2+l2)
-    da = math.rad2deg(math.arccos(1/math.sqrt((1 + h*h/w2/w2)*(1+h*h/l2/l2))))
-    return hl, la, bla, hw, wa, bwa, diag, da
-
-
-class BoatPoint(FoldedComponent):
-    def define(self):
-        self.addParameter("width", 50, paramType="length")
-        self.addParameter("depth", 25, paramType="length")
-        self.addParameter("point", 50, paramType="length", minValue=0)
-
-        self.addEdgeInterface("ledge", "sl.e1", "depth")
-        self.addEdgeInterface("cedge", "sc.e2", "width")
-        self.addEdgeInterface("redge", "sr.e1", "depth")
-
-        self.addEdgeInterface("edge", ["sl.e1", "sc.e2", "sr.e1"], ["depth", "width", "depth"])
-
-    def assemble(self):
-        w = self.p.width
-        d = self.p.depth
-        p = self.p.point
-
-        hl, la, bla, hw, wa, bwa, diag, da = pyramid(d*2,w,p)
-
-        # Flaps
-        ba = (180 - bla - bwa)/2.
-        fx = d * math.tan(math.deg2rad(ba))
-
-        fll = Face("", ((fx, 0), (hw, 0), (0, d), (0,0)))
-        flc = RightTriangle("", w/2., hl);
-        frc = RightTriangle("", hl, w/2.);
-        frr = Face("", ((d, 0), (0, hw), (0, fx), (0,0)))
-
-        # Main point
-        self.addFace(fll, "lt");
-        self.attachEdge("lt.e2", flc, "e1", prefix="lc", angle=da)
-        self.attachEdge("lc.e2", frc, "e0", prefix="rc", angle=0)
-        self.attachEdge("rc.e1", frr, "e1", prefix="rt", angle=da)
-
-        # Flaps
-        fla = RightTriangle("", d, fx);
-        flb = RightTriangle("", fx, d);
-        self.attachEdge("lt.e3", fla, "e0", prefix="fla", angle=180)
-        self.attachEdge("fla.e1", flb, "e1", prefix="flb", angle=-180)
-
-        fra = RightTriangle("", fx, d);
-        frb = RightTriangle("", d, fx);
-        self.attachEdge("rt.e0", fra, "e2", prefix="fra", angle=180)
-        self.attachEdge("fra.e1", frb, "e1", prefix="frb", angle=-180)
-
-        self.addTab("flb.e0", "lt.e0", angle = -174, width=fx/3)
-        self.addTab("frb.e2", "rt.e3", angle = -174, width=fx/3)
-
-        # To allow 0 degree attachments at base
-        se = Face("", ((0,0), (w/2., 0), (-w/2., 0)))
-        self.attachEdge("lc.e0", se, "e0", prefix="sc", angle=90-la)
-        self.mergeEdge("rc.e2", "sc.e1", angle=90-la)
-
-        se = Face("", ((0,0), (d,0)))
-        self.attachEdge("flb.e2", se, "e0", prefix="sl", angle=90-wa)
-
-        se = Face("", ((0,0), (d,0)))
-        self.attachEdge("frb.e0", se, "e0", prefix="sr", angle=90-wa)
-
-if __name__ == "__main__":
-    BoatPoint.test()
-
diff --git a/rocolib/library/Brains.py b/rocolib/library/Brains.py
index dfc5b9841a6909ba3e102b02f01de438fc617480..26f7a2c9a1582a854d458e7f2a18dd7884b45609 100644
--- a/rocolib/library/Brains.py
+++ b/rocolib/library/Brains.py
@@ -36,15 +36,15 @@ class Brains(ThruHole):
     def define(self):
         ThruHole.define(self)
         self.addParameter("brain", "nodeMCU", values=self.brains, overrides = dict(
-            nrows = 'c.getParameter("brain").get("nrows")',
-            ncols = 'c.getParameter("brain").get("ncols")',
-            rowsep = 'c.getParameter("brain").get("rowsep")',
-            colsep = 'c.getParameter("brain").get("colsep")',
+            nrows = 'c.p.brain.get("nrows")',
+            ncols = 'c.p.brain.get("ncols")',
+            rowsep = 'c.p.brain.get("rowsep")',
+            colsep = 'c.p.brain.get("colsep")',
         ))
 
     def assemble(self):
         ThruHole.assemble(self)
-        brain = self.getParameter("brain")
+        brain = self.p.brain
         self.graph.dotransform(origin = brain.get("origin"))
         for n, e in brain.get("extras").items():
             x, y = e.get("center", (0,0))
diff --git a/rocolib/library/ChairBack.yaml b/rocolib/library/ChairBack.yaml
deleted file mode 100644
index b14efdb46c51e598fe2518a4614591cbaa8b35da..0000000000000000000000000000000000000000
--- a/rocolib/library/ChairBack.yaml
+++ /dev/null
@@ -1,73 +0,0 @@
-connections:
-  connection0:
-  - - panel
-    - tr
-  - - sider
-    - t
-  - angle: 0
-  connection1:
-  - - panel
-    - tl
-  - - sidel
-    - t
-  - angle: 0
-interfaces:
-  left:
-    interface: b
-    subcomponent: sidel
-  right:
-    interface: b
-    subcomponent: sider
-parameters:
-  backheight:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  gapheight:
-    defaultValue: 20
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/ChairBackBuilder.py
-subcomponents:
-  panel:
-    classname: ChairPanel
-    kwargs: {}
-    parameters:
-      depth:
-        parameter: backheight
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
-  sidel:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: gapheight
-  sider:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: gapheight
diff --git a/rocolib/library/ChairPanel.yaml b/rocolib/library/ChairPanel.yaml
deleted file mode 100644
index 58fb8d8a3bcf094aaf758eb024cae05f14afe9af..0000000000000000000000000000000000000000
--- a/rocolib/library/ChairPanel.yaml
+++ /dev/null
@@ -1,78 +0,0 @@
-connections:
-  connection0:
-  - - sidel
-    - r
-  - - back
-    - l
-  - angle: 90
-  connection1:
-  - - back
-    - r
-  - - sider
-    - l
-  - angle: 90
-interfaces:
-  bl:
-    interface: b
-    subcomponent: sidel
-  br:
-    interface: b
-    subcomponent: sider
-  left:
-    interface: l
-    subcomponent: sidel
-  right:
-    interface: r
-    subcomponent: sider
-  tl:
-    interface: t
-    subcomponent: sidel
-  tr:
-    interface: t
-    subcomponent: sider
-parameters:
-  depth:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/ChairPanelBuilder.py
-subcomponents:
-  back:
-    classname: Rectangle
-    kwargs:
-      root: true
-    parameters:
-      l:
-        parameter: width
-      w:
-        parameter: depth
-  sidel:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: depth
-  sider:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: depth
diff --git a/rocolib/library/ChairSeat.yaml b/rocolib/library/ChairSeat.yaml
deleted file mode 100644
index 78bc361d75da67f4e4523d0ef760a3410e2f6193..0000000000000000000000000000000000000000
--- a/rocolib/library/ChairSeat.yaml
+++ /dev/null
@@ -1,113 +0,0 @@
-connections:
-  connection0:
-  - - back
-    - left
-  - - kitel
-    - t
-  - angle: 0
-  connection1:
-  - - seat
-    - bl
-  - - kitel
-    - b
-  - angle: 0
-  connection2:
-  - - back
-    - right
-  - - kiter
-    - b
-  - angle: 0
-  connection3:
-  - - seat
-    - br
-  - - kiter
-    - t
-  - angle: 0
-interfaces:
-  left:
-    interface: left
-    subcomponent: seat
-  right:
-    interface: right
-    subcomponent: seat
-parameters:
-  backheight:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  depth:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  gapheight:
-    defaultValue: 20
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  recline:
-    defaultValue: 110
-    spec:
-      maxValue: 360
-      minValue: 0
-      units: degrees
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/ChairSeatBuilder.py
-subcomponents:
-  back:
-    classname: ChairBack
-    kwargs: {}
-    parameters:
-      backheight:
-        parameter: backheight
-      gapheight:
-        parameter: gapheight
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
-  kitel:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle:
-        function: 180 - x
-        parameter: recline
-      thickness:
-        parameter: thickness
-  kiter:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle:
-        function: 180 - x
-        parameter: recline
-      thickness:
-        parameter: thickness
-  seat:
-    classname: ChairPanel
-    kwargs:
-      root: true
-    parameters:
-      depth:
-        parameter: depth
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
diff --git a/rocolib/library/Cutout.py b/rocolib/library/Cutout.py
index 06e6df69065e62f3a95e1dd138a4de800c1a57ca..5787e64e7a368de0f12567b87170997aa53c6ea4 100644
--- a/rocolib/library/Cutout.py
+++ b/rocolib/library/Cutout.py
@@ -6,8 +6,8 @@ class Cutout(DecorationComponent):
         self.addParameter("dx", 10, paramType="length")
         self.addParameter("dy", 20, paramType="length")
         self.addParameter("d", overrides={
-            "dx": 'c.getParameter("d")',
-            "dy": 'c.getParameter("d")',
+            "dx": 'c.p.d',
+            "dy": 'c.p.d',
         })
 
     def assemble(self):
diff --git a/rocolib/library/ESPSeg.yaml b/rocolib/library/ESPSeg.yaml
index 7de2c228fb1aaf9a34c2dc9021f3a9d6f39def98..28215c3a9ff7f4d99b4fa496b30d506572f7d62f 100644
--- a/rocolib/library/ESPSeg.yaml
+++ b/rocolib/library/ESPSeg.yaml
@@ -94,10 +94,10 @@ parameters:
     defaultValue: nodeMCU
     spec:
       overrides:
-        colsep: c.getParameter("brain").get("colsep")
-        ncols: c.getParameter("brain").get("ncols")
-        nrows: c.getParameter("brain").get("nrows")
-        rowsep: c.getParameter("brain").get("rowsep")
+        colsep: c.p.brain.get("colsep")
+        ncols: c.p.brain.get("ncols")
+        nrows: c.p.brain.get("nrows")
+        rowsep: c.p.brain.get("rowsep")
       values:
         nodeMCU:
           colsep: 22.86
diff --git a/rocolib/library/MountedBrains.yaml b/rocolib/library/MountedBrains.yaml
index aff7f4571405a33c673fc786fd3de74012fa9a7b..3dc99a0b7ec90dd482b3043758eb387cbea5ae5c 100644
--- a/rocolib/library/MountedBrains.yaml
+++ b/rocolib/library/MountedBrains.yaml
@@ -55,10 +55,10 @@ parameters:
     defaultValue: nodeMCU
     spec:
       overrides:
-        colsep: c.getParameter("brain").get("colsep")
-        ncols: c.getParameter("brain").get("ncols")
-        nrows: c.getParameter("brain").get("nrows")
-        rowsep: c.getParameter("brain").get("rowsep")
+        colsep: c.p.brain.get("colsep")
+        ncols: c.p.brain.get("ncols")
+        nrows: c.p.brain.get("nrows")
+        rowsep: c.p.brain.get("rowsep")
       values:
         nodeMCU:
           colsep: 22.86
diff --git a/rocolib/library/RockerChair.yaml b/rocolib/library/RockerChair.yaml
deleted file mode 100644
index 76795a885103f495986a5090e8d32f8401a9ba44..0000000000000000000000000000000000000000
--- a/rocolib/library/RockerChair.yaml
+++ /dev/null
@@ -1,132 +0,0 @@
-connections:
-  connection0:
-  - - seat
-    - left
-  - - legl
-    - topedge
-  - angle: 0
-  connection1:
-  - - seat
-    - right
-  - - legr
-    - topedge
-  - angle: 0
-  connection2:
-  - - crossbar
-    - l
-  - - legl
-    - crossbar
-  - angle: 90
-  connection3:
-  - - crossbar
-    - r
-  - - legr
-    - crossbar
-  - angle: 90
-interfaces: {}
-parameters:
-  backheight:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  depth:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  gapheight:
-    defaultValue: 20
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  height:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  recline:
-    defaultValue: 110
-    spec:
-      maxValue: 360
-      minValue: 0
-      units: degrees
-      valueType: (float, int)
-  rocker:
-    defaultValue: 10
-    spec:
-      maxValue: 360
-      minValue: 0
-      units: degrees
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/RockerChairBuilder.py
-subcomponents:
-  crossbar:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: width
-      w:
-        function: x[0] * np.sin(np.deg2rad(x[1]))
-        parameter:
-        - height
-        - rocker
-  legl:
-    classname: RockerLeg
-    kwargs:
-      mirror: true
-    parameters:
-      depth:
-        parameter: depth
-      height:
-        parameter: height
-      rocker:
-        parameter: rocker
-      thickness:
-        parameter: thickness
-  legr:
-    classname: RockerLeg
-    kwargs: {}
-    parameters:
-      depth:
-        parameter: depth
-      height:
-        parameter: height
-      rocker:
-        parameter: rocker
-      thickness:
-        parameter: thickness
-  seat:
-    classname: ChairSeat
-    kwargs:
-      root: true
-    parameters:
-      backheight:
-        parameter: backheight
-      depth:
-        parameter: depth
-      gapheight:
-        parameter: gapheight
-      recline:
-        parameter: recline
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
diff --git a/rocolib/library/RockerLeg.yaml b/rocolib/library/RockerLeg.yaml
deleted file mode 100644
index 59c4027f4f3d1ce064f26393fd8e54989cd30ddf..0000000000000000000000000000000000000000
--- a/rocolib/library/RockerLeg.yaml
+++ /dev/null
@@ -1,207 +0,0 @@
-connections:
-  connection0:
-  - - beam0
-    - t
-  - - kite0
-    - b
-  - angle: 0
-  connection1:
-  - - beam1
-    - t
-  - - kite1
-    - b
-  - angle: 0
-  connection10:
-  - - beam5
-    - b
-  - - kite4
-    - t
-  - angle: 0
-  connection11:
-  - - beam0
-    - b
-  - - kite5
-    - t
-  - angle: 0
-  connection2:
-  - - beam1
-    - b
-  - - kite0
-    - t
-  - angle: 0
-  connection3:
-  - - beam2
-    - t
-  - - kite2
-    - b
-  - angle: 0
-  connection4:
-  - - beam2
-    - b
-  - - kite1
-    - t
-  - angle: 0
-  connection5:
-  - - beam3
-    - t
-  - - kite3
-    - b
-  - angle: 0
-  connection6:
-  - - beam3
-    - b
-  - - kite2
-    - t
-  - angle: 0
-  connection7:
-  - - beam4
-    - t
-  - - kite4
-    - b
-  - angle: 0
-  connection8:
-  - - beam4
-    - b
-  - - kite3
-    - t
-  - angle: 0
-  connection9:
-  - - beam5
-    - t
-  - - kite5
-    - b
-  - angle: 0
-interfaces:
-  crossbar:
-    interface: l
-    subcomponent: beam5
-  topedge:
-    interface: r
-    subcomponent: beam2
-parameters:
-  depth:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  height:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  rocker:
-    defaultValue: 10
-    spec:
-      maxValue: 360
-      minValue: 0
-      units: degrees
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/RockerLegBuilder.py
-subcomponents:
-  beam0:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: depth
-  beam1:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: height
-  beam2:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: depth
-  beam3:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        parameter: height
-  beam4:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        function: x[0] * np.sin(np.deg2rad(x[1]))
-        parameter: &id001
-        - height
-        - rocker
-  beam5:
-    classname: Rectangle
-    kwargs: {}
-    parameters:
-      l:
-        parameter: thickness
-      w:
-        function: x[0] * np.sin(np.deg2rad(x[1]))
-        parameter: *id001
-  kite0:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle: 90
-      thickness:
-        parameter: thickness
-  kite1:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle: 90
-      thickness:
-        parameter: thickness
-  kite2:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle:
-        function: 90-(x*2)
-        parameter: rocker
-      thickness:
-        parameter: thickness
-  kite3:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle:
-        function: 90+x
-        parameter: rocker
-      thickness:
-        parameter: thickness
-  kite4:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle: 0
-      thickness:
-        parameter: thickness
-  kite5:
-    classname: Kite
-    kwargs: {}
-    parameters:
-      angle:
-        parameter: rocker
-      thickness:
-        parameter: thickness
diff --git a/rocolib/library/SimpleChair.yaml b/rocolib/library/SimpleChair.yaml
deleted file mode 100644
index 672e7b286a2f4e553a2ff625350097c66e182dcd..0000000000000000000000000000000000000000
--- a/rocolib/library/SimpleChair.yaml
+++ /dev/null
@@ -1,184 +0,0 @@
-connections:
-  connection0:
-  - - seat
-    - left
-  - - legl
-    - topedge
-  - angle: 0
-  connection1:
-  - - seat
-    - right
-  - - legr
-    - topedge
-  - angle: 0
-interfaces: {}
-parameters:
-  _dx:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _dy:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _dz:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _q_a:
-    defaultValue: 1
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_i:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_j:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_k:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  backheight:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  depth:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  gapheight:
-    defaultValue: 20
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  height:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  recline:
-    defaultValue: 110
-    spec:
-      maxValue: 360
-      minValue: 0
-      units: degrees
-      valueType: (float, int)
-  taper:
-    defaultValue: 0.5
-    spec:
-      maxValue: 1
-      minValue: 0
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/SimpleChairBuilder.py
-subcomponents:
-  legl:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: depth
-  legr:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: depth
-  seat:
-    classname: ChairSeat
-    kwargs:
-      root: true
-    parameters:
-      backheight:
-        parameter: backheight
-      depth:
-        parameter: depth
-      gapheight:
-        parameter: gapheight
-      recline:
-        parameter: recline
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
diff --git a/rocolib/library/SimpleTable.yaml b/rocolib/library/SimpleTable.yaml
deleted file mode 100644
index 5a77e45e77f45fded0bcd175645f67567f650d48..0000000000000000000000000000000000000000
--- a/rocolib/library/SimpleTable.yaml
+++ /dev/null
@@ -1,245 +0,0 @@
-connections:
-  connection0:
-  - - top
-    - l
-  - - legl
-    - topedge
-  - angle: 90
-  connection1:
-  - - top
-    - r
-  - - legr
-    - topedge
-  - angle: 90
-  connection2:
-  - - top
-    - t
-  - - legt
-    - topedge
-  - angle: 90
-  connection3:
-  - - top
-    - b
-  - - legb
-    - topedge
-  - angle: 90
-  connection4:
-  - - legl
-    - rightedge
-  - - legb
-    - leftedge
-  - angle: 90
-  connection5:
-  - - legb
-    - rightedge
-  - - legr
-    - leftedge
-  - angle: 90
-  connection6:
-  - - legr
-    - rightedge
-  - - legt
-    - leftedge
-  - angle: 90
-  connection7:
-  - - legl
-    - leftedge
-  - - legt
-    - rightedge
-  - angle: 90
-interfaces: {}
-parameters:
-  _dx:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _dy:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _dz:
-    defaultValue: 0
-    spec:
-      hidden: true
-      minValue: null
-      units: mm
-      valueType: (float, int)
-  _q_a:
-    defaultValue: 1
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_i:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_j:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  _q_k:
-    defaultValue: 0
-    spec:
-      hidden: true
-      maxValue: 1
-      minValue: -1
-      valueType: (int, float)
-  height:
-    defaultValue: 40
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  length:
-    defaultValue: 70
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  taper:
-    defaultValue: 0.5
-    spec:
-      maxValue: 1
-      minValue: 0
-      valueType: (float, int)
-  thickness:
-    defaultValue: 10
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-  width:
-    defaultValue: 50
-    spec:
-      minValue: 0
-      units: mm
-      valueType: (float, int)
-source: ../builders/SimpleTableBuilder.py
-subcomponents:
-  legb:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: length
-  legl:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
-  legr:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: width
-  legt:
-    classname: VLeg
-    kwargs: {}
-    parameters:
-      _dx:
-        parameter: _dx
-      _dy:
-        parameter: _dy
-      _dz:
-        parameter: _dz
-      _q_a:
-        parameter: _q_a
-      _q_i:
-        parameter: _q_i
-      _q_j:
-        parameter: _q_j
-      _q_k:
-        parameter: _q_k
-      height:
-        parameter: height
-      taper:
-        parameter: taper
-      thickness:
-        parameter: thickness
-      width:
-        parameter: length
-  top:
-    classname: Rectangle
-    kwargs:
-      root: true
-    parameters:
-      l:
-        parameter: length
-      w:
-        parameter: width
diff --git a/rocolib/library/Stool.py b/rocolib/library/Stool.py
deleted file mode 100644
index 8c36db6363edd2fdd15ee654141bedeeca35c399..0000000000000000000000000000000000000000
--- a/rocolib/library/Stool.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from rocolib.api.components import FoldedComponent
-from rocolib.api.composables.graph.Face import Face, Rectangle, RegularNGon
-from rocolib.api.composables.graph.Face import RegularNGon2 as r2l
-from rocolib.utils.numsym import sin, deg2rad
-
-
-class Stool(FoldedComponent):
-    def define(self):
-        self.addParameter("height", 60, paramType="length", minValue=10)
-        self.addParameter("legs", 3, paramType="count", minValue=1)
-        self.addParameter("radius", minValue=10, overrides={
-            "legwidth": 'c.getParameter("radius") * 2 * np.sin( np.pi() / c.getParameter("legs") / 2)',
-        })
-        self.addParameter("legwidth", 20, paramType="length", minValue=9)
-        self.addParameter("angle", 80, paramType="angle")
-
-    def assemble(self):
-        h =  self.p.height
-        lp = self.p.legs
-        e =  self.p.legwidth
-        ap = self.p.angle
-
-        n = lp * 2
-        self.addFace(RegularNGon("", n, e), "seat")
-
-        for i in range(0, int(n/2)):
-            s = Rectangle("", e, h)
-            self.attachEdge("seat.e%d" % (2*i), s, "e0", "leg%d" % i, angle=ap)
-
-if __name__ == "__main__":
-    from rocolib.api.composables.graph.Joint import FingerJoint
-    Stool.test(thickness=4, joint=FingerJoint(thickness=4))
-
diff --git a/rocolib/library/ThruHole.py b/rocolib/library/ThruHole.py
index b61671480639d9731fe14a335c8ff7e73eee81e2..faccc436606f0a1fd463ceb2d89d44fe95aa4033 100644
--- a/rocolib/library/ThruHole.py
+++ b/rocolib/library/ThruHole.py
@@ -12,13 +12,13 @@ class ThruHole(DecorationComponent):
         self.addParameter("diameter", 1, paramType="length")
 
     def assemble(self):
-        diam = self.getParameter("diameter")/2.
-        nr = self.getParameter("nrows")
-        nc = self.getParameter("ncols")
+        diam = self.p.diameter/2.
+        nr = self.p.nrows
+        nc = self.p.ncols
 
         def hole(i, j, d):
-            dx = (j - (nc-1)/2.)*self.getParameter("colsep")
-            dy = (i - (nr-1)/2.)*self.getParameter("rowsep")
+            dx = (j - (nc-1)/2.)*self.p.colsep
+            dy = (i - (nr-1)/2.)*self.p.rowsep
             return Face("r-%d-%d" % (i,j),
                           ((dx-d, dy-d), (dx+d, dy-d), (dx+d, dy+d), (dx-d, dy+d)),
                           recenter=False)
diff --git a/rocolib/library/VLeg.py b/rocolib/library/VLeg.py
deleted file mode 100644
index ff37fdd2d37d7e7d5dc6fbfc4d7e9639ebfd0b06..0000000000000000000000000000000000000000
--- a/rocolib/library/VLeg.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from rocolib.api.components import FoldedComponent
-from rocolib.api.composables.graph.Face import Face
-
-
-class VLeg(FoldedComponent):
-    def define(self):
-        self.addParameter("height", 40, paramType="length")
-        self.addParameter("width", 50, paramType="length")
-        self.addParameter("thickness", 10, paramType="length")
-        self.addParameter("taper", 0.5, paramType="ratio")
-
-        self.addEdgeInterface("leftedge", "leg.e0", "height")
-        self.addEdgeInterface("topedge", "leg.e1", "width")
-        self.addEdgeInterface("rightedge", "leg.e2", "height")
-
-    def assemble(self):
-        h = self.p.height
-        w = self.p.width
-        t = self.p.thickness
-        r = self.p.taper * t
-
-        s = Face("", ((h, 0), (h, w), (0, w), (0, w-r), (h-t, w-t), (h-t, t), (0, r), (0,0)))
-        self.addFace(s, "leg")
-
-if __name__ == "__main__":
-    VLeg.test()
-
diff --git a/rocolib/library/__init__.py b/rocolib/library/__init__.py
index 309c68313d001fa8619d78b8339232902b93ae36..273604e13175eeb81d31fd6f7e1512ea4ff236dc 100644
--- a/rocolib/library/__init__.py
+++ b/rocolib/library/__init__.py
@@ -1,51 +1,116 @@
 import networkx as nx
-from os import system
+import os
 from os.path import dirname, realpath, basename, join
 from glob import glob
 import importlib
 import logging
-
-from rocolib import rocopath
-from rocolib.utils.io import load_yaml
-from rocolib.api.components import newComponent
+from yaml import safe_load
+import sys
+if sys.version_info[:2] >= (3, 10):
+    # pylint: disable=no-name-in-module
+    from importlib.metadata import entry_points
+else:
+    from importlib_metadata import entry_points
+from platformdirs import user_data_dir
+from collections import OrderedDict
 
 
 log = logging.getLogger(__name__)
 ROCOLIB_LIBRARY = dirname(realpath(__file__))
-
-pyComponents = [ basename(f)[:-3] for f in glob(ROCOLIB_LIBRARY + "/[!_]*.py")]
-yamlComponents = [ basename(f)[:-5] for f in glob(ROCOLIB_LIBRARY + "/*.yaml")]
-allComponents = set(pyComponents + yamlComponents)
-
-def getSubcomponents(c):
+USER_LIBRARY = user_data_dir("rocolib", "rocolib")
+
+def _makeLibDirs():
+    dirs = OrderedDict(user = USER_LIBRARY)
+    mods = OrderedDict()
+    libs = entry_points(group="rocolib.libraries")
+    for lib in libs.names:
+        libmodule = libs[lib]
+        dirs[lib] = libmodule.load().ROCOLIB_LIBRARY
+        mods[lib] = libmodule.value
+    dirs["rocolib"] = ROCOLIB_LIBRARY
+    mods["rocolib"] = "rocolib.library"
+    return dirs, mods
+
+libDirs, libModules = _makeLibDirs()
+
+def _makeLibItems(ext):
+    data = {}
+    for k, d in libDirs.items():
+        files = [ basename(f)[:-len(ext)-1] for f in glob(d + f"/[!_]*.{ext}")]
+        for f in files:
+            fullname = f"{k}:{f}"
+            # XXX TODO: what if a pyComponent in one library is a yamlComponent in another or vice versa?
+            if f not in data:
+                data[f] = fullname
+            else:
+                data[fullname] = fullname
+    return data
+
+yamlComponents = _makeLibItems("yaml")
+pyComponents = _makeLibItems("py")
+
+allComponents = {**pyComponents, **yamlComponents}
+
+def _load_yaml(c):
+    with open(getYamlFile(c), 'r') as fd:
+        return safe_load(fd)
+
+def _getSubcomponents(c):
     try:
-        return set((x['classname'] for x in load_yaml(c).get('subcomponents', dict()).values()))
-    except FileNotFoundError:
+        return set((x['classname'] for x in _load_yaml(c).get('subcomponents', dict()).values()))
+    except (ValueError, FileNotFoundError):
         return set()
 
-def getComponentNx():
+def _getComponentNx():
     net = nx.DiGraph()
-    net.add_nodes_from(allComponents)
+    net.add_nodes_from(allComponents.keys())
     for c in allComponents:
-        for sc in getSubcomponents(c):
-            net.add_edge(sc, c)
+        for sc in _getSubcomponents(c):
+            if sc in allComponents:
+                net.add_edge(sc, c)
+            else:
+                for k, v in allComponents.items():
+                    if sc == v:
+                        net.add_edge(k, c)
+                        break
+                else:
+                    log.warning(f"Subcomponent {sc} of component {c} not found, ignoring")
     return net
 
 def getComponentTree():
-    return nx.topological_generations(getComponentNx())
+    return nx.topological_generations(_getComponentNx())
+
+def getYamlFile(c):
+    if c in yamlComponents:
+        c = yamlComponents[c]
+    elif ":" not in c:
+        raise ValueError(f"Cannot find appropriate yaml file for {c}")
+    d, c = c.split(":")
+    return f"{libDirs[d]}/{c}.yaml"
+
+def getPyComponent(c):
+    if c in pyComponents:
+        c = pyComponents[c]
+    elif ":" not in c:
+        raise ValueError(f"Cannot find appropriate python file / plugin library for {c}")
+    d, c = c.split(":")
+
+    obj = getattr(importlib.import_module(f"{libModules[d]}.{c}"), c)
+    return obj()
 
 def getComponent(c, **kwargs):
     '''
     Here we are doing Dynamic instantiation from string name of a class in dynamically imported module
     Parameter c (str): component name e.g. 'Stool'
+    kwargs: component parameter / value pairs (possibly including name)
     '''
-    if c in pyComponents:
-        # Load "module.submodule.MyClass"
-        obj = getattr(importlib.import_module("rocolib.library." + c), c)
-        # Instantiate the class (pass arguments to the constructor, if needed)
-        my_obj = obj()
-    elif c in yamlComponents:
-        my_obj = newComponent(c, f"{ROCOLIB_LIBRARY}/{c}.yaml")
+    from rocolib.api.components import newComponent
+    if c in pyComponents.keys() or c in pyComponents.values():
+        log.debug("Found python component in library, instantiating and returning object")
+        my_obj = getPyComponent(c)
+    elif c in yamlComponents.keys() or c in yamlComponents.values():
+        log.debug("Found yaml component in library, creating newComponent from yamlFile")
+        my_obj = newComponent(c, yamlFile=getYamlFile(c))
     else:
         raise ValueError(f"Component {c} not found in library")
 
@@ -59,6 +124,21 @@ def getComponent(c, **kwargs):
 
     return my_obj
 
+def save(c, lib=None):
+    if lib is True:
+        d = ROCOLIB_LIBRARY
+    elif lib:
+        d = libDirs[lib]
+    else:
+        d = USER_LIBRARY
+    os.makedirs(d, exist_ok=True)
+
+    # XXX TODO: Check for collisions?
+    # if collision:
+    #   flag to allow rebuilding, and fail otherwise?
+    #   if no flag, check if source matches and rebuild if so, fail otherwise?
+    return c.toYaml(d, c.cName + ".yaml", reldir=ROCOLIB_LIBRARY)
+
 def rebuild(built=None):
     if built is None:
         built = set()
@@ -77,7 +157,7 @@ def rebuildComponent(c, built=None, throw=True):
         log.debug(f"{c} has been rebuilt, skipping")
         return True
 
-    definition = load_yaml(c)
+    definition = _load_yaml(c)
     src = definition.get("source", None)
     success = True
     if src:
@@ -91,7 +171,7 @@ def rebuildComponent(c, built=None, throw=True):
 
         # XXX TODO: Test to make sure we don't call this script and then infinitely recurse!
         log.debug(f"Calling os.system: % python {ROCOLIB_LIBRARY}/{src}")
-        if system(f"python {ROCOLIB_LIBRARY}/{src}"):
+        if os.system(f"python {ROCOLIB_LIBRARY}/{src}"):
             success = False
 
         built.add(c)
@@ -100,15 +180,3 @@ def rebuildComponent(c, built=None, throw=True):
     if throw:
         assert success, f"Error rebuilding {c}, check stdout/stderr for details"
     return success
-
-def getComponentPaths(c):
-    paths = {}
-    if c in pyComponents:
-        paths["python"] = rocopath(join(ROCOLIB_LIBRARY, f"{c}.py"))
-    if c in yamlComponents:
-        paths["yaml"] = rocopath(join(ROCOLIB_LIBRARY, f"{c}.yaml"))
-        definition = load_yaml(c)
-        src = definition.get("source", None)
-        if src:
-            paths["builder"] = rocopath(join(ROCOLIB_LIBRARY, src))
-    return paths
diff --git a/rocolib/test/test_library.py b/rocolib/test/test_library.py
index 61b6379da60768fd5ff1237d8247e932566c08f0..7ef66f0430550e653a331f8caaa012c19d467028 100644
--- a/rocolib/test/test_library.py
+++ b/rocolib/test/test_library.py
@@ -1,6 +1,6 @@
 import pytest
 import logging
-from rocolib.library import getComponent, pyComponents, yamlComponents, allComponents, rebuild, rebuildComponent
+from rocolib.library import getComponent, pyComponents, yamlComponents, rebuild, rebuildComponent
 
 
 def test_rebuild(c = None):
@@ -9,13 +9,13 @@ def test_rebuild(c = None):
     else:
         rebuild()
 
-@pytest.mark.parametrize("component",pyComponents)
+@pytest.mark.parametrize("component",pyComponents.keys())
 def test_component_python(component):
     getComponent(component).test()
 
-@pytest.mark.parametrize("component",yamlComponents)
+@pytest.mark.parametrize("component",yamlComponents.keys())
 def test_component_yaml(component):
-    getComponent(component).makeOutput()
+    getComponent(component).test()
 
 if __name__ == "__main__":
     logging.basicConfig(level=logging.INFO)
diff --git a/rocolib/utils/io.py b/rocolib/utils/io.py
deleted file mode 100644
index 1290898f51993670fb77e567c7c434b7516762a9..0000000000000000000000000000000000000000
--- a/rocolib/utils/io.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from os.path import join
-
-from yaml import safe_load
-
-from rocolib import ROCOLIB_DIR
-
-def load_yaml(file_name):
-    if file_name[-5:] != '.yaml':
-        file_name += '.yaml'
-
-    fqn = join(ROCOLIB_DIR, 'library', file_name)
-    with open(fqn, 'r') as fd:
-        return safe_load(fd)
-