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()