diff --git a/requirements.txt b/requirements.txt
index aeaf1bc730c7ae583f7940b1bb2a73a4d230ab94..b910cf9c25955fb5a7fff9ae0d45db4fd9f5bd25 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,5 +12,6 @@ kaleido
 circlify
 shapely
 dash
+dash-bootstrap-components
 platformdirs
 importlib_metadata; python_version < '3.10'
diff --git a/rocolib/__main__.py b/rocolib/__main__.py
index 212d1c8f1d168d056906b7dbe659f1c1c2215b34..279ade6a7976f0990b33429d878183d4d79bc23b 100644
--- a/rocolib/__main__.py
+++ b/rocolib/__main__.py
@@ -8,11 +8,12 @@ from os.path import basename
 from pprint import pprint
 from textwrap import shorten
 from dash import Dash
+import dash_bootstrap_components as dbc
 from rocolib.library import getComponent, getComponentTree, libDirs
 from rocolib.api.composables.graph.Joint import FingerJoint
 
 
-def test(component, params, thickness, outdir=None, display=False, outputs=None):
+def test(component, params, thickness, outdir=None, display=False, outputs=None, kwargs=None):
     f = getComponent(component)
     if params is not None:
         for p in params:
@@ -37,7 +38,13 @@ def test(component, params, thickness, outdir=None, display=False, outputs=None)
     else:
         filedir = None
         outputs = outputs or ()
-    ret = f.makeOutput(outputs = outputs, filedir=filedir, thickness=t, joint=j, remake=False)
+
+    if kwargs is None:
+        kwargs = {}
+    else:
+        kwargs = dict(kwargs)
+
+    ret = f.makeOutput(outputs = outputs, filedir=filedir, thickness=t, joint=j, remake=False, **kwargs)
 
     for composable, outs in ret.items():
         if outs:
@@ -45,8 +52,8 @@ def test(component, params, thickness, outdir=None, display=False, outputs=None)
             pprint({k: shorten(str(v), 60) for k, v in outs.items()})
 
     if display:
-        app = Dash(__name__)
-        html = f.visualize(outputs = True, thickness=t, joint=j, remake=False)
+        app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
+        html = f.visualize(outputs = True, thickness=t, joint=j, remake=False, app=app, **kwargs)
         app.layout = html
         app.run_server(debug=True)
 
@@ -87,6 +94,9 @@ def cli_argparse():
     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")
+    parser.add_argument("-k", nargs=2, action='append',
+        metavar=("NAME", "VALUE"),
+        help="Set makeOutput kwarg NAME to value VALUE")
 
     if len(sys.argv)==1:
         parser.print_help(sys.stderr)
@@ -164,7 +174,7 @@ def cli_argparse():
         ))(c.listOutputs()), f)
 
         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)
+            test(args.component, args.p, thickness=args.t, outdir=outdir, display=args.d, outputs=args.s, kwargs=args.k)
         acted = True
 
     if not acted:
