diff options
author | Elena ``of Valhalla'' Grandi <valhalla@trueelena.org> | 2022-01-10 19:55:52 +0100 |
---|---|---|
committer | Elena ``of Valhalla'' Grandi <valhalla@trueelena.org> | 2022-01-10 19:55:52 +0100 |
commit | eda530251ec493a67e7762a0725443baa9f18f85 (patch) | |
tree | e3c3536105bc096ebdcc70b2d1e01f117577817a | |
parent | 543c15e2c928909e38743d7624420f5f4578d1d3 (diff) |
Add a script to generate an SVG of a tuckbock for playing cards etc.
-rwxr-xr-x | tuckbox.py | 230 |
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() |