summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElena ``of Valhalla'' Grandi <valhalla@trueelena.org>2022-01-10 19:55:52 +0100
committerElena ``of Valhalla'' Grandi <valhalla@trueelena.org>2022-01-10 19:55:52 +0100
commiteda530251ec493a67e7762a0725443baa9f18f85 (patch)
treee3c3536105bc096ebdcc70b2d1e01f117577817a
parent543c15e2c928909e38743d7624420f5f4578d1d3 (diff)
Add a script to generate an SVG of a tuckbock for playing cards etc.
-rwxr-xr-xtuckbox.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/tuckbox.py b/tuckbox.py
new file mode 100755
index 0000000..73d4eaf
--- /dev/null
+++ b/tuckbox.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python3
+
+import argparse
+
+import svgwrite
+
+try:
+ from lesana.command import _get_first_docstring_line # type: ignore
+except ImportError:
+ def _get_first_docstring_line(obj):
+ return ""
+
+try:
+ import argcomplete # type: ignore
+except ImportError:
+ argcomplete = False
+
+
+# 1 mm in px
+mm = 3.7795276
+
+LINE_STYLE = {
+ 'stroke': svgwrite.utils.rgb(127, 127, 127),
+ 'stroke_width': 0.5,
+ 'fill': 'none'
+ }
+
+
+class Structure():
+ def __init__(self, x, y, z, flap=20, tab=16):
+ """
+ """
+ self.x = x * mm
+ self.y = y * mm
+ self.z = z * mm
+ self.f = flap * mm
+ self.t = tab * mm # tab lenght
+
+ self.full_width = self.t + self.x * 2 + self.z * 2
+ self.full_height = self.f + self.z * 2 + self.y
+
+ def add_to(self, drw, canvas_x=297 * mm, canvas_y=210 * mm):
+ grp = drw.g()
+ grp.add(drw.polygon(
+ (
+ (3 * mm, self.z - self.t),
+ (self.z - 3 * mm, self.z - self.t),
+ (self.z, self.z),
+ (0, self.z),
+ ),
+ id='right_side_bottom_tab',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.rect(
+ (self.z, 0),
+ (self.x, self.z),
+ id='base',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.polygon(
+ (
+ (self.z + self.x + 3 * mm, self.z - self.t),
+ (self.z * 2 + self.x - 3 * mm, self.z - self.t),
+ (self.z * 2 + self.x, self.z),
+ (self.z + self.x, self.z),
+ ),
+ id='left_side_bottom_tab',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.polygon(
+ (
+ (self.z * 2 + self.x + 3 * mm, self.z - self.t),
+ (self.z * 2 + self.x * 2 - 3 * mm, self.z - self.t),
+ (self.z * 2 + self.x * 2, self.z),
+ (self.z * 2 + self.x, self.z),
+ ),
+ id='back_bottom_tab',
+ ** LINE_STYLE,
+ ))
+
+ grp.add(drw.rect(
+ (0, self.z),
+ (self.z, self.y),
+ id='right_side',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.rect(
+ (self.z, self.z),
+ (self.x, self.y),
+ id='front',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.rect(
+ (self.x + self.z, self.z),
+ (self.z, self.y),
+ id='left_side',
+ ** LINE_STYLE,
+ ))
+ # TODO: remove the semicircle from the rect
+ grp.add(drw.rect(
+ (self.x + self.z * 2, self.z),
+ (self.x, self.y),
+ id='back',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.ellipse(
+ (self.x + self.z * 2 + self.x / 2, self.z + self.y),
+ (8 * mm, 8 * mm),
+ id='back_cut',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.polygon(
+ (
+ (self.x * 2 + self.z * 2, self.z),
+ (self.x * 2 + self.z * 2 + self.t, self.z + 3 * mm),
+ (self.x * 2 + self.z * 2 + self.t, self.z + self.y - 3 * mm),
+ (self.x * 2 + self.z * 2, self.z + self.y),
+ ),
+ id='side_tab',
+ ** LINE_STYLE,
+ ))
+
+ grp.add(drw.polygon(
+ (
+ (0, self.z + self.y),
+ (self.z, self.z + self.y),
+ (self.z - 3 * mm, self.z + self.y + self.t),
+ (3 * mm, self.z + self.y + self.t),
+ ),
+ id='right_side_top_tab',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.rect(
+ (self.z, self.z + self.y),
+ (self.x, self.z),
+ id='top',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.polygon(
+ (
+ (self.z + self.x, self.z + self.y),
+ (self.z * 2 + self.x, self.z + self.y),
+ (self.z * 2 + self.x - 3 * mm, self.z + self.y + self.t),
+ (self.z + self.x + 3 * mm, self.z + self.y + self.t),
+ ),
+ id='left_side_top_tab',
+ ** LINE_STYLE,
+ ))
+
+ # TODO: make this an half-ellipse
+ grp.add(drw.ellipse(
+ (self.z + self.x / 2, self.z * 2 + self.y),
+ (self.x / 2, self.f),
+ id='top_tab',
+ ** LINE_STYLE,
+ ))
+ grp.add(drw.rect(
+ (self.z, self.z + self.y + self.z - self.f - 1),
+ (self.x, self.f + 1),
+ id='top_tab_excess',
+ ** LINE_STYLE,
+ ))
+
+ grp.rotate(
+ 180,
+ center=(canvas_x / 2, canvas_y / 2),
+ )
+ grp.translate(
+ (canvas_x - self.full_width) / 2,
+ (canvas_y - self.full_height) / 2
+ )
+ drw.add(grp)
+
+
+class Command():
+ """
+ Generate an SVG with the basic shape of a tuckbox for gaming cards.
+ """
+
+ def get_parser(self):
+ desc = _get_first_docstring_line(self)
+ parser = argparse.ArgumentParser(description=desc)
+ parser.add_argument(
+ "-x", "--size-x",
+ default=60,
+ type=int,
+ help="Internal width of the box.",
+ )
+ parser.add_argument(
+ "-y", "--size-y",
+ default=94,
+ type=int,
+ help="Internal height of the box.",
+ )
+ parser.add_argument(
+ "-z", "--size-z",
+ default=42,
+ type=int,
+ help="Internal thickness of the box.",
+ )
+ parser.add_argument(
+ "-o", "--output",
+ default="tuckbox",
+ help="Base output file name."
+ )
+ return parser
+
+ def main(self):
+ self.parser = self.get_parser()
+ if argcomplete:
+ argcomplete.autocomplete(self.parser)
+ self.args = self.parser.parse_args()
+
+ size_spec = "{}×{}×{}".format(
+ self.args.size_x,
+ self.args.size_y,
+ self.args.size_z,
+ )
+ dest = svgwrite.Drawing(
+ filename="{}-{}.svg".format(self.args.output, size_spec),
+ size=('297mm', '210mm'),
+ profile='full',
+ )
+ struc = Structure(self.args.size_x, self.args.size_y, self.args.size_z)
+ struc.add_to(dest)
+ dest.save()
+
+
+if __name__ == '__main__':
+ Command().main()