function layerByName(layers,name) { var i; for(i = 0; i < layers.length; i++) { var layer = layers[i]; if(layer.name == name) return layer; } return null; } function newLayer(doc,baseName) { var n = 0; var result = null; while(result == null) { n++; var aName = "" + baseName + n; var existingLayer = layerByName(doc.layers,aName); if(existingLayer == null) { result = doc.layers.add(); result.name = aName; } } return result; } function addP(points,x,y) { var p = points.add(); p.anchor = [x,y]; p.leftDirection = p.anchor; p.rightDirection = p.anchor; } var pattern= "abccccccddeeeee/" + "abbbbaddddbaaeb/" + "aaccbaaaabbaaab/" + "acccdaccbbbabbb/" + "acbddddcdddcccb/" + "bbbdacccdddcacc/" + "bcbaa.....eaaaa/" + "cccaa.....ebbac/" + "cbbad.....ebccc/" + "cbddddaaeeebcab/" + "bbcbdaaaaccbcab/" + "bccbbddcccdbaab/" + "ccbbddabbcddabb/" + "cabadaaabbdcabc/" + "aaaadaabbddcccc"; //~ pattern = "aaab/aabb"; apattern="jjjjppppuuuuzzzzffffllllvvvvmmccssssmmmmiiiihhhh/" + "jjjzppphuuukzzzqfffalllcvvvmmmcccsssbmmmviiiqhhh/" + "kjzzzphhhukkkzqqqfaaalcccvffmmcchhsbbbmvvviqqqhg/" + "kkzzzzhhhhkkkkqqqqaaaaccccfffmchhhbbbbvvvvqqqqgg/" + "kkkeqqddddggggkkkkbbbbddddffrrrrhhwwwwtttthhvggg/" + "kkeeqqqdddpgggvkkkbbbpdddxflrrrbbhwwwgttthhhvvgg/" + "leeeqqjjdpppgvvvkggbpppdxxxllrbbbrrwgggtzzhhvvvf/" + "lleeqjjjppppvvvvgggppppxxxxlllqbbrrrggggzzzhvvff/" + "lllfrrjjbbbbaaaalggqqqqwwwwllqqgbrrvllllzzggufff/" + "llffrrrjbbblaaalllgvqqqzwwweqqqggrvvvlllzggguuff/" + "mfffrrkkcblllallllvvvqzzzweeeqqgggvvvvlssfgguuue/" + "mmffrkkkccllllwwhhvvvvzzzzeeeewgglaaaasssffguuee/" + "mmmgwwkkcccqbwwwhhhcmffffbbbbwwwlllaaakssfffpeee/" + "mmggwwwkccqqbbwwhhccmmfffbbbwwwwllllakkksffpppee/" + "ngggwwxeeqqqbbbwhcccmmmfoobhvvvvqqqqkkkkbbppppdd/" + "nnggwxxeeeqqbbrrrrccmmgooohhhvvvfqqqffffbbbooddd/" + "nnnaxxxeerrrrcrrrwwwwgggoohhhhvfffqufffjbbaooodd/" + "nnaaaxxevrrrcccrmmwwwggggoggggffffuuufjjbaaooccd/" + "ooaaaallvvrccccmmmxwnnnnuugggaaaauuuujjjaaaolccc/" + "oooyylllvvvmxxxxmmxxnnnhuuugkkaaakzzzzjjyaallccb/" + "oopyyyllvvmmmxxxnmxxxnhhuuakkkzakkkzzziiyylllcbb/" + "oppyynnlwwmmmmxnnnxxthhhuaaakkzzkkkkziiiyyyllbbb/" + "pppybnnnwwwhhhhnnnnttthhaaaajkzzzppppeiiyynnnnbb/" + "qppbbnnowwshhhssssttttbttttjjjzzepppeeeirkknnnaa/" + "qqbbbnoowssshdsssiuuuubbtttjjjjeeepeeeerrkkknaaa/" + "qqqbbooossssdddsiiiuuubbbtzzppeeeeooddrrrkkjttaa/" + "qqccccooxxxxddddiiiiuobbszzzpppujooodddrrkjjttta/" + "rrcccssfxxxnyyyyjjjjooossszzppuujjooddppxjjjttzz/" + "rrrcsssffxnnnyyyjjjoooosssszpuuujjjodpppxxjjtzzz/" + "rrhhtssfffnnnnyttjzzzzrrrriiiiuujjyyyyppxxxesszz/" + "rhhhttsffyiiiitttddzzzjrrrdiiiyyyynyyyopxxeesssz/" + "sshhtttmyyyiiiettdddzjjjrdddityyynnnyoooweeessyy/" + "ssshttmmyyyyieeotddyyjjjjddddttynnnnoooowweesyyy/" + "ssiiummmtttteeeoodyyykkkkcccctttiiiiccccwwwdrryy/" + "siiiuummgtttjeeoooeyykkkqcccottxiiitcccqwwddrrry/" + "ttiiuuugggtjjjuooeeeyskqqqcoooxxxitttcqqqdddrrxx/" + "tttiuuggggjjjjuueeeessqqqqooooxxxxttttqqqqddrxxx/" + "ttddddiiiioooouuupzsssppppnnnnddddxxxxuuuummmmxx/" + "tudddviiizooofuuppzzssipppynnnsdddmxxxhuuucmmmwx/" + "uuudvvvizzzofffpppzzziiipyyynsssdmmmxhhhucccmwww/" + "uuuuvvvvzzzzffffppzziiiiyyyyssssmmmmhhhhccccwwww"; var g = {}; function scanPattern() // break main string apart, and notate g.x g.y extents. { g.parts = pattern.split("/"); g.x = 0; g.y = g.parts.length; g.segments = []; // unit segments segment.[p0, p1].[x,y] g.segmentRuns = []; // each run is an array of points // find the widest for(var ix = 0; ix < g.y; ix++) { var w = g.parts[ix].length; if(w > g.x) g.x = w; } g.parts.reverse(); } function getPPixel(x, y) { var p; if(y < 0 || y >= g.y) { p = '.'; } else { var row = g.parts[y]; if(x < 0 || x >= row.length) { p = '.'; } else { p = row[x]; } } return p; } function setPPixel(x,y, c) { var gg = g; if(y < 0 || y >= gg.y) return; var yRow = gg.parts[y]; if(x < 0 || x >= yRow.length) return; yRow = yRow.substr(0, x) + c + yRow.substr(x+1); g.parts[y] = yRow; } function point(x,y) { var point = {}; point.x = x; point.y = y; return point; } function buildUnitSegmentsList() { // scan all the pixels, and build the Big Bag of Unit-length segments. // g.segments[] is an array of objects, each segment.p0[2], segment.p1[2] for(var y = 0; y <= g.y; y++) { for(x = 0; x < g.x; x++) { var kAbove = getPPixel(x,y-1); var kBelow = getPPixel(x,y); if(kAbove != kBelow) { // one segment. var segment = {}; segment.p0 = point(x,y); segment.p1 = point(x+1,y); g.segments.push(segment); } } } for(var x = 0; x <= g.x; x++) { for(y = 0; y < g.y; y++) { var kAbove = getPPixel(x-1,y); var kBelow = getPPixel(x,y); if(kAbove != kBelow) { // one segment. var segment = {}; segment.p0 = point(x,y); segment.p1 = point(x,y+1); g.segments.push(segment); } } } } function pullSegment(ix) { var gg = g; var segment = gg.segments.splice(ix, 1)[0]; return segment; } function peq(pA, pB) { if(pA.x == pB.x && pA.y == pB.y) return true; else return false; } function sign(a) { if(a < 0) return -1 if(a > 0) return +1; return 0; } function tryToExtendSegment(p0, p1) { // look for a segment (p1, p2) which goes in exact same direction as (p0,p1). // (might be the opposite direction of how we recorded it) var gg = g; var p2 = {}; p2.x = p1.x + sign(p1.x - p0.x); p2.y = p1.y + sign(p1.y - p0.y); for(var ix = 0; ix < g.segments.length; ix++) { var segment = g.segments[ix]; var matchF = peq(p1, segment.p0) && peq(p2, segment.p1); var matchB = peq(p1, segment.p1) && peq(p2, segment.p0); if(matchF || matchB) { pullSegment(ix); return p2; } } return null; } function tryToConnect(p0) { var gg = g; var result = {}; for(var ix = 0; ix < gg.segments.length; ix++) { var segment = gg.segments[ix]; if(peq(p0, segment.p0)) { pullSegment(ix); return segment.p1; } if(peq(p0, segment.p1)) { pullSegment(ix); return segment.p0; } } return null; } function pullSegmentRun(firstSegment) { // return an array of points which are a greedily-found set of connected lines // of the pattern. var result = []; result.push(firstSegment.p0); var previousPoint = firstSegment.p0; var currentPoint = firstSegment.p1; while(true) { // find a segment that extends the current one, if possible var nextPoint = tryToExtendSegment(previousPoint, currentPoint); // removes it from g.segments, if found. if(nextPoint) { previousPoint = currentPoint; currentPoint = nextPoint; } else { // no next segment along straight path. Wrap up this vector... result.push(currentPoint); // and see if there's another we can connect to (in any direction) var nextPoint = tryToConnect(currentPoint); if(!nextPoint) break; // we did! previousPoint = currentPoint; currentPoint = nextPoint; } } // also work backwards... while(true) { currentPoint = result[0]; previousPoint = result[1]; var nextPoint = tryToExtendSegment(previousPoint, currentPoint); if(nextPoint) { result[0] = nextPoint; } else { var nextPoint = tryToConnect(currentPoint); if(!nextPoint) break; result.splice(0, 0, nextPoint); } } return result; } function buildLongSegments() { var result = []; // given an array of unit segments, return an array of multi-unit segments // destroys the input g.segments. var gg = g; while(gg.segments.length > 0) { var longSegment = pullSegment(0); while(true) { var nextPoint = tryToExtendSegment(longSegment.p0, longSegment.p1); if(nextPoint) longSegment.p1 = nextPoint; else break; } while(true) { var nextPoint = tryToExtendSegment(longSegment.p1, longSegment.p0); if(nextPoint) longSegment.p0 = nextPoint; else break; } result.push(longSegment); } return result; } function floodShape(x, y, newChar, oldChar) { if(x < 0 || x >= g.x || y < 0 || y >= g.y) return; if(!oldChar) oldChar = getPPixel(x,y); var cNow = getPPixel(x,y); if(cNow != oldChar) return; setPPixel(x,y,newChar); floodShape(x-1,y, newChar, oldChar); floodShape(x+1,y, newChar, oldChar); floodShape(x,y-1, newChar, oldChar); floodShape(x,y+1, newChar, oldChar); } function addHolesPerShape(cx, cy, s, holeSize, justOneHolePerShape) { var layer = newLayer(app.documents[0],"holes_"); var gg = g; for(var y = gg.y - 1; y >= 0; y--) { for(var x = 0; x < gg.x; x++) { var c = getPPixel(x,y); if(c != '.') { var pathItem = layer.pathItems.add(); pathItem.filled = false; pathItem.closed = true; var points = pathItem.pathPoints; // brand new, empty var hR = holeSize / 2; var hCx = cx + x * s + s/2; var hCy = cy + y * s + s/2; addP(points, hCx - hR, hCy - hR); addP(points, hCx - hR, hCy + hR); addP(points, hCx + hR, hCy + hR); addP(points, hCx + hR, hCy - hR); if(justOneHolePerShape) floodShape(x, y, '.'); } } } } function main() { var docRef = documents[0]; var docHeight = docRef.height; var docWidth = docRef.width; // default values var cx = 0; var cy = -docHeight; var gg = g; var layer = newLayer(app.documents[0],"blips_"); scanPattern(); buildUnitSegmentsList(); var s = 72; g.segments = buildLongSegments(); // walk segments until none remain while(g.segments.length > 0) { var segment = pullSegment(0); var segmentRun = pullSegmentRun(segment); g.segmentRuns.push(segmentRun); } // add segments... for(var ix = 0; ix < g.segmentRuns.length; ix++) { var pathItem = layer.pathItems.add(); pathItem.filled = false; pathItem.closed = false; var points = pathItem.pathPoints; // brand new, empty var segmentRun = g.segmentRuns[ix]; if(peq(segmentRun[0], segmentRun[segmentRun.length - 1])) { // first and last meet, make it a closed vector. segmentRun.splice(segmentRun.length - 1, 1); pathItem.closed = true; } for(var px = 0; px < segmentRun.length; px++) { var p = segmentRun[px]; addP(points, cx + p.x * s, cy + p.y * s); } } addHolesPerShape(cx, cy, s, s * 0.25, true); } main();