diff --git a/rocolib/api/Parameterized.py b/rocolib/api/Parameterized.py
index 18f0ad2bb69fba74d5c0a02882e030eef92621d6..37d595b18e21c655ba5e2fa19322c35851f23bbe 100644
--- a/rocolib/api/Parameterized.py
+++ b/rocolib/api/Parameterized.py
@@ -11,18 +11,18 @@ VALID_ATTR = re.compile('^[a-zA-Z_][a-zA-Z0-9_.]*$')
 
 PARAM_TYPES = {
     "length": {
-        "valueType": "(float, int)",
+        "valueType": "float",
         "minValue": 0,
         "units": "mm",
     },
     "angle": {
-        "valueType": "(float, int)",
+        "valueType": "float",
         "minValue": 0,
         "maxValue": 360,
         "units": "degrees",
     },
     "ratio": {
-        "valueType": "(float, int)",
+        "valueType": "float",
         "minValue": 0,
         "maxValue": 1,
     },
@@ -76,6 +76,11 @@ class Parameter:
         self.value = None
 
     def resolveValue(self, value):
+        if value is None:
+            return None
+        vt = self.spec.get("valueType")
+        if vt:
+            value = eval(vt)(value)
         if "values" not in self.spec:
             return value
         sanitize = lambda x : yaml.safe_load(yaml.safe_dump(x))
@@ -114,7 +119,10 @@ class Parameter:
             if (self.spec.get(spec, None) is not None and not test(self.spec[spec])):
                 raise ValueError(f"When setting parameter {self.name}: {value} {error} {self.spec[spec]}")
 
-        check("valueType", lambda x : isinstance(value, eval(x)), "is not of type")
+        check("valueType", lambda x : eval(x)(value) is not None, "is not of type")
+        vt = self.spec.get("valueType")
+        if vt:
+            value = eval(vt)(value)
         check("minValue", lambda x : value >= x, "is less than")
         check("maxValue", lambda x : value <= x, "is greater than")
         check("values", lambda x : value in x, "is not in")
@@ -126,7 +134,7 @@ class Parameter:
 
     def getValue(self):
         if self.value is not None:
-            if "values" in self.spec:
+            if "values" in self.spec and isinstance(self.spec["values"], dict):
                 return self.spec["values"][self.value]
             else:
                 return self.value
@@ -270,4 +278,4 @@ class Parameterized(object):
             self._parameters.pop(name)
             delattr(self.p.__class__, name)
         else:
-            raise KeyError("Parameter %s not does not exist on object %s" % (n, str(self)))
+            raise KeyError("Parameter %s not does not exist on object %s" % (name, str(self)))
diff --git a/rocolib/api/components/Component.py b/rocolib/api/components/Component.py
index 4b34101f310163c3c3ac7c807ecd4b3d5c74eb95..3ec4c9011142ae3a32428a93f1781e0af5cb4dc0 100644
--- a/rocolib/api/components/Component.py
+++ b/rocolib/api/components/Component.py
@@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
 
 def newComponent(cName, name=None, yamlFile=None):
     class _C(Component):
-        def __init__(self):
+        def __init__(self, name=name, yamlFile=yamlFile):
             Component.__init__(self, name, yamlFile)
             self.cName = cName
         def define(self): pass
@@ -39,6 +39,7 @@ class Component(Parameterized):
     def __init__(self, name=None, yamlFile=None):
         Parameterized.__init__(self, name)
         self.cName = self.__class__.__name__
+        self.yamlFile = yamlFile
         self.reset()
 
         if not yamlFile:
@@ -60,6 +61,13 @@ class Component(Parameterized):
         self.predefine()
         self.define()
 
+    def reload(self, params=None):
+        self.__init__(name=self.name, yamlFile=self.yamlFile)
+        if not params:
+            return
+        for k, v in params.items():
+            self.setParameter(k, v)
+
     def toLibrary(self, lib=None):
         save(self, lib)
 
@@ -402,13 +410,15 @@ class Component(Parameterized):
 
                 for (key, composable) in self.composables.items():
                     composable.attach(fromPort, toPort, **kwargs)
-            except:
+            except Exception as e:
                 logstr  =  "Error in attach: \n"
                 logstr += f"  from {from_interface}: "
                 logstr += fromPort.toString()
                 logstr +=  "\n"
                 logstr += f"  to {to_interface}: "
                 logstr += toPort.toString()
+                logstr +=  "\n"
+                logstr += str(e)
                 log.error(logstr)
                 raise
 
@@ -512,6 +522,7 @@ class Component(Parameterized):
 
     def visualize(self, outputs=True, **ka):
         widgets = self.makeOutput(outputs, widgets=True, **ka)
+
         elts = [ html.H1(f"RoCo component visualizer: {self.name}") ]
         tabs = []
 
@@ -521,13 +532,15 @@ class Component(Parameterized):
                 widgets.append(html.Div([
                     html.H3(v['desc']),
                     html.P(f"Widget: {k}"),
-                    v['widget'],
+                    html.Div(v['widget'], id = dict(composable=c, name=k, type='widget')),
                 ], style={'padding': 10, 'flex': 1}))
-            tabs.append(dcc.Tab(label = f"Composable: {c}",
+            #tabs.append(dcc.Tab(label = f"Composable: {c}",
+            tabs.append(html.Div(
                                 children = html.Div(widgets, style={'display': 'flex', 'flex-direction': 'row'})
             ))
 
-        elts.append(html.Div(dcc.Tabs(tabs)))
+        #elts.append(html.Div(dcc.Tabs(tabs)))
+        elts += reversed(tabs)
         return html.Div(elts)
 
     def listOutputs(self):
diff --git a/rocolib/api/components/MechanicalComponent.py b/rocolib/api/components/MechanicalComponent.py
index a9f73f81087146d9ecb47493179faa661ceb3b1c..72435dfe926c0025ef11355125e500a2dd23c81b 100644
--- a/rocolib/api/components/MechanicalComponent.py
+++ b/rocolib/api/components/MechanicalComponent.py
@@ -25,7 +25,7 @@ class MechanicalComponent(Component):
         else:
             self._euler = None
             self._quat = [ self.addParameter("_q_"+x, int(x == "a"),
-                                valueType="(int, float)", minValue=-1, maxValue=1, hidden=True)
+                                valueType="float", minValue=-1, maxValue=1, hidden=True)
                 for x in "aijk"
             ]
             #self.addSemanticConstraint(np.Eq(np.norm(self._quat), 1))
diff --git a/rocolib/api/composables/ComponentComposable.py b/rocolib/api/composables/ComponentComposable.py
index 45df43b159ba0b335ca55f88fa25972fb230b224..985318573818442a53a4c848413793e0ef944f07 100644
--- a/rocolib/api/composables/ComponentComposable.py
+++ b/rocolib/api/composables/ComponentComposable.py
@@ -1,8 +1,13 @@
 from rocolib.api.composables.Composable import Composable, output, widget
 import networkx as nx
+import json
 from circlify import circlify
 from rocolib.utils.nx2go import GraphVisualization as gv
-from dash import dcc
+from dash import dcc, html, Input, Output, ctx, ALL
+from dash.exceptions import PreventUpdate
+import dash_bootstrap_components as dbc
+import logging
+from pprint import pformat
 
 
 class ComponentComposable(Composable):
@@ -72,3 +77,166 @@ class ComponentComposable(Composable):
     @widget('displaymap', "component topology map")
     def displayMap(self, fp, **ka):
         return dcc.Graph(figure = self._drawComponentTree())
+
+    @widget('parameters', "component parameter form")
+    def paramForm(self, fp, **ka):
+        inputs = []
+
+        inputs.append(html.H4("Interfaces"))
+        inputrow = []
+        for i, (name, info) in enumerate(self.c.getInterfaceInfo().items()):
+            port=info.get("port", "")
+
+            disabled = False
+            if port=="EdgePort":
+                color = 'primary'
+            elif port=="FacePort":
+                color = 'info'
+            else:
+                disabled = True
+                color = 'secondary'
+
+            elem = dbc.Button(name, n_clicks=0,
+                id=dict(name=name, type='interface', port=port),
+                color = color,
+                disabled = disabled,
+            )
+            inputrow.append( dbc.Col ( html.Div(elem, className="d-grid gap-2") , width=2) )
+            if i % 6 == 5:
+                inputs.append(dbc.Row(inputrow))
+                inputrow = []
+        if inputrow:
+            inputs.append(dbc.Row(inputrow))
+
+        inputs.append(html.H4("Parameters"))
+        for name, info in self.c.getParameterInfo().items():
+
+            val = info.get("value", info.get("defaultValue", None))
+            if val is None:
+                val = ''
+
+            inputrow = [ dbc.Col( html.Div(name), width=3 ) , dbc.Col( html.Div(json.dumps(val)), width=1 ) ]
+
+            spec = info.get("spec", {})
+            elem = dbc.Input
+            idict = dict(
+                id = dict(name=name, type='parameter', valueType=("values" in spec and 'values' or spec.get("valueType", ''))),
+                type = 'text',
+                value = val,
+                debounce = True,
+            )
+
+            minval = maxval = None
+            if "minValue" in spec and spec["minValue"] is not None:
+                minval = idict["min"] = spec.get("minValue")
+                minval = round(minval, 2)
+            if "maxValue" in spec and spec["maxValue"] is not None:
+                maxval = idict["max"] = spec.get("maxValue")
+                minval = round(maxval, 2)
+
+            if spec.get("values"):
+                elem = dbc.Select
+                idict.pop("type")
+                idict.pop("debounce")
+                idict["options"] = [dict(label=x, value=json.dumps(x)) for x in spec.get("values")]
+                idict["value"] = json.dumps(val)
+            elif spec.get("valueType") == "int":
+                idict["type"] = 'number'
+            elif spec.get("valueType") == "float":
+                idict["type"] = 'range'
+                if "max" not in idict:
+                    mx = int(max(val * 2, idict.get("min", 0) + 10))
+                    idict["max"] = mx
+                    maxval = f"{mx} >"
+                if "min" not in idict:
+                    idict["min"] = int(min(val - 10, 0))
+                    minval = f"< {idict['min']}"
+                delta = idict['max'] - idict['min']
+                idict["step"] = 0.1
+                if delta > 5:
+                    idict["step"] = 1
+                elif delta > 20:
+                    idict["step"] = 2
+                elif delta > 100:
+                    idict["step"] = 5
+            elif spec.get("valueType") == "bool":
+                elem = dbc.Switch
+                idict.pop("type")
+                idict.pop("debounce")
+                idict["label"] = name
+                idict["value"] = val
+            else:
+                logging.info(f"Unknown valueType: {spec.get('valueType')} for parameter {name}, using default text input")
+                idict["value"] = json.dumps(val)
+
+            inputgroup = []
+
+            if minval is not None:
+                inputgroup.append( dbc.InputGroupText(minval) )
+
+            e = elem(**idict)
+            inputgroup.append( e )
+
+            if maxval is not None:
+                inputgroup.append( dbc.InputGroupText(maxval) )
+
+            inputrow.append( dbc.Col(dbc.InputGroup(inputgroup)) )
+            inputs.append(dbc.Row(inputrow))
+
+        inputs.append(html.Div("Hello world", id="output_params"))
+
+        app = ka.pop("app", None)
+        if app:
+            @app.callback(Output(dict(composable=ALL, name=ALL, type="widget"), "children"),
+                          Input(dict(name=ALL, type="parameter", valueType=ALL), "value"),
+                          Input(dict(name=ALL, type="interface", port=ALL), "n_clicks"))
+            def updateParams(values, btns):
+                if not ctx.triggered_id:
+                    raise PreventUpdate
+                tname = ctx.triggered_id["name"]
+                ttype = ctx.triggered_id["type"]
+
+                outputs = [ d["id"]["name"] for d in ctx.outputs_list ]
+                if ttype == "parameter":
+                    params = _extract(ctx.inputs_list[0])
+                    self.c.reload(params)
+                    ka.pop("remake", None)
+                elif ttype == "interface":
+                    ka["remake"] = False
+                    iface = self.c.getInterface(tname)
+                    if ctx.triggered_id["port"] == "EdgePort":
+                        ka["highlight"] = iface.edges
+                    elif ctx.triggered_id["port"] == "FacePort":
+                        ka["highlight"] = [e.name for e in iface.getFace().edges]
+                    else:
+                        raise PreventUpdate
+                widgets = self.c.makeOutput(outputs, widgets=True, **ka)
+                return [widgets[d["id"]["composable"]][d["id"]["name"]]['widget'] for d in ctx.outputs_list]
+
+            @app.callback(Output('output_params', 'children'),
+                Input(dict(name=ALL, type="parameter", valueType=ALL), "value"))
+            def updateParams(values):
+                if not ctx.triggered_id:
+                    s = f"Current parameters set to:"
+                else:
+                    s = f"Parameter changed: {ctx.triggered_id}; rebuilding with new parameters:"
+                params = _extract(ctx.inputs_list[0])
+                return html.Div([html.Div(s), html.Code(pformat(params))])
+
+        return html.Div(dbc.Container(inputs))
+
+def _extract(ctxlist):
+    params = {}
+    for i in ctxlist:
+        val = i['value']
+        if val == '':
+            val = None
+        elif i['id']['valueType'] not in ('int', 'float', 'bool'):
+            try:
+                val = json.loads(val)
+            except (TypeError, json.decoder.JSONDecodeError):
+                logging.debug(f"Error json-parsing param {i['id']['name']} = {val} ({type(val)})")
+                logging.debug(f"... leaving as string")
+                pass
+        params[i['id']['name']] = val
+    return params
diff --git a/rocolib/api/composables/GraphComposable.py b/rocolib/api/composables/GraphComposable.py
index 6dbd00b2a1a97f21b96b0cfbe4a0442278508141..50d248c614620ff518c795279f1e5307b383521e 100644
--- a/rocolib/api/composables/GraphComposable.py
+++ b/rocolib/api/composables/GraphComposable.py
@@ -78,5 +78,5 @@ class GraphComposable(Composable, BaseGraph):
 
     @widget('display2D', "2D cut pattern")
     def display2D(self, fp, **ka):
-        kwargs = {k:v for k,v in ka.items() if k in "scale axes".split()}
+        kwargs = {k:v for k,v in ka.items() if k in "scale axes highlight".split()}
         return dcc.Graph(figure = cutfig(self.getEdgeSet(), **kwargs))
diff --git a/rocolib/api/composables/graph/Drawing.py b/rocolib/api/composables/graph/Drawing.py
index 33e7d421700e31b52873b344e50624222d271ca7..b09a85f763d626ddeb4a21c691637563c6a28682 100644
--- a/rocolib/api/composables/graph/Drawing.py
+++ b/rocolib/api/composables/graph/Drawing.py
@@ -24,13 +24,11 @@ class Drawing:
     buffer = 10
 
     for flist in g.facelists:
-        hyperedges = []
-        for face in flist:
-            for hyperedge in face.edges: 
-                if hyperedge not in hyperedges:
-                    hyperedges.append(hyperedge)
+        hyperedges = set((e for face in flist for e in face.edges if e.pts2D is not None and e.length > 0))
 
         pts = [p for e in hyperedges for p in e.pts2D]
+        if not pts:
+            continue
         minx = min([x[0] for x in pts])
         miny = min([x[1] for x in pts])
         dx = maxx - minx 
@@ -63,6 +61,8 @@ class Drawing:
                 self.edges[e.name] = Edge(e.name, e.pts2D[0] + [dx, dy], e.pts2D[1] + [dx, dy], edge)
                 pts.extend(list(e.pts2D))
         for face in flist:
+            if face.area == 0:
+                continue
             for e in face.get2DDecorations():
                 self.edges[e[0]] = Edge(e[0], e[1] + [dx, dy], e[2] + [dx, dy], EdgeType(e[3], interior=True))
 
diff --git a/rocolib/api/composables/graph/DrawingEdge.py b/rocolib/api/composables/graph/DrawingEdge.py
index 95bf59323de5ae9a81aada6dd94c0c6c4f843967..ce415a8d9dec06c8953df0a54de29941cff5ff53 100644
--- a/rocolib/api/composables/graph/DrawingEdge.py
+++ b/rocolib/api/composables/graph/DrawingEdge.py
@@ -8,9 +8,9 @@ class EdgeType:
 
   TYPEDATA = ( 
     ( "REGISTRATION", False, True , "#00ff00", "#00ff00"             , 6      , "reg"),
-    ( "BOUNDARY_CUT", False, True , "#ff0000", "#000000"             , 5      , "cut"),
-    ( "INTERIOR_CUT", False, True , "#ff0000", "#888888"             , 5      , "cut"),
-    ( "FOLD"        , True , True , "#0000ff", ("#0000ff", "#ff0000"), (1, 3) , "xxx"),
+    ( "BOUNDARY_CUT", False, True , "#0000ff", "#000000"             , 5      , "cut"),
+    ( "INTERIOR_CUT", False, True , "#0000ff", "#888888"             , 5      , "cut"),
+    ( "FOLD"        , True , True , "#ff0000", ("#0000ff", "#ff0000"), (1, 3) , "xxx"),
     ( "FLEX"        , True , False, "#00ffff", ("#00ffff", "#ffff00"), (1, 3) , "xxx"),
     ( "FLAT"        , False, False, "#ffff00", "#ffffff"             , 3      , "nan"),
   )
diff --git a/rocolib/api/composables/graph/Graph.py b/rocolib/api/composables/graph/Graph.py
index 3cf2190b965b061040086b39b11de18da05b6aa0..3bf2b327072324126288f9973018e5cf5ac4db37 100644
--- a/rocolib/api/composables/graph/Graph.py
+++ b/rocolib/api/composables/graph/Graph.py
@@ -343,10 +343,10 @@ class Graph():
         buffer = 10
 
         for flist in self.facelists:
-            edges = set((e for face in flist for e in face.edges if e.pts2D is not None))
+            edges = set((e for face in flist for e in face.edges if e.pts2D is not None and e.length > 0))
 
             if not edges:
-                log.warning(f"No placed edges in flist, moving on.")
+                log.info(f"No placed edges in flist; all edges have length=0.  Moving on.")
                 continue
 
             pts = [p for e in edges for p in e.pts2D]
@@ -377,6 +377,10 @@ class Graph():
                 edgeset[e.name] = edge
 
             for face in flist:
+                if face.area == 0:
+                    if face.get2DDecorations():
+                        log.info(f"Skipping decorations on face with area=0")
+                    continue
                 for e in face.get2DDecorations():
                     edge = dict(p0 = e[1] + [dx, dy],
                                 p1 = e[2] + [dx, dy],
diff --git a/rocolib/api/ports/EdgePort.py b/rocolib/api/ports/EdgePort.py
index 7d07f05d9e2a28bb77f554d06cc81bc6e0f28e43..86336a8cca9cfeac1c828f92830b2bc6bfeaaf56 100644
--- a/rocolib/api/ports/EdgePort.py
+++ b/rocolib/api/ports/EdgePort.py
@@ -3,23 +3,21 @@ from rocolib.utils.utils import prefix as prefixString
 
 
 class EdgePort(Port):
-    def __init__(self, parent, graph, edges, lengths):
+    def __init__(self, parent, graph, edges, params):
         Port.__init__(self, parent, {})
         if isinstance(edges, str) or not hasattr(edges, "__iter__"):
             edges = [edges]
-        if isinstance(lengths, str) or not hasattr(lengths, "__iter__"):
-            lengths = [lengths]
-        if len(edges) != len(lengths):
-            raise ValueError("Need one-to-one mapping of edges to lengths")
+        if isinstance(params, str) or not hasattr(params, "__iter__"):
+            params = [params]
         self.graph = graph
         self.edges = edges
-        self.lengths = lengths
+        self.params = params
 
     def getEdges(self):
         return self.edges
 
     def getParams(self):
-        return self.lengths
+        return self.params
 
     def update(self, newparent, oldcomposable, newcomposable, prefix):
         self.setParent(newparent)
diff --git a/rocolib/builders/AngleBuilder.py b/rocolib/builders/AngleBuilder.py
new file mode 100644
index 0000000000000000000000000000000000000000..885ccc1792b469419e865a719079c6213de25740
--- /dev/null
+++ b/rocolib/builders/AngleBuilder.py
@@ -0,0 +1,15 @@
+from rocolib.api.components.Component import newComponent
+from rocolib.api.Function import Function
+
+c = newComponent("Angle")
+
+c.addParameter("h", 20, paramType="length")
+c.addParameter("angle", 90, paramType="angle")
+
+c.addSubcomponent("base", "Rectangle", inherit=True, prefix=None)
+c.addSubcomponent("edge", "Rectangle")
+c.addConstraint(("edge", "w"), "h")
+
+c.join(("base", "b"), ("edge", "t"), angle=Function("angle"))
+
+c.toLibrary(True)
diff --git a/rocolib/builders/MountedBrainsBuilder.py b/rocolib/builders/MountedBrainsBuilder.py
index b6d35135f3793eecad3247f5776f4b09f3f4c91c..c7e1bc0547bbdd32159c26ff3db279becb90a2f0 100644
--- a/rocolib/builders/MountedBrainsBuilder.py
+++ b/rocolib/builders/MountedBrainsBuilder.py
@@ -4,17 +4,17 @@ from rocolib.api.Function import Function
 
 c = newComponent("MountedBrains")
 
-c.addSubcomponent("beam", "SimpleRectBeam")
+c.addSubcomponent("beam", "SimpleRectBeam", inherit=True, prefix=None)
 c.addSubcomponent("brain", "Brains", inherit="brain", prefix=None)
+c.delParameter("width")
+c.delParameter("depth")
 
-c.addParameter("length", 90, paramType="length")
 c.addParameter("width", 50, paramType="length")
 c.addParameter("depth", 20, paramType="length")
-c.addParameter("offset", (0,0))
+c.addParameter("offset", (0,0), valueType="list")
 
 c.addConstraint(("beam", "width"), "depth")
 c.addConstraint(("beam", "depth"), "width")
-c.addConstraint(("beam", "length"), "length")
 
 c.addConstraint(("beam", "width#minValue"), "brain", 'x.get("height")')
 c.addConstraint(("beam", "depth#minValue"), "brain", 'x.get("width")')
diff --git a/rocolib/builders/ServoMountBuilder.py b/rocolib/builders/ServoMountBuilder.py
index 0ccd12c96d51faf7e4a420169368969541360eb4..c5d1e09730c1975768187c23df78c10be76a917e 100644
--- a/rocolib/builders/ServoMountBuilder.py
+++ b/rocolib/builders/ServoMountBuilder.py
@@ -10,10 +10,12 @@ c.addParameter("center", True, valueType="bool")
 c.addParameter("shift", 0, paramType="length")
 
 # XXX TODO: Define type: tuple of two numbers
-c.addParameter("offset", 0)
+c.addParameter("offset", [], valueType="list")
 
-c.addSubcomponent("beam", "SimpleRectBeam", inherit=("length", "addTabs"), prefix=None)
+c.addSubcomponent("beam", "SimpleRectBeam", inherit=True, prefix=None)
 c.addSubcomponent("mount", "Cutout")
+c.delParameter("width")
+c.delParameter("depth")
 
 c.addConstraint(("mount", "dx"), "servo", 'x.get("motorwidth") * 0.99')
 c.addConstraint(("mount", "dy"), "servo", 'x.get("motorlength")')
diff --git a/rocolib/library/Angle.yaml b/rocolib/library/Angle.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe772cc3d410437d262a5c6bc2a400b3575c3a7f
--- /dev/null
+++ b/rocolib/library/Angle.yaml
@@ -0,0 +1,117 @@
+connections:
+  connection0:
+  - - base
+    - b
+  - - edge
+    - t
+  - angle:
+      parameter: angle
+interfaces: {}
+parameters:
+  _dx:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dy:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dz:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _q_a:
+    defaultValue: 1
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_i:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_j:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_k:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  angle:
+    defaultValue: 90
+    spec:
+      maxValue: 360
+      minValue: 0
+      units: degrees
+      valueType: float
+  h:
+    defaultValue: 20
+    spec:
+      minValue: 0
+      units: mm
+      valueType: float
+  l:
+    defaultValue: 100
+    spec:
+      minValue: 0
+      units: mm
+      valueType: float
+  w:
+    defaultValue: 400
+    spec:
+      minValue: 0
+      units: mm
+      valueType: float
+source: ../builders/AngleBuilder.py
+subcomponents:
+  base:
+    classname: Rectangle
+    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
+      l:
+        parameter: l
+      w:
+        parameter: w
+  edge:
+    classname: Rectangle
+    kwargs: {}
+    parameters:
+      l:
+        parameter: l
+        subcomponent: base
+      w:
+        parameter: h
diff --git a/rocolib/library/Cutout.py b/rocolib/library/Cutout.py
index 5787e64e7a368de0f12567b87170997aa53c6ea4..c17d3a25c993045f15412125178050ada00f38ef 100644
--- a/rocolib/library/Cutout.py
+++ b/rocolib/library/Cutout.py
@@ -5,7 +5,7 @@ class Cutout(DecorationComponent):
     def define(self):
         self.addParameter("dx", 10, paramType="length")
         self.addParameter("dy", 20, paramType="length")
-        self.addParameter("d", overrides={
+        self.addParameter("d", paramType="length", overrides={
             "dx": 'c.p.d',
             "dy": 'c.p.d',
         })
diff --git a/rocolib/library/ESPSeg.yaml b/rocolib/library/ESPSeg.yaml
index 3743f2b77ff87fe054f7561ae3dcb429b8786f26..218b6edb44d15270ca76bc492bcc9188ecfe42c4 100644
--- a/rocolib/library/ESPSeg.yaml
+++ b/rocolib/library/ESPSeg.yaml
@@ -89,7 +89,7 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   brain:
     defaultValue: nodeMCU
     spec:
@@ -135,10 +135,11 @@ parameters:
     spec:
       values:
         ds2g:
-          horndepth: 1
+          horndepth: 2
           hornheight: 9
-          hornlength: 11
+          hornlength: 10
           hornoffset: 4
+          hornwidth: 3
           motorheight: 11
           motorlength: 17
           motorwidth: 8.5
@@ -148,6 +149,7 @@ parameters:
           hornheight: 11
           hornlength: 10
           hornoffset: 8
+          hornwidth: 5
           motorheight: 19
           motorlength: 23
           motorwidth: 13
@@ -157,6 +159,7 @@ parameters:
           hornheight: 14
           hornlength: 38
           hornoffset: 7
+          hornwidth: 3
           motorheight: 29
           motorlength: 31
           motorwidth: 17
@@ -166,6 +169,7 @@ parameters:
           hornheight: 10
           hornlength: 7
           hornoffset: 4
+          hornwidth: 3
           motorheight: 14
           motorlength: 20
           motorwidth: 9
@@ -175,51 +179,51 @@ parameters:
     spec:
       maxValue: 1
       minValue: 0
-      valueType: (float, int)
+      valueType: float
   height:
     defaultValue: 40
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   left.angle:
-    defaultValue: 0
+    defaultValue: 180
     spec:
       maxValue: null
       minValue: null
       units: degrees
-      valueType: (float, int)
+      valueType: float
   length:
     defaultValue: 90
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   right.angle:
-    defaultValue: 0
+    defaultValue: 180
     spec:
       maxValue: null
       minValue: null
       units: degrees
-      valueType: (float, int)
+      valueType: float
   tailwidth:
     defaultValue: 0.3333333333333333
     spec:
       maxValue: 1
       minValue: 0
-      valueType: (float, int)
+      valueType: float
   tire_thickness:
     defaultValue: 0
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   width:
     defaultValue: 60
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/ESPSegBuilder.py
 subcomponents:
   brain:
diff --git a/rocolib/library/MountedBrains.yaml b/rocolib/library/MountedBrains.yaml
index 3dc99a0b7ec90dd482b3043758eb387cbea5ae5c..d80ca298f92c7c9353667bf06b3447817f364412 100644
--- a/rocolib/library/MountedBrains.yaml
+++ b/rocolib/library/MountedBrains.yaml
@@ -8,6 +8,9 @@ connections:
     offset:
       parameter: offset
 interfaces:
+  bot:
+    interface: bot
+    subcomponent: beam
   botedge0:
     interface: botedge0
     subcomponent: beam
@@ -38,6 +41,9 @@ interfaces:
   tabedge:
     interface: tabedge
     subcomponent: beam
+  top:
+    interface: top
+    subcomponent: beam
   topedge0:
     interface: topedge0
     subcomponent: beam
@@ -51,6 +57,60 @@ interfaces:
     interface: topedge3
     subcomponent: beam
 parameters:
+  _dx:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dy:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dz:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _q_a:
+    defaultValue: 1
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_i:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_j:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_k:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  addTabs:
+    defaultValue: true
+    spec:
+      hidden: true
+      valueType: bool
   brain:
     defaultValue: nodeMCU
     spec:
@@ -96,40 +156,75 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
+  fullTab:
+    defaultValue: false
+    spec:
+      hidden: true
+      valueType: bool
   length:
-    defaultValue: 90
+    defaultValue: 100
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   offset:
     defaultValue:
     - 0
     - 0
-    spec: {}
+    spec:
+      valueType: list
+  phase:
+    defaultValue: 0
+    spec:
+      hidden: true
+      values:
+      - 0
+      - 1
+      - 2
+      - 3
   width:
     defaultValue: 50
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/MountedBrainsBuilder.py
 subcomponents:
   beam:
     classname: SimpleRectBeam
     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
+      addTabs:
+        parameter: addTabs
       depth:
         parameter: width
       depth#minValue:
         function: x.get("width")
         parameter: brain
+      fullTab:
+        parameter: fullTab
       length:
         parameter: length
       length#minValue:
         function: x.get("length")
         parameter: brain
+      phase:
+        parameter: phase
       width:
         parameter: depth
       width#minValue:
diff --git a/rocolib/library/MountedServo.yaml b/rocolib/library/MountedServo.yaml
index 8754865a8ec54baa3e1e44b4b5ded91dfdd17cde..83d75f28f432275d083b2e46c89430817ad35449 100644
--- a/rocolib/library/MountedServo.yaml
+++ b/rocolib/library/MountedServo.yaml
@@ -6,6 +6,9 @@ connections:
     - mount
   - {}
 interfaces:
+  bot:
+    interface: bot
+    subcomponent: mount
   botedge0:
     interface: botedge0
     subcomponent: mount
@@ -45,6 +48,9 @@ interfaces:
   tabedge:
     interface: tabedge
     subcomponent: mount
+  top:
+    interface: top
+    subcomponent: mount
   topedge0:
     interface: topedge0
     subcomponent: mount
@@ -64,86 +70,115 @@ parameters:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _dy:
     defaultValue: 0
     spec:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _dz:
     defaultValue: 0
     spec:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _q_a:
     defaultValue: 1
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_i:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_j:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_k:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   addTabs:
     defaultValue: true
     spec:
+      hidden: true
       valueType: bool
   angle:
-    defaultValue: 0
+    defaultValue: 180
     spec:
       maxValue: null
       minValue: null
       units: degrees
-      valueType: (float, int)
+      valueType: float
   center:
     defaultValue: true
     spec:
       valueType: bool
+  centermount:
+    defaultValue: true
+    spec:
+      valueType: bool
   flip:
     defaultValue: false
     spec:
       valueType: bool
+  fullTab:
+    defaultValue: false
+    spec:
+      hidden: true
+      valueType: bool
+  hornmount:
+    defaultValue: true
+    spec:
+      valueType: bool
+  hornslots:
+    defaultValue: true
+    spec:
+      valueType: bool
   length:
     defaultValue: 100
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   offset:
+    defaultValue: []
+    spec:
+      valueType: list
+  phase:
     defaultValue: 0
-    spec: {}
+    spec:
+      hidden: true
+      values:
+      - 0
+      - 1
+      - 2
+      - 3
   servo:
     defaultValue: fs90r
     spec:
       values:
         ds2g:
-          horndepth: 1
+          horndepth: 2
           hornheight: 9
-          hornlength: 11
+          hornlength: 10
           hornoffset: 4
+          hornwidth: 3
           motorheight: 11
           motorlength: 17
           motorwidth: 8.5
@@ -153,6 +188,7 @@ parameters:
           hornheight: 11
           hornlength: 10
           hornoffset: 8
+          hornwidth: 5
           motorheight: 19
           motorlength: 23
           motorwidth: 13
@@ -162,6 +198,7 @@ parameters:
           hornheight: 14
           hornlength: 38
           hornoffset: 7
+          hornwidth: 3
           motorheight: 29
           motorlength: 31
           motorwidth: 17
@@ -171,6 +208,7 @@ parameters:
           hornheight: 10
           hornlength: 7
           hornoffset: 4
+          hornwidth: 3
           motorheight: 14
           motorlength: 20
           motorwidth: 9
@@ -180,23 +218,41 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/MountedServoBuilder.py
 subcomponents:
   mount:
     classname: ServoMount
     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
       addTabs:
         parameter: addTabs
       center:
         parameter: center
       flip:
         parameter: flip
+      fullTab:
+        parameter: fullTab
       length:
         parameter: length
       offset:
         parameter: offset
+      phase:
+        parameter: phase
       servo:
         parameter: servo
       shift:
@@ -221,5 +277,13 @@ subcomponents:
         parameter: _q_k
       angle:
         parameter: angle
+      centermount:
+        parameter: centermount
+      flip:
+        parameter: flip
+      hornmount:
+        parameter: hornmount
+      hornslots:
+        parameter: hornslots
       servo:
         parameter: servo
diff --git a/rocolib/library/Paperbot.yaml b/rocolib/library/Paperbot.yaml
index a01cac005a5dcf83d4d98bbeda8c456a491119bc..984e5e1fc71f4ee0d467bf1af1a6c65900ff27d1 100644
--- a/rocolib/library/Paperbot.yaml
+++ b/rocolib/library/Paperbot.yaml
@@ -6,31 +6,31 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   height:
     defaultValue: 25
     spec:
       minValue: 20
       units: mm
-      valueType: (float, int)
+      valueType: float
   length:
     defaultValue: 80
     spec:
       minValue: 77
       units: mm
-      valueType: (float, int)
+      valueType: float
   tire_thickness:
     defaultValue: 0
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   width:
     defaultValue: 60
     spec:
       minValue: 60
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/PaperbotBuilder.py
 subcomponents:
   paperbot:
diff --git a/rocolib/library/PinchedRectBeam.py b/rocolib/library/PinchedRectBeam.py
index a58245357fa5a60abd771c42a3070136a619d7cf..1229faa03134f74b18dcffbe680ed37c098ab732 100644
--- a/rocolib/library/PinchedRectBeam.py
+++ b/rocolib/library/PinchedRectBeam.py
@@ -14,6 +14,9 @@ class PinchedRectBeam(FoldedComponent):
         self.addEdgeInterface(f"topedge{i}", f"s{i}t.e2", ["topwidth", "topdepth"][i % 2])
         self.addEdgeInterface(f"botedge{i}", f"s{i}b.e2", ["botwidth", "botdepth"][i % 2])
 
+    self.addEdgeInterface("top", [f"s{i}t.e2" for i in range(4)], ["topwidth", "topdepth"])
+    self.addEdgeInterface("bot", [f"s{3-i}b.e2" for i in range(4)], ["botdepth", "botwidth"])
+
   def modifyParameters(self):
     tw = self.p.topwidth
     td = self.p.topdepth
diff --git a/rocolib/library/RectBeam.py b/rocolib/library/RectBeam.py
index 74ebf2375dd66e0c2cf4221770ff2cafae0b7017..f5b00597631099eba7580d43b0c725b719b3752a 100644
--- a/rocolib/library/RectBeam.py
+++ b/rocolib/library/RectBeam.py
@@ -8,9 +8,9 @@ class RectBeam(FoldedComponent):
     self.addParameter("width", 20, paramType="length")
     self.addParameter("depth", 50, paramType="length")
 
-    self.addParameter("phase", 0, valueType="int")
+    self.addParameter("phase", 0, paramType="count")
 
-    self.addParameter("angle", overrides=("tangle", "bangle"))
+    self.addParameter("angle", paramType="angle", maxValue=180, overrides=("tangle", "bangle"))
     self.addParameter("tangle", 80, paramType="angle", maxValue=180)
     self.addParameter("bangle", 135, paramType="angle", maxValue=180)
 
diff --git a/rocolib/library/RegularNGon.py b/rocolib/library/RegularNGon.py
index 6f128eaa93c1ace3fba3b3d6f450d3b9cf0de9dd..cb0217282e18501045643287d1f3085e177b23eb 100644
--- a/rocolib/library/RegularNGon.py
+++ b/rocolib/library/RegularNGon.py
@@ -1,23 +1,29 @@
 from rocolib.api.components import FoldedComponent
-from rocolib.api.composables.graph.Face import RegularNGon2 as Shape
+from rocolib.api.composables.graph.Face import RegularNGon2 as NGonRadius, RegularNGon as NGonEdge
 
 
 class RegularNGon(FoldedComponent):
   def define(self):
     self.addParameter("n", 5, valueType="int", minValue=3)
-    self.addParameter("radius", 25, paramType="length")
+    self.addParameter("l", 25, paramType="length")
+    self.addParameter("ltype", "radius", values=("radius", "edge"))
 
-    self.addEdgeInterface("e0", "e0", "radius")
+    for i in range(10):
+      self.addEdgeInterface(f"e{i}", f"e{i}", "l")
     self.addFaceInterface("face", "r")
 
   def assemble(self):
-    self.addFace(Shape("r", self.p.n, self.p.radius))
+    if self.p.ltype == "radius":
+      Shape = NGonRadius
+    else:
+      Shape = NGonEdge
+    self.addFace(Shape("r", self.p.n, self.p.l))
 
     for i in range(self.p.n):
         try: 
-            self.setEdgeInterface("e%d" % i, "e%d" % i, "radius")
+            self.setEdgeInterface("e%d" % i, "e%d" % i, "l")
         except KeyError:
-            self.addEdgeInterface("e%d"%i, "e%d" % i, "radius")
+            self.addEdgeInterface("e%d"%i, "e%d" % i, "l")
 
 if __name__ == "__main__":
     RegularNGon.test()
diff --git a/rocolib/library/ServoMotor.py b/rocolib/library/ServoMotor.py
index 47fbbbbf9f177e2dbd4f36de96b70a4ae5e63b07..3980b2303dd26273a57df6b7f221bb4806351439 100644
--- a/rocolib/library/ServoMotor.py
+++ b/rocolib/library/ServoMotor.py
@@ -1,5 +1,5 @@
 from rocolib.api.components import FoldedComponent
-from rocolib.api.composables.graph.Face import Rectangle as Shape
+from rocolib.api.composables.graph.Face import Rectangle, Face
 from rocolib.api.ports import AnchorPort
 from rocolib.utils.utils import decorateGraph
 from rocolib.utils.transforms import Translate, RotateZ
@@ -8,7 +8,12 @@ from rocolib.utils.numsym import dot, deg2rad
 
 class ServoMotor(FoldedComponent):
     def define(self):
-        self.addParameter("angle", 0, paramType="angle", minValue=None, maxValue=None)
+        self.addParameter("angle", 180, paramType="angle", minValue=None, maxValue=None)
+        self.addParameter("centermount", True, valueType="bool")
+        self.addParameter("hornmount", True, valueType="bool")
+        self.addParameter("hornslots", True, valueType="bool")
+        self.addParameter("flip", False, valueType="bool")
+
         self.addParameter("servo", "fs90r", values=ServoMotor.dims)
         self.addInterface("mount", AnchorPort(self, self.getGraph(), "horn", Translate([0,0,0])))
         self.addFaceInterface("horn", "horn")
@@ -18,18 +23,31 @@ class ServoMotor(FoldedComponent):
 
         dz = s.get("hornheight")
         dy = s.get("motorlength") / 2 - s.get("hornoffset")
+        if self.p.flip:
+            dy = -dy
 
-        f = Shape("horn", 0, 0)
-        decorateGraph(f, Shape("hole", 1, 1))
+        f = Rectangle("horn", 0, 0)
+        if self.p.centermount:
+            decorateGraph(f, Rectangle("centerhole", 1, 1))
+        if self.p.hornmount:
+            decorateGraph(f, Face("hornhole", [(ds*dd, s.get("hornlength")+dd) for dd in (-0.1, 0.1) for ds in (-1, 1)], recenter=False))
+        if self.p.hornslots:
+            x = s.get("hornwidth") + s.get("horndepth")
+            dx = s.get("horndepth")*1.5 / 2
+            dy = ((s.get("hornwidth") + s.get("horndepth")*.5) / 2) / dx
+            while x < s.get("hornlength"):
+                decorateGraph(f, Face(f"hornslotp{x}", [(ds*dd, x+dd) for dd in (-dx, dx) for ds in (-dy, dy)], recenter=False))
+                decorateGraph(f, Face(f"hornslotn{x}", [(ds*dd, -x+dd) for dd in (-dx, dx) for ds in (-dy, dy)], recenter=False))
+                x += s.get("horndepth") * 3
         self.addFace(f)
-        self.setInterface("mount", AnchorPort(self, self.getGraph(), "horn", dot(RotateZ(deg2rad(self.p.angle)), Translate([0,-dy,dz]))))
+        self.setInterface("mount", AnchorPort(self, self.getGraph(), "horn", dot(Translate([0,-dy,dz]), RotateZ(deg2rad(self.p.angle)))))
 
 
     '''
     Servo dimension parameters:
 
                  |<-G->|
-    ^      =====v===== { H
+    ^   =o======v======o= { H (X)J
     E          _I_
     v ________| | |_____
       ^ |       |<-F->|<> D
@@ -48,6 +66,7 @@ class ServoMotor(FoldedComponent):
 
     G : hornlength
     H : horndepth
+    J : hornwidth
 
     '''
 
@@ -57,10 +76,11 @@ class ServoMotor(FoldedComponent):
             motorwidth    = 8.5,
             motorheight   = 11,
             shoulderlength= 3.5,
-            hornlength    = 11,
+            hornlength    = 10,
             hornheight    = 9,
             hornoffset    = 4,
-            horndepth     = 1,
+            horndepth     = 2,
+            hornwidth     = 3,
         ),
         s4303r = dict(
             motorlength   = 31,
@@ -71,6 +91,7 @@ class ServoMotor(FoldedComponent):
             hornheight    = 14,
             hornoffset    = 7,
             horndepth     = 2,
+            hornwidth     = 3,
         ),
         tgy1370a = dict(
             motorlength   = 20,
@@ -81,6 +102,7 @@ class ServoMotor(FoldedComponent):
             hornheight    = 10,
             hornoffset    = 4,
             horndepth     = 2,
+            hornwidth     = 3,
         ),
         fs90r = dict(
             motorlength   = 23,
@@ -91,6 +113,7 @@ class ServoMotor(FoldedComponent):
             hornheight    = 11,
             hornoffset    = 8,
             horndepth     = 2,
+            hornwidth     = 5,
         ),
     )
 
diff --git a/rocolib/library/ServoMount.yaml b/rocolib/library/ServoMount.yaml
index c60f15883cd8960c3eb62fd3eb64c409ef962655..cf71c411879ecec09baee76015b6e8c44ea9eb3f 100644
--- a/rocolib/library/ServoMount.yaml
+++ b/rocolib/library/ServoMount.yaml
@@ -8,6 +8,9 @@ connections:
     offset:
       parameter: offset
 interfaces:
+  bot:
+    interface: bot
+    subcomponent: beam
   botedge0:
     interface: botedge0
     subcomponent: beam
@@ -41,6 +44,9 @@ interfaces:
   tabedge:
     interface: tabedge
     subcomponent: beam
+  top:
+    interface: top
+    subcomponent: beam
   topedge0:
     interface: topedge0
     subcomponent: beam
@@ -54,9 +60,59 @@ interfaces:
     interface: topedge3
     subcomponent: beam
 parameters:
+  _dx:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dy:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _dz:
+    defaultValue: 0
+    spec:
+      hidden: true
+      minValue: null
+      units: mm
+      valueType: float
+  _q_a:
+    defaultValue: 1
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_i:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_j:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
+  _q_k:
+    defaultValue: 0
+    spec:
+      hidden: true
+      maxValue: 1
+      minValue: -1
+      valueType: float
   addTabs:
     defaultValue: true
     spec:
+      hidden: true
       valueType: bool
   center:
     defaultValue: true
@@ -66,24 +122,40 @@ parameters:
     defaultValue: false
     spec:
       valueType: bool
+  fullTab:
+    defaultValue: false
+    spec:
+      hidden: true
+      valueType: bool
   length:
     defaultValue: 100
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   offset:
+    defaultValue: []
+    spec:
+      valueType: list
+  phase:
     defaultValue: 0
-    spec: {}
+    spec:
+      hidden: true
+      values:
+      - 0
+      - 1
+      - 2
+      - 3
   servo:
     defaultValue: fs90r
     spec:
       values:
         ds2g:
-          horndepth: 1
+          horndepth: 2
           hornheight: 9
-          hornlength: 11
+          hornlength: 10
           hornoffset: 4
+          hornwidth: 3
           motorheight: 11
           motorlength: 17
           motorwidth: 8.5
@@ -93,6 +165,7 @@ parameters:
           hornheight: 11
           hornlength: 10
           hornoffset: 8
+          hornwidth: 5
           motorheight: 19
           motorlength: 23
           motorwidth: 13
@@ -102,6 +175,7 @@ parameters:
           hornheight: 14
           hornlength: 38
           hornoffset: 7
+          hornwidth: 3
           motorheight: 29
           motorlength: 31
           motorwidth: 17
@@ -111,6 +185,7 @@ parameters:
           hornheight: 10
           hornlength: 7
           hornoffset: 4
+          hornwidth: 3
           motorheight: 14
           motorlength: 20
           motorwidth: 9
@@ -120,23 +195,41 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/ServoMountBuilder.py
 subcomponents:
   beam:
     classname: SimpleRectBeam
     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
       addTabs:
         parameter: addTabs
       depth:
         function: x.get("motorheight")
         parameter: servo
+      fullTab:
+        parameter: fullTab
       length:
         parameter: length
       length#minValue:
         function: x.get("motorlength") + 2 * x.get("shoulderlength")
         parameter: servo
+      phase:
+        parameter: phase
       width:
         function: x.get("motorwidth")
         parameter: servo
diff --git a/rocolib/library/SimpleRectBeam.py b/rocolib/library/SimpleRectBeam.py
index dfcca369e64b87fff0ab6b6906c58449af8ece7b..64e344c36960901d0ee3301a2bc142dd477c771e 100644
--- a/rocolib/library/SimpleRectBeam.py
+++ b/rocolib/library/SimpleRectBeam.py
@@ -1,5 +1,6 @@
 from rocolib.api.components import FoldedComponent
 from rocolib.api.composables.graph.Face import Rectangle
+from rocolib.utils.tabs import BeamTabDecoration, BeamSlotDecoration
 
 class SimpleRectBeam(FoldedComponent):
   def define(self):
@@ -7,7 +8,9 @@ class SimpleRectBeam(FoldedComponent):
     self.addParameter("width", 20, paramType="length")
     self.addParameter("depth", 50, paramType="length")
 
-    self.addParameter("addTabs", True, valueType="bool")
+    self.addParameter("addTabs", True, valueType="bool", hidden=True)
+    self.addParameter("phase", 0, values=[0,1,2,3], hidden=True)
+    self.addParameter("fullTab", False, valueType="bool", hidden=True)
 
     for i in range(4):
       self.addEdgeInterface("topedge%d" % i, "r%d.e2" % i, ["width", "depth"][i % 2])
@@ -16,23 +19,38 @@ class SimpleRectBeam(FoldedComponent):
     self.addEdgeInterface("slotedge", "r3.e1", "length")
     self.addEdgeInterface("tabedge", "r0.e3", "length")
 
-  def assemble(self):
-    rs = []
-    rs.append(Rectangle("", self.p.width, self.p.length))
-    rs.append(Rectangle("", self.p.depth, self.p.length))
-    rs.append(Rectangle("", self.p.width, self.p.length))
-    rs.append(Rectangle("", self.p.depth, self.p.length))
+    self.addEdgeInterface("top", ["r%d.e2" % i for i in range(4)], ["width", "depth"])
+    self.addEdgeInterface("bot", ["r%d.e0" % (3-i) for i in range(4)], ["depth", "width"])
 
+  def assemble(self):
+    nf = 4
+    def wfn(i):
+      if i % 2:
+        return self.p.depth
+      else:
+        return self.p.width
+    rs = [Rectangle("", wfn(i), self.p.length) for i in range(nf)]
+
+    p = self.p.phase
     fromEdge = None
-    for i in range(4):
+    for j in range(nf):
+      i = (j + p) % nf
       self.attachEdge(fromEdge, rs[i], "e3", prefix="r%d"%i, angle=90)
       fromEdge = 'r%d.e1' % i
       self.setFaceInterface("face%d" % i, "r%d" % i)
 
-    tabEdge, slotEdge = "r0.e3", "r3.e1"
+    tabEdge, slotEdge = f"r{p}.e3", f"r{(p-1)%nf}.e1"
 
     if self.p.addTabs:
-        self.addTab(tabEdge, slotEdge, angle= 90, width=min(10, self.p.depth))
+      mw = min(10, wfn(p-1))
+      tab = Rectangle("", self.p.fullTab and wfn(p-1) or mw, self.p.length)
+      self.attachEdge(tabEdge, tab, "e1", prefix="tab", angle=90)
+      BeamTabDecoration(tab, "tab.e1", mw)
+      BeamSlotDecoration(rs[(p-1)%nf], slotEdge, mw)
+      tabEdge = "tab.e3"
+
+    self.setEdgeInterface("slotedge", slotEdge, "length")
+    self.setEdgeInterface("tabedge", tabEdge, "length")
 
 if __name__ == "__main__":
   SimpleRectBeam.test()
diff --git a/rocolib/library/SimpleUChannel.py b/rocolib/library/SimpleUChannel.py
index bd9abc23584b2c89a0dada86138cb4620d89a5e2..660f116aafb95f5c513a87ff5e6652eb8b322122 100644
--- a/rocolib/library/SimpleUChannel.py
+++ b/rocolib/library/SimpleUChannel.py
@@ -17,8 +17,8 @@ class SimpleUChannel(FoldedComponent):
     self.addEdgeInterface("ledge", "r0.e3", "length")
     self.addEdgeInterface("redge", "r2.e1", "length")
 
-    self.addEdgeInterface("top", ["r%d.e0" % i for i in range(3)], ["depth", "width", "depth"])
-    self.addEdgeInterface("bot", ["r%d.e2" % (2-i) for i in range(3)], ["depth", "width", "depth"])
+    self.addEdgeInterface("top", ["r%d.e0" % i for i in range(3)], ["depth", "width"])
+    self.addEdgeInterface("bot", ["r%d.e2" % (2-i) for i in range(3)], ["depth", "width"])
 
   def assemble(self):
     rs = []
diff --git a/rocolib/library/SplitEdge.py b/rocolib/library/SplitEdge.py
index 0c1cabda6f2f2328e8ba5fc92e47955b45f47f15..35c901c82db4ed816ce3b6e4da6a80dd8f4fffc4 100644
--- a/rocolib/library/SplitEdge.py
+++ b/rocolib/library/SplitEdge.py
@@ -8,8 +8,8 @@ def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
 
 class SplitEdge(FoldedComponent):
     def define(self):
-        self.addParameter("toplength", (50, 50), valueType="(tuple, list)")
-        self.addParameter("botlength", (100,), valueType="(tuple, list)")
+        self.addParameter("toplength", (50, 50), valueType="list")
+        self.addParameter("botlength", (100,), valueType="list")
         self.addParameter("width", 0)
 
         for i in range(100):
diff --git a/rocolib/library/TwoDOF.py b/rocolib/library/TwoDOF.py
index b7edc10f6331e71c5a3b7c8c8048d3c3b9f2383a..bb16d5a16a80a97fc9bd150fc152c23061b6e914 100644
--- a/rocolib/library/TwoDOF.py
+++ b/rocolib/library/TwoDOF.py
@@ -7,8 +7,8 @@ class TwoDOF(FoldedComponent):
     self.addParameter("handlelength", 15, paramType="length")
     self.addParameter("hingelength", 10, paramType="length")
     self.addParameter("thickness", 10, paramType="length")
-    self.addParameter("pitch", 135, paramType="angle")
-    self.addParameter("yaw", 60, paramType="angle")
+    self.addParameter("pitch", 15, paramType="angle", minValue=-90, maxValue=90)
+    self.addParameter("yaw", 10, paramType="angle", minValue=-90, maxValue=90)
 
     self.addEdgeInterface("mountedge", "mount.e0", "mountlength")
     self.addEdgeInterface("outedge", "r2.e2", "handlelength")
@@ -28,9 +28,9 @@ class TwoDOF(FoldedComponent):
     r2 = Rectangle("", h, t)
 
     self.addFace(b, "base")
-    self.attachEdge("base.e2", r1, "e0", prefix="r1", angle=-180)
-    self.attachEdge("r1.e3", r2, "e1", prefix="r2", angle=y)
-    self.attachEdge("base.e0", b0, "e2", prefix="mount", angle=p)
+    self.attachEdge("base.e2", r1, "e0", prefix="r1", angle=p > 0 and p-180 or -180)
+    self.attachEdge("r1.e3", r2, "e1", prefix="r2", angle=90+y)
+    self.attachEdge("base.e0", b0, "e2", prefix="mount", angle=p < 0 and 180+p or 180)
 
 if __name__ == "__main__":
     TwoDOF.test()
diff --git a/rocolib/library/UChannel.py b/rocolib/library/UChannel.py
index f1e46835d38c5d1b7321744bcc39079c2a571836..40156a93df31170388f6ff3f8cbf5a41737a872a 100644
--- a/rocolib/library/UChannel.py
+++ b/rocolib/library/UChannel.py
@@ -17,8 +17,8 @@ class UChannel(FoldedComponent):
       self.addEdgeInterface("topedge%d" % i, "r%d.e0" % i, ["depth", "width"][i % 2])
       self.addEdgeInterface("botedge%d" % i, "r%d.e2" % i, ["depth", "width"][i % 2])
       self.addFaceInterface("face%d" % i, "r%d" % i)
-    self.addEdgeInterface("top", ["r%d.e0" % i for i in range(3)], ["depth", "width", "depth"])
-    self.addEdgeInterface("bot", ["r%d.e2" % (2-i) for i in range(3)], ["depth", "width", "depth"])
+    self.addEdgeInterface("top", ["r%d.e0" % i for i in range(3)], ["depth", "width"])
+    self.addEdgeInterface("bot", ["r%d.e2" % (2-i) for i in range(3)], ["depth", "width"])
 
     self.addEdgeInterface("ledge", "r0.e3", "length")
     self.addEdgeInterface("redge", "r2.e1", "length")
diff --git a/rocolib/library/Wheel.yaml b/rocolib/library/Wheel.yaml
index 00d9c75833ca03fc4009d8018b5da9b2355d0161..ab68af642f621c6de9920df30bf16481cf81f921 100644
--- a/rocolib/library/Wheel.yaml
+++ b/rocolib/library/Wheel.yaml
@@ -6,6 +6,9 @@ connections:
     - face
   - copyDecorations: true
 interfaces:
+  bot:
+    interface: bot
+    subcomponent: drive
   botedge0:
     interface: botedge0
     subcomponent: drive
@@ -45,6 +48,9 @@ interfaces:
   tabedge:
     interface: tabedge
     subcomponent: drive
+  top:
+    interface: top
+    subcomponent: drive
   topedge0:
     interface: topedge0
     subcomponent: drive
@@ -64,92 +70,121 @@ parameters:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _dy:
     defaultValue: 0
     spec:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _dz:
     defaultValue: 0
     spec:
       hidden: true
       minValue: null
       units: mm
-      valueType: (float, int)
+      valueType: float
   _q_a:
     defaultValue: 1
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_i:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_j:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   _q_k:
     defaultValue: 0
     spec:
       hidden: true
       maxValue: 1
       minValue: -1
-      valueType: (int, float)
+      valueType: float
   addTabs:
     defaultValue: true
     spec:
+      hidden: true
       valueType: bool
   angle:
-    defaultValue: 0
+    defaultValue: 180
     spec:
       maxValue: null
       minValue: null
       units: degrees
-      valueType: (float, int)
+      valueType: float
   center:
     defaultValue: true
     spec:
       valueType: bool
+  centermount:
+    defaultValue: true
+    spec:
+      valueType: bool
   flip:
     defaultValue: false
     spec:
       valueType: bool
+  fullTab:
+    defaultValue: false
+    spec:
+      hidden: true
+      valueType: bool
+  hornmount:
+    defaultValue: true
+    spec:
+      valueType: bool
+  hornslots:
+    defaultValue: true
+    spec:
+      valueType: bool
   length:
     defaultValue: 100
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   offset:
+    defaultValue: []
+    spec:
+      valueType: list
+  phase:
     defaultValue: 0
-    spec: {}
+    spec:
+      hidden: true
+      values:
+      - 0
+      - 1
+      - 2
+      - 3
   radius:
     defaultValue: 25
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   servo:
     defaultValue: fs90r
     spec:
       values:
         ds2g:
-          horndepth: 1
+          horndepth: 2
           hornheight: 9
-          hornlength: 11
+          hornlength: 10
           hornoffset: 4
+          hornwidth: 3
           motorheight: 11
           motorlength: 17
           motorwidth: 8.5
@@ -159,6 +194,7 @@ parameters:
           hornheight: 11
           hornlength: 10
           hornoffset: 8
+          hornwidth: 5
           motorheight: 19
           motorlength: 23
           motorwidth: 13
@@ -168,6 +204,7 @@ parameters:
           hornheight: 14
           hornlength: 38
           hornoffset: 7
+          hornwidth: 3
           motorheight: 29
           motorlength: 31
           motorwidth: 17
@@ -177,6 +214,7 @@ parameters:
           hornheight: 10
           hornlength: 7
           hornoffset: 4
+          hornwidth: 3
           motorheight: 14
           motorlength: 20
           motorwidth: 9
@@ -186,13 +224,13 @@ parameters:
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
   tire_thickness:
     defaultValue: 0
     spec:
       minValue: 0
       units: mm
-      valueType: (float, int)
+      valueType: float
 source: ../builders/WheelBuilder.py
 subcomponents:
   drive:
@@ -219,12 +257,22 @@ subcomponents:
         parameter: angle
       center:
         parameter: center
+      centermount:
+        parameter: centermount
       flip:
         parameter: flip
+      fullTab:
+        parameter: fullTab
+      hornmount:
+        parameter: hornmount
+      hornslots:
+        parameter: hornslots
       length:
         parameter: length
       offset:
         parameter: offset
+      phase:
+        parameter: phase
       servo:
         parameter: servo
       shift:
diff --git a/rocolib/library/__init__.py b/rocolib/library/__init__.py
index 513e33d0b75e82255890f986f22ea65eecdd580b..b78421523abcacbbf24bb1ea6a12b9295f5062dc 100644
--- a/rocolib/library/__init__.py
+++ b/rocolib/library/__init__.py
@@ -53,6 +53,8 @@ allComponents = {**pyComponents, **yamlComponents}
 
 def _load_yaml(c):
     yamld, yamlc = _getLib(c, yamlComponents)
+    if not yamld or not yamlc:
+        raise ValueError("No suitable yaml file found")
     yamlFile = f"{libDirs[yamld]}/{yamlc}.yaml"
     with open(yamlFile, 'r') as fd:
         return safe_load(fd)
diff --git a/rocolib/utils/display.py b/rocolib/utils/display.py
index 355af0ef87342d15eb263472b6ae8403d03837e8..bf295cf90e5345bbdd38f92b4e404338200cf276 100644
--- a/rocolib/utils/display.py
+++ b/rocolib/utils/display.py
@@ -61,14 +61,16 @@ def linestyle(e, **kwargs):
 
     width = 3
     angle = e["angle"]
+    dash = None
 
     if e["interior"]: # Interior decorations
         width = 1
         if e["edgeType"] in ( EdgeType.BOUNDARY_CUT, EdgeType.INTERIOR_CUT ):
-            color = 'red'
+            color = 'blue'
             zorder = 30
         elif e["edgeType"] in ( EdgeType.FOLD, EdgeType.FLEX ):
-            color = 'blue'
+            color = 'red'
+            dash = 'dash'
             zorder = 20
         elif e["edgeType"] in ( EdgeType.FLAT ):
             color = 'white'
@@ -77,15 +79,17 @@ def linestyle(e, **kwargs):
             color = 'gray'
             zorder = 100
     elif e["faces"] == 1: # CUT
-        color = 'red'
+        color = 'blue'
         zorder = 30
     elif e["faces"] == 2: # FOLD or FLEX or BEND
         if angle: # FOLD
             if angle > 0: # mountain fold
-                color = 'blue'
+                color = 'red'
+                dash = 'dash'
                 zorder = 20
             else: # valley fold
                 color = 'green'
+                dash = 'dot'
                 zorder = 20
         else: # FLEX or BEND or FLAT
             color = 'white'
@@ -94,11 +98,11 @@ def linestyle(e, **kwargs):
         color = 'grey'
         zorder = 100
 
-    ret = dict(color = color, width = width, zorder = zorder)
+    ret = dict(color = color, width = width, zorder = zorder, dash = dash)
     ret.update(kwargs)
     return ret
 
-def cutfig(edgeset, scale=1000, axes=True):
+def cutfig(edgeset, scale=1000, axes=True, highlight=[]):
     layout = go.Layout(
         showlegend=False,
         dragmode='pan',
@@ -146,6 +150,22 @@ def cutfig(edgeset, scale=1000, axes=True):
             )
         ))
 
+        if n in highlight:
+            zorder -= 1
+            style['color'] = 'yellow'
+            style['width'] = 10
+            style.pop('dash', None)
+
+            traces.append((zorder,
+                go.Scatter(
+                    x = [x0, x1],
+                    y = [y0, y1],
+                    mode='lines',
+                    name=n,
+                    line=style,
+                )
+            ))
+
         style["width"] = 0
         traces.append((zorder,
             go.Scatter(
diff --git a/rocolib/utils/tabs.py b/rocolib/utils/tabs.py
index 1f94ddbbc58a919b54c272d3615734024ebd0a4b..30efe29242f55099893f23542cfee5ef823c3201 100644
--- a/rocolib/utils/tabs.py
+++ b/rocolib/utils/tabs.py
@@ -81,7 +81,7 @@ def BeamTabSlotHelper(face, faceEdge, thick, widget, **kwargs):
       for (name, edge) in t.edges.items():
         e = edge.copy()
         e.transform(angle = theta, origin = globalOrigin)
-        face.addDecoration((((e.x1, e.y1), (e.x2, e.y2)), e.edgetype.edgetype))
+        face.addDecoration(([(e.x1, e.y1), (e.x2, e.y2)], e.edgetype.edgetype))
       try:
         if kwargs["alternating"]:
           t.mirrorY()