diff --git a/boxes/generators/beequeencage.py b/boxes/generators/beequeencage.py new file mode 100644 index 000000000..8b90be332 --- /dev/null +++ b/boxes/generators/beequeencage.py @@ -0,0 +1,183 @@ +# Copyright (C) 2025 Martin Scharrer +# +# Based on boxes/generators/closedbox.py, Copyright (C) 2013-2014 Florian Festi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from boxes import Boxes, edges, boolarg +from boxes.Color import Color + + +class BeeQueenCageWallSettings(edges.Settings): + """ Settings for walls of BeeQueenCage + """ + NAME = None + PREFIX = None + + absolute_params = { + "airhole_width": (3.0, float, "width of air holes (0 for no holes) [mm]"), + "airhole_separation": (1.5, float, "distance between air holes [mm]"), + "radius": (1.0, float, "corner radius of air holes [mm]"), + "top_margin": (4.0, float, "distance of air holes on top side [mm]"), + "bottom_margin": (22.0, float, "distance of air holes on bottom side [mm]"), + "side_margin": (4.0, float, "side distance of air holes [mm]"), + } + + def __init__(self, thickness, relative: bool = True, **kw) -> None: + self.values = {} + for name, (value, t, description) in self.absolute_params.items(): + if isinstance(value, tuple): + value = value[0] + if type(value) not in (bool, int, float, str): + raise ValueError("Type not supported: %r", value) + self.values[name] = value + + self.thickness = thickness + factor = 1.0 + if relative: + factor = thickness + for name, (value, t, description) in self.relative_params.items(): + self.values[name] = value * factor + self.setValues(thickness, relative, **kw) + + @classmethod + def parserArguments(cls, parser, prefix=None, **defaults): + name = cls.NAME or cls.__doc__.split("\n")[0] + if not prefix: + prefix = cls.PREFIX or cls.__name__[:-len("Settings")] + group = parser.add_argument_group(name) + group.prefix = prefix + + for name, (default, t, description) in (sorted(cls.absolute_params.items()) + + sorted(cls.relative_params.items())): + aname = name.replace(" ", "_") + group.add_argument(f"--{prefix}_{aname}", + type=t, + action="store", default=default, + choices=None, + help=description) + +class BeeQueenCagePlugSettings(BeeQueenCageWallSettings): + """ Settings for plug of BeeQueenCage + """ + absolute_params = { + 'diameter_top': (25.0, float, "diameter of top part of the plug (0 for no plug) [mm]"), + 'diameter_bottom': (17.0, float, + "diameter of bottom part of the plug (Should correspondent to hole diameter) [mm]"), + 'diameter_inner': (9.65, float, "diameter of inner cutout in bottom part (0 for no inner cutout) [mm]"), + } + + +class BeeQueenCage(Boxes): + """Cage box to house a bee queen""" + + ui_group = "Beekeeping" + + description = """Cage box to house a bee queen. +The default opening on top is suitable for a Nicot queen-rearing cell cup block (CNE2) or a cork plug. +This makes the cage suitable as a hatching cage. + +Holes can be configured per side. The default value of 3.0mm is suitable as air holes, but this can be changed to +produce queen excluders (4.2mm) or drone excluders (5.2mm). Some space should be left closed to provide cover for +the young queen against aggressive worker bees. + +If required a plug can be added to close the top opening. +By default it contains a inner cutout to fit a Nicot cell cup (CNE3) making it a wooden alternative to the +plastic Nicot cell cup block (CNE2). +""" + + def __init__(self) -> None: + Boxes.__init__(self) + self.addSettingsArgs(edges.FingerJointSettings) + self.buildArgParser("outside", x=25, y=25, h=70) + + # Custom UI parameters + ap = self.argparser + ap.add_argument("--d", type=float, default=17.0, help="Diameter of top hole [mm]") + for name in ("front", "back", "left", "right"): + ap.add_argument(f"--holes_{name}", type=boolarg, default=True, + help=f"create holes on the {name}") + + self.addSettingsArgs(BeeQueenCageWallSettings) + + def airholes(self, w, h, label): + if label == "Top": + self.render_top(w, h) + elif label == "Bottom": + pass + elif getattr(self, f"holes_{label.lower()}"): + label = "" + g = self.wallSettings.airhole_width + dg = self.wallSettings.airhole_separation + r = self.wallSettings.radius + tm = self.wallSettings.top_margin + bm = self.wallSettings.bottom_margin + sm = self.wallSettings.side_margin + + k = g + dg # pitch of holes + num_holes = int((h - tm - bm + dg / 2) / k) # number of holes over height + xch = w / 2. + l = w - 2 * sm # length of holes + bh = (h - k * num_holes + dg + bm - tm) / 2. # offset of first hole + + for n in range(0, num_holes): + self.rectangularHole(xch, bh + n * k, l, g, r, True, False) + + def render_top(self, x, h): + self.hole(x / 2., h / 2., d=self.d) + + def render_wall(self, edges, w, h, label, move="right"): + self.rectangularWall(w, h, edges, bedBolts=[None] * 4, + move=move, label=label, callback=[lambda: self.airholes(w, h, label)]) + + def render_plug(self): + """ Render round plug for the bee queen cage""" + dt = self.plugSettings.diameter_top + db = self.plugSettings.diameter_bottom + di = self.plugSettings.diameter_inner + + if dt > 0 and db > 0: + with self.saved_context(): + self.parts.disc(dt, label="Plug\nTop", move="up", + callback=lambda: self.hole(0, 0, d=db, color=Color.ETCHING)) + self.parts.disc(db, label="Plug\nBottom", callback=None if di == 0 else lambda: self.hole(0, 0, d=di)) + + def render(self): + self.plugSettings = BeeQueenCagePlugSettings( + self.thickness, True, **self.edgesettings.get("BeeQueenCagePlug", {})) + self.wallSettings = BeeQueenCageWallSettings( + self.thickness, True, **self.edgesettings.get("BeeQueenCageWall", {})) + + ox, oy, oh = x, y, h = self.x, self.y, self.h + + if self.outside: + x = self.adjustSize(x) + y = self.adjustSize(y) + h = self.adjustSize(h) + + self.render_wall("FFFF", x, h, "Front", "right") + self.render_wall("FFFF", x, h, "Back", "right") + self.render_wall("FfFf", y, h, "Left", "right") + self.render_wall("FfFf", y, h, "Right", "right") + + with self.saved_context(): + self.render_wall("ffff", x, y, "Top", "up" if 2 * oy <= oh else "right") + self.render_wall("ffff", x, y, "Bottom", "right") + + # Move drawing point to the right for lid rendering + if 2 * oy > oh: + self.render_wall("ffff", x, y, "Top", "right only") + self.render_wall("ffff", x, y, "Bottom", "right only") + + self.render_plug() diff --git a/examples/BeeQueenCage.svg b/examples/BeeQueenCage.svg new file mode 100644 index 000000000..15371e9b5 --- /dev/null +++ b/examples/BeeQueenCage.svg @@ -0,0 +1,112 @@ + + + +BeeQueenCage + + +Beekeeping - BeeQueenCage +boxes BeeQueenCage +Cage box to house a bee queen + +Cage box to house a bee queen. +The default opening on top is suitable for a Nicot queen-rearing cell cup block (CNE2) or a cork plug. +This makes the cage suitable as a hatching cage. + +Holes can be configured per side. The default value of 3.0mm is suitable as air holes, but this can be changed to +produce queen excluders (4.2mm) or drone excluders (5.2mm). Some space should be left closed to provide cover for +the young queen against aggressive worker bees. + +If required a plug can be added to close the top opening. +By default it contains a inner cutout to fit a Nicot cell cup (CNE3) making it a wooden alternative to the +plastic Nicot cell cup block (CNE2). + + +Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) +Command line: boxes BeeQueenCage +Command line short: boxes BeeQueenCage + + + + + 100.0mm, burn:0.10mm + + + + + + + + + + + Front + + + + + + + + + + + Back + + + + + + + + + + + Left + + + + + + + + + + + Right + + + + Top + + + Bottom + + + + TopPlug + + + + BottomPlug + + \ No newline at end of file diff --git a/static/samples/BeeQueenCage-thumb.jpg b/static/samples/BeeQueenCage-thumb.jpg new file mode 100644 index 000000000..f89806ba5 Binary files /dev/null and b/static/samples/BeeQueenCage-thumb.jpg differ diff --git a/static/samples/BeeQueenCage.jpg b/static/samples/BeeQueenCage.jpg new file mode 100644 index 000000000..3881cc4f8 Binary files /dev/null and b/static/samples/BeeQueenCage.jpg differ