#!/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( '--cellsize', type=float, nargs=2, default=(15, 15), help="Size of one cell in mm", metavar=('X', 'Y') ) parser.add_argument( '--stone-radius', type=float, default=6.5, help="Radius of stone in mm" ) parser.add_argument( '--stone-margin', type=float, default=0, help="Margin of stone in mm" ) parser.add_argument( '--borders', type=float, nargs=4, default=(5, 5, 5, 5), 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( '--engrave-color', type=str, default='#000000', 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()