diff --git a/boxes/__init__.py b/boxes/__init__.py index 91be3a6..9ce6942 100755 --- a/boxes/__init__.py +++ b/boxes/__init__.py @@ -479,7 +479,7 @@ class Boxes: if default is None: default = "e" self.argparser.add_argument( "--top_edge", action="store", - type=ArgparseEdgeType("efFhcESŠikvLtG"), choices=list("efFhcESŠikvfLtG"), + type=ArgparseEdgeType("efFhcESŠikvLtGyY"), choices=list("efFhcESŠikvfLtGyY"), default=default, help="edge type for top edge") elif arg == "outside": if default is None: default = True @@ -600,6 +600,9 @@ class Boxes: # Mounting Edge edges.MountingSettings(self.thickness, True, **self.edgesettings.get("Mounting", {})).edgeObjects(self) + # Handle Edge + edges.HandleEdgeSettings(self.thickness, True, + **self.edgesettings.get("HandleEdge", {})).edgeObjects(self) # HexHoles self.hexHolesSettings = HexHolesSettings(self.thickness, True, **self.edgesettings.get("HexHoles", {})) diff --git a/boxes/edges.py b/boxes/edges.py index a9318a5..4933a36 100644 --- a/boxes/edges.py +++ b/boxes/edges.py @@ -23,6 +23,39 @@ import abc from boxes import gears +def argparseSections(s): + """ + Parse sections parameter + + :param s: string to parse + + """ + + result = [] + + s = re.split(r"\s|:", s) + + try: + for part in s: + m = re.match(r"^(\d+(\.\d+)?)/(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1)) / n] * n) + continue + m = re.match(r"^(\d+(\.\d+)?)\*(\d+)$", part) + if m: + n = int(m.group(3)) + result.extend([float(m.group(1))] * n) + continue + result.append(float(part)) + except ValueError: + raise argparse.ArgumentTypeError("Don't understand sections string") + + if not result: + result.append(0.0) + + return result + def getDescriptions(): d = {edge.char: edge.description for edge in globals().values() if inspect.isclass(edge) and issubclass(edge, BaseEdge) @@ -2465,3 +2498,112 @@ class RoundedTriangleFingerHolesEdge(RoundedTriangleEdge): def __call__(self, length, **kw): self.fingerHolesAt(0, 0.5*self.settings.thickness, length, 0) super().__call__(length, **kw) + + +class HandleEdgeSettings(Settings): + + """Settings for HandleEdge +Values: + +* absolute_params + + * height : 20. : height above the wall in mm + * radius : 10. : radius of corners in mm + * hole_width : "40:40" : width of hole(s) in percentage of maximum hole width (width of edge - (n+1) * material thickness) + * hole_height : 75. : height of hole(s) in percentage of maximum hole height (handle height - 2 * material thickness) + * on_sides : True, : added to side panels if checked, to front and back otherwise (only used with top_edge parameter) + +* relative + + * outset : 1. : extend the handle along the length of the edge (multiples of thickness) + +""" + + absolute_params = { + "height" : 20., + "radius" : 10., + "hole_width" : "40:40", + "hole_height" : 75., + "on_sides": True, + } + + relative_params = { + "outset" : 1., + } + + def edgeObjects(self, boxes, chars="yY", add=True): + edges = [HandleEdge(boxes, self), + HandleHoleEdge(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add) + +# inspiration came from https://www.thingiverse.com/thing:327393 + +class HandleEdge(Edge): + """Extends an 'edge' by adding a rounded bumpout with optional holes""" + description = "Handle for e.g. a drawer" + char = "y" + extra_height = 0.0 + + def __call__(self, length, **kw): + length += 2 * self.settings.outset + extra_height = self.extra_height * self.settings.thickness + + r = self.settings.radius + if r > length / 2: + r = length / 2 + if r > self.settings.height: + r = self.settings.height + + widths = argparseSections(self.settings.hole_width) + + if self.settings.outset: + self.polyline(0, -180, self.settings.outset, 90) + else: + self.corner(-90) + + if self.settings.hole_height and sum(widths) > 0: + if sum(widths) < 100: + slot_offset = ((1 - sum(widths) / 100) * (length - (len(widths) + 1) * self.thickness)) / (len(widths) * 2) + else: + slot_offset = 0 + + slot_height = (self.settings.height - 2 * self.thickness) * self.settings.hole_height / 100 + slot_x = self.thickness + slot_offset + + for w in widths: + if sum(widths) > 100: + slotwidth = w / sum(widths) * (length - (len(widths) + 1) * self.thickness) + else: + slotwidth = w / 100 * (length - (len(widths) + 1) * self.thickness) + slot_x += slotwidth / 2 + with self.saved_context(): + self.moveTo((self.settings.height / 2) + extra_height, slot_x, 0) + self.rectangularHole(0,0,slot_height,slotwidth,slot_height/2,True,True) + slot_x += slotwidth / 2 + slot_offset + self.thickness + slot_offset + + self.edge(self.settings.height - r + extra_height, tabs=1) + self.corner(90, r, tabs=1) + self.edge(length - 2 * r, tabs=1) + self.corner(90, r, tabs=1) + self.edge(self.settings.height - r + extra_height, tabs=1) + + if self.settings.outset: + self.polyline(0, 90, self.settings.outset, -180) + else: + self.corner(-90) + + def margin(self): + return self.settings.height + +class HandleHoleEdge(HandleEdge): + """Extends an 'edge' by adding a rounded bumpout with optional holes and holes for parallel finger joint""" + description = "Handle with holes for parallel finger joint" + char = "Y" + extra_height = 1.0 + + def __call__(self, length, **kw): + self.fingerHolesAt(0, -0.5*self.settings.thickness, length, 0) + super().__call__(length, **kw) + + def margin(self): + return self.settings.height + self.extra_height*self.settings.thickness diff --git a/boxes/generators/alledges.py b/boxes/generators/alledges.py index f3198df..63454bf 100644 --- a/boxes/generators/alledges.py +++ b/boxes/generators/alledges.py @@ -30,6 +30,7 @@ class AllEdges(Boxes): self.addSettingsArgs(edges.LidSettings) self.addSettingsArgs(edges.ClickSettings) self.addSettingsArgs(edges.FlexSettings) + self.addSettingsArgs(edges.HandleEdgeSettings) self.buildArgParser(x=100) diff --git a/boxes/generators/storageshelf.py b/boxes/generators/storageshelf.py index 96c226e..c3da729 100644 --- a/boxes/generators/storageshelf.py +++ b/boxes/generators/storageshelf.py @@ -84,7 +84,7 @@ class StorageShelf(_TopEdge): #if top_edge is t put the handle on the x walls if(self.top_edge=='t'): t1,t2,t3,t4=(t2,t1,t4,t3) - self.closedtop = self.top_edge in "fFhŠ" + self.closedtop = self.top_edge in "fFhŠY" # x sides diff --git a/boxes/lids.py b/boxes/lids.py index 783fb7d..710885a 100644 --- a/boxes/lids.py +++ b/boxes/lids.py @@ -92,7 +92,7 @@ class _TopEdge(Boxes): def addTopEdgeSettings(self, fingerjoint={}, stackable={}, hinge={}, cabinethinge={}, lid={}, click={}, - roundedtriangle={}, mounting={}): + roundedtriangle={}, mounting={}, handle={}): self.addSettingsArgs(edges.FingerJointSettings, **fingerjoint) self.addSettingsArgs(edges.StackableSettings, **stackable) self.addSettingsArgs(edges.HingeSettings, **hinge) @@ -101,6 +101,7 @@ class _TopEdge(Boxes): self.addSettingsArgs(edges.ClickSettings, **click) self.addSettingsArgs(edges.RoundedTriangleEdgeSettings, **roundedtriangle) self.addSettingsArgs(edges.MountingSettings, **mounting) + self.addSettingsArgs(edges.HandleEdgeSettings, **handle) def topEdges(self, top_edge): t1 = t2 = t3 = t4 = self.edges.get(top_edge, self.edges["e"]) @@ -128,6 +129,18 @@ class _TopEdge(Boxes): t4 = "G" else: #PARAM_BACK t2 = "G" + elif t1.char == "y": + t1 = t2 = t3 = t4 = "e" + if self.edges["y"].settings.on_sides == True: + t1 = t3 = "y" + else: + t2 = t4 = "y" + elif t1.char == "Y": + t1 = t2 = t3 = t4 = "h" + if self.edges["Y"].settings.on_sides == True: + t1 = t3 = "Y" + else: + t2 = t4 = "Y" return [t1, t2, t3, t4] def drawLid(self, x, y, top_edge, bedBolts=[None, None]): @@ -136,7 +149,7 @@ class _TopEdge(Boxes): self.rectangularWall(x, y, "CCCC", bedBolts=[d2, d3, d2, d3], move="up", label="top") elif top_edge == "f": self.rectangularWall(x, y, "FFFF", move="up", label="top") - elif top_edge in "FhŠ": + elif top_edge in "FhŠY": self.rectangularWall(x, y, "ffff", move="up", label="top") elif top_edge == "L": self.rectangularWall(x, y, "nlmE", move="up", label="lid top") diff --git a/documentation/src/api_edges.rst b/documentation/src/api_edges.rst index f85e86e..b1115f7 100644 --- a/documentation/src/api_edges.rst +++ b/documentation/src/api_edges.rst @@ -61,6 +61,8 @@ A set of instances are kept the ``.edges`` attribute of the * t : RoundedTriangleEdge * uUvV : CabinetHingeEdge * X : FlexEdge +* y : HandleEdge +* Y : HandleHoleEdge * Z : GroovedEdgeCounterPart * z : GroovedEdge