Skip to content

Instantly share code, notes, and snippets.

@ednisley
Created August 6, 2025 23:07
Show Gist options
  • Save ednisley/e662201392ae0e4be0847f4fdb538189 to your computer and use it in GitHub Desktop.
Save ednisley/e662201392ae0e4be0847f4fdb538189 to your computer and use it in GitHub Desktop.
Python source code: SVG generator for laser-cut layered paper
# Generator for rainbow block layered paper
# Ed Nisley - KE4ZNU
# 2025-08-03 cargo-culted from svg library examples
import svg
import math
from argparse import ArgumentParser
from random import randint, choice, seed
from itertools import chain
from pprint import pprint
INCH = 25.4
X = 0
Y = 1
def as_mm(number):
return repr(number) + "mm"
parser = ArgumentParser()
parser.add_argument('--layernum', type=int, default=0)
parser.add_argument('--colors', type=int, default=16)
parser.add_argument('--seed', type=int, default=1)
parser.add_argument('--width', type=int, default=16)
parser.add_argument('--height', type=int, default=16)
parser.add_argument('--debug', default=False)
args = parser.parse_args()
PageSize = (round(8.5*INCH,3), round(11.0*INCH,3))
SheetCenter = (PageSize[X]/2,PageSize[X]/2) # symmetric on Y!
SheetSize = (200,200) # overall sheet
AlignOC = (180,180) # alignment pins in corners
AlignOD = 5.0 # … pin diameter
MatrixOA = (170,170) # outer limit of cell matrix
CellCut = "black" # C00 Black
SheetCut = "red" # C02 Red
HeavyCut = "rgb(255,128,0)" # C05 Orange black mask paper is harder
HeavyCellCut = "rgb(0,0,160)" # C09 Dark Blue ditto
Tooling = "rgb(12,150,217)" # T2 Tool
DefStroke = "0.2mm"
DefFill = "none"
ThisLayer = args.layernum # determines which cells get cut
Layers = args.colors # black mask = 0, color n = not perforated
SashWidth = 1.5 # between adjacent cells
CellSize = ((MatrixOA[X] - (args.width - 1)*SashWidth)/args.width,
(MatrixOA[Y] - (args.height - 1)*SashWidth)/args.height)
CellOC = (CellSize[X] + SashWidth,CellSize[Y] + SashWidth)
if args.seed:
seed(args.seed)
#--- accumulate tooling layout
ToolEls = []
# mark center of sheet for drag-n-drop location
ToolEls.append(
svg.Circle(
cx=SheetCenter[X],
cy=SheetCenter[Y],
r="2mm",
stroke=Tooling,
stroke_width=DefStroke,
fill="none",
)
)
# mark page perimeter for alignment check
if False:
ToolEls.append(
svg.Rect(
x=0,
y=0,
width=as_mm(PageSize[X]),
height=as_mm(PageSize[Y]),
stroke=Tooling,
stroke_width=DefStroke,
fill="none",
)
)
# center huge box on matrix center
if False:
ToolEls.append(
svg.Rect(
x=as_mm(SheetCenter[X] - 2*SheetSize[X]/2),
y=as_mm(SheetCenter[Y] - 2*SheetSize[Y]/2),
width=as_mm(2*SheetSize[X]),
height=as_mm(2*SheetSize[Y]),
stroke=Tooling,
stroke_width=DefStroke,
fill="none",
)
)
#--- accumulate sheet cuts
SheetEls = []
# cut perimeter
SheetEls.append(
svg.Rect(
x=as_mm(SheetCenter[X] - SheetSize[X]/2),
y=as_mm(SheetCenter[Y] - SheetSize[Y]/2),
width=as_mm(SheetSize[X]),
height=as_mm(SheetSize[Y]),
stroke=SheetCut if ThisLayer > 0 else HeavyCut,
stroke_width=DefStroke,
fill="none",
),
)
# cut layer ID holes except on mask layer
if ThisLayer > 0:
c = ((1,1))
h = f'{ThisLayer:0{Layers.bit_length()}b}'
for i in range(Layers.bit_length()):
SheetEls.append(
svg.Circle(
cx=as_mm(SheetCenter[X] + c[X]*AlignOC[X]/2 - (i + 2)*AlignOD),
cy=as_mm(SheetCenter[Y] + c[Y]*AlignOC[Y]/2),
r=AlignOD/4 if h[-(i + 1)] == '1' else AlignOD/8,
stroke=SheetCut,
stroke_width=DefStroke,
fill="none",
)
)
# cut alignment pin holes except on mask layer
if ThisLayer > 0:
for c in ((1,1),(-1,1),(-1,-1),(1,-1)):
SheetEls.append(
svg.Circle(
cx=as_mm(SheetCenter[X] + c[X]*AlignOC[X]/2),
cy=as_mm(SheetCenter[Y] + c[Y]*AlignOC[Y]/2),
r=as_mm(AlignOD/2),
stroke=SheetCut,
stroke_width=DefStroke,
fill="none",
)
)
#--- calculate matrix contents
CenterPoint = (choice(range(args.width)),choice(range(args.height)))
CellMatrix = [[math.hypot(x - CenterPoint[X],y - CenterPoint[Y])
for y in range(args.height)]
for x in range(args.width)]
dmax = max(list(chain.from_iterable(CellMatrix)))
if args.debug:
print(CenterPoint)
print(dmax)
pprint(CellMatrix)
print()
#--- accumulate matrix cuts
LayerThreshold = (ThisLayer/Layers)*dmax
if args.debug:
print(LayerThreshold)
MatrixEls = []
for i in range(args.width):
x =i*CellOC[X]
for j in range(args.height):
y = j*CellOC[Y]
if args.debug:
print(i)
print(j)
print(CellMatrix[i][j])
if ThisLayer == 0: # black mask
s = HeavyCellCut
elif LayerThreshold < CellMatrix[i][j]: # rest of sheets above color layer
s = CellCut
else:
s = Tooling # at or below color layer
MatrixEls.append(
svg.Rect(
x=as_mm(SheetCenter[X] - MatrixOA[X]/2 + x),
y=as_mm(SheetCenter[Y] - MatrixOA[Y]/2 + y),
width=as_mm(CellSize[X]),
height=as_mm(CellSize[Y]),
stroke=s,
stroke_width=DefStroke,
fill="none",
)
)
#--- assemble and blurt out the SVG file
if not args.debug:
canvas = svg.SVG(
width=as_mm(PageSize[X]),
height=as_mm(PageSize[Y]),
elements=[
ToolEls,
SheetEls,
MatrixEls
],
)
print(canvas)
@ednisley
Copy link
Author

ednisley commented Aug 6, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment