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()
|