From dfc2f075d96cbe5e68f534df8ee8292c993ddc54 Mon Sep 17 00:00:00 2001 From: Marcin Zukowski Date: Mon, 28 Sep 2020 14:45:54 -0700 Subject: [PATCH] Adding Grooved Edge (z, Z), example use in UnevenHeightBox --- boxes/__init__.py | 3 + boxes/edges.py | 131 ++++++++++++++++++++++++++++ boxes/generators/unevenheightbox.py | 29 ++++-- 3 files changed, 154 insertions(+), 9 deletions(-) diff --git a/boxes/__init__.py b/boxes/__init__.py index ef85b0f..365c0f9 100755 --- a/boxes/__init__.py +++ b/boxes/__init__.py @@ -541,6 +541,9 @@ class Boxes: # Rounded Triangle Edge edges.RoundedTriangleEdgeSettings(self.thickness, True, **self.edgesettings.get("RoundedTriangleEdge", {})).edgeObjects(self) + # Grooved Edge + edges.GroovedSettings(self.thickness, True, + **self.edgesettings.get("Grooved", {})).edgeObjects(self) # HexHoles self.hexHolesSettings = HexHolesSettings(self.thickness, True, diff --git a/boxes/edges.py b/boxes/edges.py index b17afd7..35078fc 100644 --- a/boxes/edges.py +++ b/boxes/edges.py @@ -342,6 +342,137 @@ class OutSetEdge(Edge): return self.boxes.thickness +############################################################################# +#### GroovedEdge +############################################################################# + +class GroovedSettings(Settings): + """Settings for Grooved Edge +Values: + +* absolute_params + + * style : "arc" : the style of grooves + * tri_angle : 30 : the angle of triangular cuts + * arc_angle : 120 : the angle of arc cuts + * width : 0.2 : the width of each groove (fraction of the edge length) + * gap : 0.1 : the gap between grooves (fraction of the edge length) + * margin : 0.3 : minimum space left and right without grooves (fraction of the edge length) + * inverse : False : invert the groove directions + * interleave : False : alternate the direction of grooves +""" + + PARAM_ARC = "arc" + PARAM_FLAT = "flat" + PARAM_SOFTARC = "softarc" + PARAM_TRIANGLE = "triangle" + + absolute_params = { + "style": (PARAM_ARC, PARAM_FLAT, PARAM_TRIANGLE, PARAM_SOFTARC), + "tri_angle": 30, + "arc_angle": 120, + "width": 0.2, + "gap": 0.1, + "margin": 0.3, + "inverse": False, + "interleave": False, + } + + def edgeObjects(self, boxes, chars="zZ", add=True): + edges = [GroovedEdge(boxes, self), + GroovedEdgeCounterPart(boxes, self)] + return self._edgeObjects(edges, boxes, chars, add) + + +class GroovedEdgeBase(BaseEdge): + def is_inverse(self): + return self.settings.inverse != self.inverse + + def __call__(self, length, **kw): + if length == 0.0: + return + + def check_bounds(val, mn, mx, name): + if not mn <= val <= mx: + raise ValueError(f"{name} needs to be in [{mn}, {mx}] but is {val}") + + style = self.settings.style + width = self.settings.width + margin = self.settings.margin + gap = self.settings.gap + interleave = self.settings.interleave + + check_bounds(width, 0, 1, "width") + check_bounds(margin, 0, 0.5, "margin") + check_bounds(gap, 0, 1, "gap") + + # Check how many grooves fit + count = max(0, int((1 - 2 * margin + gap) / (width + gap))) + inside_width = max(0, count * (width + gap) - gap) + margin = (1 - inside_width) / 2 + + # Convert to actual length + margin = length * margin + gap = length * gap + width = length * width + + # Determine the initial inversion + inv = 1 if self.is_inverse() else -1 + if interleave and self.inverse and count % 2 == 0: + inv = -inv + + # The edge until the first groove + self.edge(margin, tabs=1) + + # Grooves + for i in range(count): + if i > 0: + self.edge(gap) + if interleave: + inv = -inv + if style == GroovedSettings.PARAM_FLAT: + self.edge(width) + elif style == GroovedSettings.PARAM_ARC: + angle = self.settings.arc_angle / 2 + side_length = width / math.sin(math.radians(angle)) / 2 + self.corner(inv * -angle) + self.corner(inv * angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * -angle) + elif style == GroovedSettings.PARAM_SOFTARC: + angle = self.settings.arc_angle / 2 + side_length = width / math.sin(math.radians(angle)) / 4 + self.corner(inv * -angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * angle, side_length) + self.corner(inv * -angle, side_length) + elif style == GroovedSettings.PARAM_TRIANGLE: + angle = self.settings.tri_angle + side_length = width / math.cos(math.radians(angle)) / 2 + self.corner(inv * -angle) + self.edge(side_length) + self.corner(inv * 2 * angle) + self.edge(side_length) + self.corner(inv * -angle) + else: + raise ValueError("Unknown GroovedEdge style: %s)" % style) + + # The final edge + self.edge(margin, tabs=1) + + +class GroovedEdge(GroovedEdgeBase): + description = """Edge with grooves""" + char = 'z' + inverse = False + + +class GroovedEdgeCounterPart(GroovedEdgeBase): + description = """Edge with grooves (opposing side)""" + char = 'Z' + inverse = True + + ############################################################################# #### Gripping Edge ############################################################################# diff --git a/boxes/generators/unevenheightbox.py b/boxes/generators/unevenheightbox.py index ee6b8b6..93bb623 100644 --- a/boxes/generators/unevenheightbox.py +++ b/boxes/generators/unevenheightbox.py @@ -25,6 +25,7 @@ class UnevenHeightBox(Boxes): def __init__(self): Boxes.__init__(self) self.addSettingsArgs(edges.FingerJointSettings) + self.addSettingsArgs(edges.GroovedSettings) self.buildArgParser("x", "y", "outside", bottom_edge="F") self.argparser.add_argument( "--height0", action="store", type=float, default=50, @@ -44,12 +45,19 @@ class UnevenHeightBox(Boxes): self.argparser.add_argument( "--lid_height", action="store", type=float, default=0, help="additional height of the lid") + self.argparser.add_argument( + "--edge_types", action="store", type=str, default="eeee", + help="which edges are flat (e) or grooved (z,Z), counter-clockwise from the front") def render(self): x, y = self.x, self.y heights = [self.height0, self.height1, self.height2, self.height3] + edge_types = self.edge_types + if len(edge_types) != 4 or any(et not in "ezZ" for et in edge_types): + raise ValueError("Wrong edge_types style: %s)" % edge_types) + if self.outside: x = self.adjustSize(x) y = self.adjustSize(y) @@ -61,10 +69,10 @@ class UnevenHeightBox(Boxes): h0, h1, h2, h3 = heights b = self.bottom_edge - self.trapezoidWall(x, h0, h1, [b, "F", "e", "F"], move="right") - self.trapezoidWall(y, h1, h2, [b, "f", "e", "f"], move="right") - self.trapezoidWall(x, h2, h3, [b, "F", "e", "F"], move="right") - self.trapezoidWall(y, h3, h0, [b, "f", "e", "f"], move="right") + self.trapezoidWall(x, h0, h1, [b, "F", edge_types[0], "F"], move="right") + self.trapezoidWall(y, h1, h2, [b, "f", edge_types[1], "f"], move="right") + self.trapezoidWall(x, h2, h3, [b, "F", edge_types[2], "F"], move="right") + self.trapezoidWall(y, h3, h0, [b, "f", edge_types[3], "f"], move="right") with self.saved_context(): if b != "e": @@ -79,14 +87,17 @@ class UnevenHeightBox(Boxes): self.rectangularWall(x, y, edges, move="up") if self.lid: - self.moveTo(0, maxh+self.lid_height+self.edges["F"].spacing()+self.edges[b].spacing()+3*self.spacing, 180) - self.trapezoidWall(y, h0, h3, "Ffef", move="right" + + self.moveTo(0, maxh+self.lid_height+self.edges["F"].spacing()+self.edges[b].spacing()+1*self.spacing, 180) + edge_inverse = {"e": "e", "z": "Z", "Z": "z"} + edge_types = [edge_inverse[et] for et in edge_types] + + self.trapezoidWall(y, h0, h3, "Ff" + edge_types[3] + "f", move="right" + (" only" if h0 == h3 == 0.0 else "")) - self.trapezoidWall(x, h3, h2, "FFeF", move="right" + + self.trapezoidWall(x, h3, h2, "FF" + edge_types[2] + "F", move="right" + (" only" if h3 == h2 == 0.0 else "")) - self.trapezoidWall(y, h2, h1, "Ffef", move="right" + + self.trapezoidWall(y, h2, h1, "Ff" + edge_types[1] + "f", move="right" + (" only" if h2 == h1 == 0.0 else "")) - self.trapezoidWall(x, h1, h0, "FFeF", move="right" + + self.trapezoidWall(x, h1, h0, "FF" + edge_types[0] + "F", move="right" + (" only" if h1 == h0 == 0.0 else ""))