The new connector shapes improved some things, but they are still not quite satisfactory. There's less jiggle and I might go as far as saying it has changed from an assembly hindrance to more of an annoyance. I don't feel like they'll scatter if I put my hand down too hard, but I still have to be careful dragging the pattern. The main problem to fix is the stretch. The original connectors had a shallow angle that magnified the effect of lost material, while these are designed to keep the gap perpendicular to the direction of stretch. Here's an example with border pieces:
The red oval shows how the old pieces stretch more than 2mm beyond the new ones. The new ones are so close to the expected 12.5cm length I had to persuade myself the pieces were as long as I expected (25mm each). At 0.2mm kerf, the four gaps could stretch out 0.8mm, which is probably what I'm seeing. The ruler isn't precise enough for sub-mm measurement, and the kerf may really be less than 0.2mm (see last post).
The kerf-compensated pieces are shown to the right (clear acrylic) and they have a much tighter fit. I don't know when I'll place another order, but I really would like to have a border that doesn't jiggle at all, even if it means wasting some material and paying for two boundary cuts.
The new connectors on the 2x2 neighborhood tiles are also an improvement in terms of stretch:
The only disappointment is that the asymmetry doesn't prevent joining a tile with another that's been flipped. (See tiles to the right.) The internal cells make this irrelevant. No matter what, you need to leave appropriate cell shapes in between. But it's still a usability issue. I found myself placing some incorrectly. I think I just need to make it thicker at the top with more of a difference between left and right side. These aren't as amenable to kerf compensation because I really want to cut all the pieces together like a jigsaw puzzle.
Finally, the main point of the second order was to get two colors of acrylic. I made the live cells white so I could use the new connectors everywhere, but I could also produce a complementary set that is blue on white.
Unlike last time, I used a script to generate most of the SVG file. I did add the border by hand. I'll post the Python code a little later if I have time.
Here's a python script I used to generate part of the SVG for laser cutting. You can see it's missing the boundary tiles and also won't cut cells completely unless they are surrounded. I added the rest by hand in Inkscape. I am not sure when I'll have a chance to update the script, but here it is if anyone wants to generate these laser cuts themselves.
Code: Select all
import json
TILE_TYPES = ['0001', '0011', '0101', '0111', '1111']
TILE_ROTATION = {}
for tile in TILE_TYPES:
for i in range(len(tile)):
TILE_ROTATION.setdefault(tile[i:] + tile[:i], (tile, (4 - i) % 4 * 90))
SVG_HEAD = '''
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="420mm"
height="297mm"
viewBox="0 0 420 297"
version="1.1"
id="svg8">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1">
'''.strip()
SVG_TAIL = '''
</g>
</svg>
'''.strip()
PATH_FORMAT = '''
<path
id="%s"
transform="translate(%s, %s) rotate(%s)"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="%s" />
'''.strip()
CUT_FORMAT = '''
<path
id="%s"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="%s" />
'''.strip()
PATH = {
'1111': 'm -1.5126,12.5 c -1.91e-4,-0.95599 -0.775357,-1.73099 -1.73168,-1.73116 h -1.44642 c -0.216895,-0.93615 -0.586106,-1.81279 -1.062987,-2.56677 l 1.02267,-1.02267 c 1.555276,-1.6964 -0.752017,-4.00369 -2.448429,-2.44842 l -1.022668,1.02267 c -0.753984,-0.47688 -1.630618,-0.8461 -2.566772,-1.06299 l 2e-6,-1.44642 c -1.72e-4,-0.95632 -0.7751729,-1.73149 -1.7311639,-1.73168 m 24.9999989,-10e-6 c -0.955988,1.9e-4 -1.730991,0.77536 -1.731158,1.73168 v 1.44642 c -0.936158,0.2169 -1.812791,0.58611 -2.566775,1.06299 l -1.022668,-1.02267 c -1.696398,-1.55528 -4.003695,0.75202 -2.448421,2.44843 l 1.022669,1.02267 c -0.476881,0.75398 -0.846093,1.63061 -1.062991,2.56677 h -1.446415 c -0.956322,1.7e-4 -1.731489,0.77517 -1.731682,1.73116 m -9e-6,-25 c 1.91e-4,0.95599 0.775357,1.73099 1.731681,1.73116 h 1.446419 c 0.216896,0.93616 0.586106,1.81279 1.062988,2.56677 l -1.02267,1.02267 c -1.555276,1.6964 0.752016,4.0037 2.44843,2.44842 l 1.022668,-1.02267 c 0.753983,0.47689 1.630617,0.8461 2.566772,1.063 l -2e-6,1.44641 c 1.72e-4,0.95632 0.775173,1.73149 1.731164,1.73169 m -24.9999989,0 c 0.95599,-1.9e-4 1.7309919,-0.775361 1.7311589,-1.73168 v -1.44642 c 0.936157,-0.2169 1.812791,-0.58611 2.566774,-1.06299 l 1.022668,1.02267 c 1.696398,1.55528 4.003695,-0.75202 2.44842,-2.44843 l -1.02267,-1.02267 c 0.476883,-0.75398 0.846094,-1.63061 1.062991,-2.56677 h 1.446416 c 0.956323,-1.7e-4 1.731489,-0.77517 1.731682,-1.73116',
'0111': 'm 3.5,-12.5 c 0,0 -0.06374,1.69814 0.685232,3.44371 l 0.923977,-0.3824 c 0.178221,0.43012 0.393682,0.84396 0.643883,1.23662 l -1.022671,1.02268 c -1.555276,1.69641 0.752023,4.0037 2.44842,2.44868 l 1.023189,-1.02294 c 0.815144,0.50883 1.695945,0.86759 2.566249,1.06352 v 1.44641 c 1.8e-4,0.956299 0.775121,1.731489 1.731161,1.73169 m -25.000006,-1.99264 c 0.836348,-0.006 1.514211,-0.6839 1.514126,-1.52035 v -0.6413 c 0.761728,-0.17148 1.53307,-0.48538 2.246895,-0.93018 l 0.453199,0.45321 c 1.506958,1.36493 3.508079,-0.63618 2.142511,-2.14249 l -0.45321,-0.4532 c 0.444791,-0.71382 0.75869,-1.48517 0.93018,-2.24689 h 0.6413 c 0.836448,9e-5 1.514642,-0.67777 1.520349,-1.51413 m 16.004645,14.01153 c -0.956029,1.9e-4 -1.730978,0.77537 -1.731158,1.73168 v 1.44642 c -0.454541,0.10079 -0.899461,0.24096 -1.329632,0.4191 l -0.382928,-0.92346 c -2.205199,0.91357 -3.95717,2.66572 -4.870501,4.87102 l 0.92346,0.38292 c -0.17815,0.43018 -0.31831,0.8751 -0.4191,1.32963 h -1.44643 c -0.95631,1.8e-4 -1.731483,0.77514 -1.731674,1.73117 m -14.012032,-8.99944 c 0,0 1.698136,-0.0637 3.443711,0.68523 l -0.382399,0.92397 c 0.43012,0.17823 0.84396,0.39369 1.236622,0.64389 l 1.022668,-1.02267 c 1.696413,-1.55528 4.003707,0.75202 2.44869,2.44842 l -1.02294,1.02318 c 0.508825,0.81515 0.867582,1.69595 1.063513,2.56625 h 1.446417 c 0.956291,1.8e-4 1.731481,0.77513 1.731684,1.73117',
'0101': 'm -12.5,3.5 c 1.18195,1.3e-4 2.3523,0.23297 3.44423,0.68532 l -0.3824,0.92397 c 0.43012,0.17825 0.84395,0.3937 1.23661,0.64389 l 1.02268,-1.02319 c 1.6964,-1.555021 4.00371,0.75228 2.44869,2.44882 l -1.02294,1.0228 c 0.25033,0.39276 0.46609,0.80636 0.64443,1.23662 l 0.92397,-0.3824 c 0.45235,1.09194 0.68517,2.2623 0.68521,3.44414 m 7.00937,0 c 2.6e-4,-0.83661 0.67853,-1.51474 1.51515,-1.51465 h 0.6413 c 0.0882,-0.39802 0.21095,-0.78758 0.36691,-1.16427 l -0.92449,-0.38291 c 0.81194,-1.96016 2.36928,-3.5175 4.32944,-4.32945 l 0.38291,0.92449 c 0.37669,-0.15597 0.76625,-0.2787 1.16427,-0.36691 v -0.6413 c -9e-5,-0.83662 0.67805,-1.5149 1.51466,-1.51515 m 0,-7.00988 c -1.18193,-1.3e-4 -2.3523,-0.23297 -3.44423,-0.68532 l 0.3824,-0.92397 c -0.43012,-0.17825 -0.84394,-0.3937 -1.23661,-0.64389 l -1.02268,1.02319 c -1.6964,1.55502 -4.00369,-0.75228 -2.44869,-2.44882 l 1.02294,-1.0228 c -0.25033,-0.39276 -0.46607,-0.80636 -0.64441,-1.23662 l -0.92397,0.382399 c -0.45235,-1.091939 -0.68519,-2.262299 -0.68523,-3.444139 m -7.00938,0 c -2.6e-4,0.83661 -0.67854,1.51474 -1.51516,1.51465 h -0.6413 c -0.0882,0.39802 -0.21094,0.78758 -0.36689,1.16427 l 0.92447,0.38291 c -0.81194,1.96016 -2.36927,3.5175 -4.32943,4.32945 l -0.38291,-0.92449 c -0.37669,0.15597 -0.76625,0.2787 -1.16427,0.36691 v 0.6413 c 9e-5,0.83662 -0.67804,1.5149 -1.51465,1.51515',
# '0001': 'm 12.5,4.5 c -3.402711,0.0332 -6.103479,2.04311 -7.352011,4.93871 l 0.92398,0.38241 c -0.155879,0.37652 -0.278759,0.76638 -0.3669,1.16427 h -0.640789 c -0.83662,-9e-5 -1.514901,0.67804 -1.51516,1.51464 m 8.95088,-16.99996 c -1.050509,4e-5 -2.09075,-0.20682 -3.061321,-0.60876 l 0.382931,-0.92448 c -0.376499,-0.15598 -0.738931,-0.34431 -1.082619,-0.56328 l -0.45321,0.4532 c -1.50631,1.36557 -3.507412,-0.63555 -2.14249,-2.14251 l 0.453199,-0.45321 c -0.218969,-0.34369 -0.4073,-0.70612 -0.563279,-1.08262 l -0.924481,0.38293 c -0.401939,-0.97057 -0.608801,-2.01081 -0.608761,-3.06131 m -8.99994,0 c -0.0332,3.402699 -2.04311,6.10348 -4.93871,7.352 l -0.38241,-0.92398 c -0.376521,0.15588 -0.766379,0.27876 -1.16427,0.3669 v 0.64079 c 9e-5,0.83662 -0.67804,1.5149 -1.514639,1.51516 m 0,7.04395 c 4.605838,-5.29e-4 8.939316,3.59514 8.999448,9.00518',
'0001': 'm 12.5,4.5 c -3.40271,0.0332 -6.14802,2.04311 -7.39656,4.93871 l 0.92398,0.38241 c -0.15587,0.37652 -0.27875,0.76638 -0.3669,1.16427 h -0.60124 c -0.83662,-9e-5 -1.55445,0.67804 -1.5547,1.51464 m 8.99542,-16.99996 c -1.05051,4e-5 -2.09075,-0.20682 -3.06132,-0.60876 l 0.38293,-0.92448 c -0.3765,-0.15598 -0.73893,-0.34431 -1.08262,-0.56328 l -0.45321,0.4532 c -1.50631,1.36557 -3.50741,-0.63555 -2.14249,-2.14251 l 0.4532,-0.45321 c -0.21897,-0.34369 -0.4073,-0.70612 -0.56328,-1.08262 l -0.92448,0.38293 c -0.40194,-0.97057 -0.6088,-2.01081 -0.60876,-3.06131 m -8.99994,0 c -0.0332,3.402699 -2.04311,6.148005 -4.93871,7.396525 l -0.38241,-0.92398 c -0.37652,0.15588 -0.76638,0.27876 -1.16427,0.3669 v 0.64079 c 9e-5,0.83662 -0.67804,1.5149 -1.51464,1.51516 m 0,6.999425 c 4.60584,-5.29e-4 8.93932,3.59514 8.99945,9.00518',
'0011': 'm 12.5,3.5 c -3.84769,0.0464 -6.8972,2.24419 -8.31524,5.55621 l 0.92397,0.3824 c -0.17812,0.43017 -0.31836,0.87508 -0.4191,1.32964 h -1.44694 c -0.9563,1.9e-4 -1.73146,0.77513 -1.73167,1.73116 m 10.98898,-16.00485 c -0.83689,-0.005 -1.51498,-0.68356 -1.51489,-1.52015 1e-5,-0.21375 0,-0.42755 0,-0.64131 -0.81752,-0.18539 -1.5665,-0.49011 -2.24586,-0.93017 l -0.45321,0.4532 c -1.50631,1.36557 -3.50741,-0.63555 -2.14249,-2.14251 l 0.45319,-0.4532 c -0.21889,-0.34355 -0.40736,-0.70577 -0.56327,-1.0821 l -0.92397,0.38292 c -0.40203,-0.97073 -0.60888,-2.01115 -0.60874,-3.06183 m -17.00077,16.00059 c 3.8477,0.0464 6.89721,2.24419 8.31525,5.55621 l -0.92397,0.3824 c 0.17812,0.43017 0.31836,0.87508 0.41909,1.32964 h 1.44695 c 0.9563,1.9e-4 1.73147,0.77513 1.73168,1.73116 m -10.989,-16.00485 c 0.83691,-0.005 1.51499,-0.68356 1.5149,-1.52015 -1e-5,-0.21375 -1e-5,-0.42755 -1e-5,-0.64131 0.81752,-0.18539 1.56651,-0.49011 2.24587,-0.93017 l 0.45321,0.4532 c 1.5063,1.36557 3.50741,-0.63555 2.14249,-2.14251 l -0.45319,-0.4532 c 0.21889,-0.34355 0.40734,-0.70577 0.56326,-1.0821 l 0.92397,0.38292 c 0.40203,-0.97073 0.60887,-2.01115 0.60875,-3.06183'
}
JIGSAW_CUT = "m -3.5,-12.5 h 2.348779 v -2.787939 h -0.695651 v -1.290354 c 0.03328,-1.059009 4.111292,-1.236928 4.167187,0.235643 v 1.054711 h -0.960535 v 2.787939 h 2.14022"
JIGSAW_CUT = "m 3.5,-12.5 -2.481144,0 v -2.444102 h 0.827943 v -1.63431 c -0.06569,-0.961777 -4.308405,-0.374402 -4.299479,0.579599 v 1.054711 h 0.960535 v 2.444102 l -2.007855,0"
LIVE_CELL_SEG = 5.9291
LIVE_CELL_AX = -11.535541
LIVE_CELL_3_CUT = "m %s,0 %s,0 c 6e-6,1.279634 -0.488184,2.558897 -1.464495,3.535202 1.95261,1.952621 5.118255,1.952609 7.070877,10e-7 -0.976303,-0.976312 -1.464505,-2.255561 -1.464504,-3.53518 l %s 0"
LIVE_CELL_2_CUT = "m %s,0 h %s m 4.181362,0 h %s m %s,3.535529 c 1.952618,-1.952618 1.952619,-5.118443 8e-6,-7.071067 1.952618,-1.952619 5.118437,-1.952611 7.071063,3e-6 -1.952626,1.952622 -1.952609,5.118439 1.1e-5,7.071059 -1.952628,1.952633 -5.118458,1.952634 -7.071082,5e-6 z"
def windows(rows):
return filter(lambda tup: tup[2] != '0000',
[(i, j, rows[i][j] + rows[i][j + 1] + rows[i + 1][j + 1] + rows[i + 1][j])
for i in range(len(rows) - 1) for j in range(len(rows[0]) - 1)])
def cellvalue(rows, i, j):
try:
return int(rows[i][j])
except IndexError:
return 0
def neighborhoods(rows):
res = []
for i in range(len(rows)):
for j in range(len(rows[i])):
neighborhood = (cellvalue(rows, i - 1, j - 1),
cellvalue(rows, i - 1, j ),
cellvalue(rows, i - 1, j + 1),
cellvalue(rows, i , j + 1),
cellvalue(rows, i + 1, j + 1),
cellvalue(rows, i + 1, j ),
cellvalue(rows, i + 1, j - 1),
cellvalue(rows, i , j - 1))
if sum(neighborhood):
res.append((i, j, cellvalue(rows, i, j), neighborhood))
return res
def match_neighbors(neighborhood, pattern):
as_string = "".join(str(v) for v in neighborhood + neighborhood)
return as_string.index(pattern) if pattern in as_string else None
def split_neighborhood(neighborhood):
for i in range(4):
shift = neighborhood[i:] + neighborhood[:i]
yield i, (shift[:4], shift[4:])
for i in range(8):
shift = neighborhood[i:] + neighborhood[:i]
yield i, (shift[:4], shift[4:6], shift[6:])
for i in range(2):
shift = neighborhood[i:] + neighborhood[:i]
yield i, (shift[:2], shift[2:4], shift[4:6], shift[6:])
def crowding_tiles(split):
return all(set([tup, tup[::-1]]).intersection([(0, 0, 1, 1), (0, 1), (1, 1)]) for tup in split[1])
def survive_tiles(split):
return all(set([tup, tup[::-1]]).intersection([(0, 0, 0, 1), (0, 0, 1, 0), (0, 0, 1, 1), (0, 1, 0, 1)]) for tup in split[1])
def divide_cell(cell):
tx, ty = (cell[1] + 0.5) * 25, (cell[0] + 0.5) * 25
nghcells = cell[3]
if sum(nghcells) >= 4:
split = list(filter(crowding_tiles, split_neighborhood(nghcells)))[0]
# long split of crowded cell
cellpos = split[0]
rotation = 22.5 + 45 * cellpos
start = -7
length = 14
if nghcells[cellpos - 1] + nghcells[cellpos] == 0:
start -= 1
length += 1
if nghcells[(cellpos + 3) % 8] + nghcells[(cellpos + 4) % 8] == 0:
length += 1
print(PATH_FORMAT % ('cell_a_%d_%d_%d' % (cell[0], cell[1], cell[2]), tx, ty, rotation,
'm %d 0 l %d 0' % (start, length)))
# short (or double) split of crowded cell
if len(split[1]) >= 3:
cellpos = (split[0] + 6) % 8
rotation = 22.5 + 45 * cellpos
start = -7
length = 7
if nghcells[cellpos - 1] + nghcells[cellpos] == 0:
start -= 1
length += 1
if len(split[1]) == 4:
length += 7
if nghcells[(cellpos + 3) % 8] + nghcells[(cellpos + 4) % 8] == 0:
length += 1
print(PATH_FORMAT % ('cell_b_%d_%d_%d' % (cell[0], cell[1], cell[2]), tx, ty, rotation,
'm %d 0 l %d 0' % (start, length)))
elif cell[2] and sum(nghcells) in [2,3]:
split = list(filter(survive_tiles, split_neighborhood(nghcells)))[0]
# cell split
cellpos = split[0]
if sum(split[1][1]) == 2:
cellpos = (cellpos + 4) % 8
rotation = 22.5 + 45 * cellpos
start = -8
length1 = LIVE_CELL_SEG
if nghcells[cellpos - 1] + nghcells[cellpos] == 0:
start -= 1
length1 += 1
length2 = LIVE_CELL_SEG
ax = LIVE_CELL_AX
if nghcells[(cellpos + 3) % 8] + nghcells[(cellpos + 4) % 8] == 0:
length2 += 1
ax -= 1
path = (LIVE_CELL_2_CUT % (start, length1, length2, ax) if sum(nghcells) == 2
else LIVE_CELL_3_CUT % (start, length1, length2))
print(PATH_FORMAT % ('cell_%d_%d_%d' % (cell[0], cell[1], cell[2]), tx, ty, rotation, path))
ROTATION = {
0: lambda x, y: (x, y),
1: lambda x, y: (-y, x),
2: lambda x, y: (-x, -y),
3: lambda x, y: (y, -x)
}
CUTS = {
'0': {
True: (-4.5, 4.5),
False: (-3.5, 3.5),
},
'1': {
True: (-1.5126, 1.5126),
False: (-3.5, 3.5),
}
}
PATTERN = '''
...........
....**.....
....*.*.**.
......*.**.
..*...*....
.*.*.**....
..*........
...........
'''
rows = PATTERN.replace('.', '0').replace('*', '1').strip().split('\n')
cut_ids = set()
print(SVG_HEAD)
for i, j, tile in windows(rows):
normtile, rotation = TILE_ROTATION[tile]
tx, ty = (j + 1) * 25, (i + 1) * 25
print(PATH_FORMAT % (tile + '_%d_%d' % (i, j), tx, ty, rotation, PATH[normtile]))
for k in range(len(tile)):
from_val = tile[k]
to_val = tile[(k + 1) % 4]
cut_from, cut_to = CUTS[from_val][from_val == to_val]
mx, my = ROTATION[k](cut_from, -12.5)
lx, ly = ROTATION[k](cut_to - cut_from, 0)
cut_id = 'cut_%d_%d_%d' % ((i + 1) if k == 2 else i, (j + 1) if k == 1 else j, 3 * (k % 2))
if from_val == '0' and to_val == '1':
print(PATH_FORMAT % (cut_id, tx, ty, k * 90, JIGSAW_CUT))
elif cut_id not in cut_ids and from_val == to_val:
cut_ids.add(cut_id)
print(CUT_FORMAT % (cut_id, "m %f %f l %f %f") % (tx + mx, ty + my, lx, ly))
for cell in neighborhoods(rows):
divide_cell(cell)
print(SVG_TAIL)