goban/lasercut/goban-lasercut.py

384 lines
13 KiB
Python
Raw Normal View History

2021-11-02 01:47:43 +01:00
#!/usr/bin/env python3
import argparse
import math
import drawSvg as draw
def is_hoshi(pos, size):
"""
Is this position a hoshi ?
"""
for axis in (0, 1):
if size[axis] > 10:
if pos[axis] not in (3, round(size[axis]/2)-1, size[axis]-4):
return False
elif pos[axis] not in (2, round(size[axis]/2)-1, size[axis]-3):
return False
return True
class GobanDraw:
"""
Drawing a goban and its stones.
@param config: Config from argparse
"""
def __init__(self, config) -> None:
self.config = config
self.scale = config.resolution
self.total_size = (
((config.size[0]-1)*config.cellsize[0]+
config.borders[1]+config.borders[3]+
2*config.stone_radius+2*config.stone_margin)*self.scale,
((config.size[1]-1)*config.cellsize[1]+
config.borders[0]+config.borders[2]+
2*config.stone_radius+2*config.stone_margin)*self.scale)
print("Total size: %dx%d" % self.total_size)
self.recto_drawing = draw.Drawing(*self.total_size)
self.verso_drawing = draw.Drawing(*self.total_size)
self.base_drawing = draw.Drawing(*self.total_size)
def drawEngraving(self) -> None:
"""
Draw engraving of stones and goban
"""
if not self.config.engrave:
return
engraved_stones = 0
borders = [b*self.scale for b in self.config.borders]
cellsize = [s*self.scale for s in self.config.cellsize]
radius = self.config.stone_radius * self.scale
margin = self.config.stone_margin * self.scale
thickness = self.config.thickness * self.scale
mark_thickness = self.config.mark_thickness * self.scale
color = self.config.engrave_color
for x in range(self.config.size[0]):
for y in range(self.config.size[1]):
# pos(recto_x, recto_y, verso_x, verso_y)
pos = (
borders[3]+x*cellsize[0]+radius+margin,
borders[0]+y*cellsize[1]+radius+margin,
self.total_size[0]-borders[3]-x*cellsize[0]-radius-margin,
borders[0]+y*cellsize[1]+radius+margin
)
# Draw grid on recto
if x > 0:
self.recto_drawing.append(
draw.Line(
pos[0]-radius-margin,
pos[1],
pos[0]-cellsize[0]+radius+margin,
pos[1],
stroke=color,
stroke_width=thickness
)
)
if y > 0:
self.recto_drawing.append(
draw.Line(
pos[0],
pos[1]-radius-margin,
pos[0],
pos[1]-cellsize[1]+radius+margin,
stroke=color,
stroke_width=thickness
)
)
# Draw engraving on stones (recto and verso)
if engraved_stones < self.config.engraved_stones:
engraved_radius = mark_thickness
while engraved_radius < radius:
self.recto_drawing.append(
draw.Circle(
pos[0],
pos[1],
engraved_radius - mark_thickness/2,
fill='none',
stroke=color,
stroke_width=mark_thickness
)
)
self.verso_drawing.append(
draw.Circle(
pos[2],
pos[3],
engraved_radius - mark_thickness/2,
fill='none',
stroke=color,
stroke_width=mark_thickness
)
)
engraved_radius += 2*mark_thickness
engraved_stones += 1
# Draw Hoshi on base
if is_hoshi((x, y), self.config.size):
self.base_drawing.append(
draw.Circle(
pos[0],
pos[1],
radius/3,
fill='none',
stroke=color,
stroke_width=thickness
)
)
def drawCutting(self) -> None:
"""
Draw cutting of stones and goban
"""
if not self.config.cut:
return
borders = [b*self.scale for b in self.config.borders]
cellsize = [s*self.scale for s in self.config.cellsize]
radius = self.config.stone_radius * self.scale
margin = self.config.stone_margin * self.scale
corner_radius = min(borders) / 2
thickness = self.config.cut_thickness * self.scale
color = self.config.cut_color
# Cut corners on recto and base
if self.config.round_corners:
self.recto_drawing.append(
draw.Arc(
corner_radius,
corner_radius,
corner_radius,
180,
270,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.base_drawing.append(
draw.Arc(
corner_radius,
corner_radius,
corner_radius,
180,
270,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.recto_drawing.append(
draw.Arc(
self.total_size[0]-corner_radius,
corner_radius,
corner_radius,
270,
0,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.base_drawing.append(
draw.Arc(
self.total_size[0]-corner_radius,
corner_radius,
corner_radius,
270,
0,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.recto_drawing.append(
draw.Arc(
corner_radius,
self.total_size[1]-corner_radius,
corner_radius,
90,
180,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.base_drawing.append(
draw.Arc(
corner_radius,
self.total_size[1]-corner_radius,
corner_radius,
90,
180,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.recto_drawing.append(
draw.Arc(
self.total_size[0]-corner_radius,
self.total_size[1]-corner_radius,
corner_radius,
0,
90,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
self.base_drawing.append(
draw.Arc(
self.total_size[0]-corner_radius,
self.total_size[1]-corner_radius,
corner_radius,
0,
90,
stroke=color,
stroke_width=thickness,
fill='none'
)
)
# Cut stones on recto and holes on base
for x in range(self.config.size[0]):
for y in range(self.config.size[1]):
# pos(recto_x, recto_y)
pos = (
borders[3]+x*cellsize[0]+radius+margin,
borders[0]+y*cellsize[1]+radius+margin,
)
self.recto_drawing.append(
draw.Circle(
pos[0],
pos[1],
radius+margin,
fill='none',
stroke=color,
stroke_width=thickness
)
)
self.recto_drawing.append(
draw.Arc(
pos[0],
pos[1],
radius,
90+math.acos(0.75)*360/(2*math.pi),
90-math.acos(0.75)*360/(2*math.pi),
fill='none',
stroke=color,
stroke_width=thickness
)
)
self.recto_drawing.append(
draw.Arc(
pos[0],
pos[1]+3*radius/2,
radius,
270-math.acos(0.75)*360/(2*math.pi),
270+math.acos(0.75)*360/(2*math.pi),
fill='none',
stroke=color,
stroke_width=thickness
)
)
self.base_drawing.append(
draw.Circle(
pos[0],
pos[1],
radius/4,
fill='none',
stroke=color,
stroke_width=thickness
)
)
def save(self) -> None:
"""
Save drawing.
"""
self.recto_drawing.saveSvg(self.config.prefix+'recto.svg')
self.verso_drawing.saveSvg(self.config.prefix+'verso.svg')
self.base_drawing.saveSvg(self.config.prefix+'base.svg')
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Create goban and stones for lasercut"
)
parser.add_argument(
'--size', type=int, nargs=2, default=(19, 19),
help="Size of goban (number of stones)", metavar=('X', 'Y')
)
parser.add_argument(
2021-11-14 17:01:51 +01:00
'--cellsize', type=float, nargs=2, default=(15, 15),
2021-11-02 01:47:43 +01:00
help="Size of one cell in mm", metavar=('X', 'Y')
)
parser.add_argument(
2021-11-14 17:01:51 +01:00
'--stone-radius', type=float, default=6.5,
2021-11-02 01:47:43 +01:00
help="Radius of stone in mm"
)
parser.add_argument(
2021-11-14 17:01:51 +01:00
'--stone-margin', type=float, default=0,
2021-11-02 01:47:43 +01:00
help="Margin of stone in mm"
)
parser.add_argument(
2021-11-14 17:01:51 +01:00
'--borders', type=float, nargs=4, default=(5, 5, 5, 5),
2021-11-02 01:47:43 +01:00
help="Borders of goban in mm",
metavar=('UP', 'RIGHT', 'BOTTOM', 'LEFT')
)
parser.add_argument(
'--thickness', type=float, default=2.0,
help="Thickness of grid in mm"
)
parser.add_argument(
'--mark-thickness', type=float, default=1.5,
help="Thickness of mark on stone in mm"
)
parser.add_argument(
'--cut-thickness', type=float, default=0.2,
help="Thickness of cut stroke in mm"
)
parser.add_argument(
'--engrave', default=False, action='store_true',
help='Enable engraving'
)
parser.add_argument(
'--cut', default=False, action='store_true',
help='Enable cutting'
)
parser.add_argument(
'--round-corners', default=False, action='store_true',
help='Round corners for goban'
)
parser.add_argument(
2021-11-05 17:35:34 +01:00
'--engrave-color', type=str, default='#000000',
2021-11-02 01:47:43 +01:00
help="Engrave color in web format #000000 to #ffffff"
)
parser.add_argument(
'--cut-color', type=str, default='#ff0000',
help="Cut color in web format #000000 to #ffffff"
)
parser.add_argument(
'--engraved-stones', type=int, default=181,
help="Number of engraved (black) stones"
)
parser.add_argument(
'--prefix', default='',
help='Prefix for filenames, ' +
'create three files named prefixrecto.svg, prefixverso.svg and '+
'prefixbase.svg'
)
parser.add_argument(
'--resolution', type=int, default=100,
help="Resolution in pixels/mm"
)
conf = parser.parse_args()
goban = GobanDraw(conf)
goban.drawEngraving()
goban.drawCutting()
goban.save()