From 989cbdbcba561f72b4c2b621013aef1142d960ba Mon Sep 17 00:00:00 2001 From: branchwelder Date: Sun, 27 Feb 2022 18:45:33 -0800 Subject: [PATCH] Working on l-system embroideries --- .vscode/settings.json | 7 +- convo.svg | 1 + embroiderTurtle.py | 91 +++++++++++++++++++ embryoid.py | 22 ++++- hilbert.pes | Bin 0 -> 7249 bytes hilbert.svg | 1 + l-system.py | 203 ++++++++++++++++++++++++++++++++++++++++++ linger_longer.pes | Bin 0 -> 5168 bytes linger_longer.svg | 1 + tests_input/convo.svg | 43 +++++++++ tests_pes/convo.pes | Bin 0 -> 5249 bytes 11 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 convo.svg create mode 100644 embroiderTurtle.py create mode 100644 hilbert.pes create mode 100644 hilbert.svg create mode 100644 l-system.py create mode 100644 linger_longer.pes create mode 100644 linger_longer.svg create mode 100644 tests_input/convo.svg create mode 100644 tests_pes/convo.pes diff --git a/.vscode/settings.json b/.vscode/settings.json index 01ee7a8..2844c0e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "terminal.integrated.cwd": "${workspaceFolder}" + "terminal.integrated.cwd": "${workspaceFolder}", + "cSpell.words": [ + "pyplot", + "xlabel", + "ylabel" + ] } \ No newline at end of file diff --git a/convo.svg b/convo.svg new file mode 100644 index 0000000..8057a21 --- /dev/null +++ b/convo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/embroiderTurtle.py b/embroiderTurtle.py new file mode 100644 index 0000000..dae4d45 --- /dev/null +++ b/embroiderTurtle.py @@ -0,0 +1,91 @@ +import matplotlib.pyplot as plt +from math import pi, sin, cos, isnan + +DEGREES_TO_RADIANS = pi / 180 + + +def print_coords(coords): + for (x, y) in coords: + if isnan(x): + print("") + else: + print("({:.2f}, {:.2f})".format(x, y)) + + +def turtle_to_coords(turtle_program, turn_amount=45): + # The state variable tracks the current location and angle of the turtle. + # The turtle starts at (0, 0) facing up (90 degrees). + state = (0.0, 0.0, 90.0) + + # Throughout the turtle's journey, we "yield" its location. These coordinate + # pairs become the path that plot_coords draws. + yield (0.0, 0.0) + + # Loop over the program, one character at a time. + for command in turtle_program: + x, y, angle = state + + if command in "Ff": # Move turtle forward + state = ( + x - cos(angle * DEGREES_TO_RADIANS), + y + sin(angle * DEGREES_TO_RADIANS), + angle, + ) + + if command == "f": + # Insert a break in the path so that + # this line segment isn't drawn. + yield (float("nan"), float("nan")) + + yield (state[0], state[1]) + + elif command == "+": # Turn turtle clockwise without moving + state = (x, y, angle + turn_amount) + + elif command == "-": # Turn turtle counter-clockwise without moving + state = (x, y, angle - turn_amount) + + # Note: We silently ignore unknown commands + + +def plot_coords(coords, bare_plot=False): + if bare_plot: + # Turns off the axis markers. + plt.axis("off") + # Ensures equal aspect ratio. + plt.axes().set_aspect("equal", "datalim") + # Converts a list of coordinates into + # lists of X and Y values, respectively. + X, Y = zip(*coords) + # Draws the plot. + plt.plot(X, Y) + + +def transform_sequence(sequence, transformations): + return "".join(transformations.get(c, c) for c in sequence) + + +def transform_multiple(sequence, transformations, iterations): + for _ in range(iterations): + sequence = transform_sequence(sequence, transformations) + return sequence + + +def hilbert(): + return turtle_to_coords( + transform_multiple("L", {"L": "-RF+LFL+FR-", "R": "+LF-RFR-FL+"}, 5), 90 + ) + + +if __name__ == "__main__": + plt.style.use("bmh") # Use some nicer default colors + plt.xlabel("x") + plt.ylabel("y") + + plot_coords( + turtle_to_coords( + transform_multiple("L", {"L": "-RF+LFL+FR-", "R": "+LF-RFR-FL+"}, 5), 90 + ) + ) + + plt.show() diff --git a/embryoid.py b/embryoid.py index 97f3a0f..3259b68 100644 --- a/embryoid.py +++ b/embryoid.py @@ -22,8 +22,15 @@ class Embryoid: def add_stitch_block(self, block): self.pattern.add_block(block) + def block_from_coords(self, coords): + block = [] + for coord in coords: + block.append((coord[0], coord[1])) + self.pattern.add_block(block, "teal") + def parse_svg(self, fname): paths, attributes = svg2paths(fname) + print(paths) print(attributes) for path in paths: block = [] @@ -43,15 +50,22 @@ def solid_block(x_len=100, y_len=100, num_stitches=20): return stitches -def parse(fname): +def parse(fname, outname): e = Embryoid() e.parse_svg(INPUT_SVG + fname) - e.save_svg("linger_longer.svg") - e.save_pes("linger_longer.pes") + e.save_svg(outname + ".svg") + e.save_pes(outname + ".pes") if __name__ == "__main__": - parse("linger_longer_audioplot.svg") + from embroiderTurtle import hilbert + + e = Embryoid() + e.block_from_coords([*hilbert()]) + e.save_pes("hilbert.pes") + e.save_svg("hilbert.svg") + print(*hilbert()) + # parse("convo.svg", "convo") # e = Embryoid() # e.add_stitch_block(solid_block()) # e.save_svg("block_test.svg") diff --git a/hilbert.pes b/hilbert.pes new file mode 100644 index 0000000000000000000000000000000000000000..170dcd23dee55d60a0a7c66b5a082a8d65e02faf GIT binary patch literal 7249 zcmeI$OOG5^8He%XTQW(oGfsj-jEw_Gteq_z2qZfskSx9c!a_)pRRq}}qbQ4f9KI3h z?&^E>y{hlkhv%uDN>BST6D!IBDP#R+{%`gB)|^vQJ?@^L|Ka0D4Zu9W{` z4B^%A+mHYLr{90_I6U+5?_V7bk>CHvN8zh~eRQQf_#iy^`oSyV>_m?q|MSt~zl3lt zgzFDNE&ilbj}L+xNI0fOykAPFd-zeQPF90j2|1xh z4d#@fi5ADyq^C)*T4c4!>X1A1sLhNztf9kt)F!KiCbe4ZK~46g7TVP6kkv(xT3u$k z%&SLMpRAOe(xW~zN-?0;kgO5L)EY6PG4mQ>NUZ^Rz#azdM?f)JmC2nOC2z9$8&-mmc+)Q8&I@>cuHHZPwBZ`8jzKu&oQOUq|B>NR*$SM zxl50F%&3bFwc2F0(4a(B-CoK2PN!D4b-VsBiG`arFy)@v0LPu_(7>o zR)bmzIiW`l=9HjGtrl5rbg0#4MjhtWMvGcaax=bx*Gnz3+URgh?f6=$6Hlm5$S3r? zR%()!pusUE%p}aKK~|lt8o5S~>ddGXU&Sk>I#~@Q98)9SC?)Ya^>y-fdTx|ziDwyGp)-P^y#FpjJXo=uv|?C1|3>F*WIF(yJC(ZL&J#4n1l!qYi86upYI^YN1K3 z7JE>WJ*kB@wK`;V$?B2SM@p?e=TOQy)klw7U2>N-sK=VrWsP0dsvcQ=vQlzNkNV6g z#Q;N&X+Y0_UJc0_ku@fd>CuQ8jakE(wP-}v5Cdur*@Fh`NkfdNH71X_2Q=cI(3pE< z%)O!!Swpf01(5op~GqN&rMvrF9C__%IIav!V zskLB6OXjt}oLV_K=NjZ(3(d(|U`ee7*HBBYsTP=1D<><%j9M8p8S|QvH6?38p3tKy zGn#M@Ot=>`C2NL^S~Kn;W!zJmVM?tDdBQ!YDfgr%+@lljRZYp7k(H4%dNgB38FI`y zrktLfUd_o`khLT)>Cu82Em^~owP-=s967b->_Itu(i{tFEy-GuwI*wWEwwhBLtDVH(GCUc(~do;U{Bg1Qp?xWA)J&V_k==t9dDE(_ltJiJ1WQpJ=$^asKAz58?x3| zQESbNR?KUS4Yju9Ezf~1&joGBT4P16HP0cfcur}J4Yjsp71&X$V5VSRJF+5KA>72R zQe;LU@FpYNE=67sXorH=2iox(LItlWv_qs;2yf$^Qsgy>Lb!u>OOe+r+VR>&1-YO{ zJ6^k}z!n>hX-m(RUTw%)leHqR=+T-Ptys^BwP;P&23u-v*n_t0NgJ%GwIXYY1+|vU zEScAWtT|aZIj2W+W|SkN){LwvCe)fTqY3kxVn(fuoUwUsKGe zm64TWPOY4ooO#X3T9CCQFX_>O87+Aapat&_;p1ykh@aleHmhOWx9>4Kv!Jz>Z@o z=qc#cj;u&l2=C!;DKeuF?%{qZvL5YFus-eBg9`Sf9U`?t`KJMYr}}#|gx`Jm%Rhed z>0dwn+v7jKfA(>+3Vw~@m0z5%!?p9(_xb<*3h@8HN%$a+{gcsGSN_9|@r3_!`NAKe zNKdT%8lLzNU(V`%vd<&_#j5Nm|8!;liyq&1etky2lV{$4x1;a7Kku{Wua3WHeHTA| zwv)3JyAD@yeqYsjtMlx?zIdJc{=<2yx#~Q}k6-rvtMeTCj;~L(j>C04UjJckf4n-M ztHQaTOI07Q@7ccneyZpDE|pa1w_ z-PJme_jlQRbsY|U`}KGhc-fx1&yOEJT-Wb^{`=qaFL!;a>wLJ5$3MSs`}4hQ{=2^a zFW=vOzlU{J_g!@zFMj;8`?I`zFZ}&_@%??-`42z0=imRad{xMwi1>K(%eeV(eCrE8 z|Lk)=|K?>s`^G)|`emoi_kWu+>{sra{cyV9k+J$X+vAfMcelUr^G`qb^M@CIK3mT> X=)Cwm7eD&Vzsr8*UzWQ#zsLUpn>ja9 literal 0 HcmV?d00001 diff --git a/hilbert.svg b/hilbert.svg new file mode 100644 index 0000000..dde125b --- /dev/null +++ b/hilbert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/l-system.py b/l-system.py new file mode 100644 index 0000000..d1ba05e --- /dev/null +++ b/l-system.py @@ -0,0 +1,203 @@ +import pygame +import math + +# other = variables +# F,A = move n forward +# G = move n forward without drawing a line +# B = move n backwards +# - = turn left by angle +# + = turn right by angle +# [ = push position and angle +# ] = pop position and angle +# a,b,c,d = color 1,2,3,4 +# 1-4 line size (std = 1) +# + +rules = {} + +rules["F"] = "F-F++F-F" +axiom = "F++F++F" +angle = 60 + +# rules['A'] = '+F-A-F+' # Sierpinsky +# rules['F'] = '-A+F+A-' +# axiom = 'A' +# angle = 60 + +# rules['F'] = 'F+F-F-F+F' # Koch curve 1 +# axiom = 'F' +# angle = 60 + +# rules['F'] = 'F+F--F+F' # Koch curve 2 +# axiom = 'F' +# angle = 60 + +# rules["X"] = "X+YF+" # Dragon curve +# rules["Y"] = "-FX-Y" +# axiom = "FX" +# angle = 90 + +# rules['X'] = 'F-[[X]+X]+F[+FX]-X' # Wheat +# rules['F'] = 'FF' +# axiom = 'X' +# angle = 25 + +# rules['F'] = 'a2FF-[c1-F+F+F]+[c1+F-F-F]' # Tree - colored +# axiom = 'F' +# angle = 23 + +# rules['X'] = 'F-[[X]-1X]+2F-[+3FX]+1X' # Wheat +# rules['F'] = 'X' +# axiom = 'X' +# angle = 25 + +iterations = 7 # number of iterations +step = 15 # step size / line length + +color1 = (105, 46, 26) # brown 1 +color2 = (201, 146, 127) # brown 2 +color3 = (101, 250, 52) # green +color4 = (255, 255, 255) # white + +angleoffset = 90 + +size = width, height = 10000, 10000 # display with/height +pygame.init() # init display +screen = pygame.Surface(size) # open screen + +# startpos = 100, height - 225 +# startpos = 50, height / 2 - 50 +startpos = width / 2, height / 2 +# startpos = 100, height / 2 +# startpos = 10,10 + + +def applyRule(input): + output = "" + for ( + rule, + result, + ) in rules.items(): # applying the rule by checking the current char against it + if input == rule: + output = result # Rule 1 + break + else: + output = input # else ( no rule set ) output = the current char -> no rule was applied + return output + + +def processString(oldStr): + newstr = "" + for character in oldStr: + newstr = newstr + applyRule(character) # build the new string + return newstr + + +def createSystem(numIters, axiom): + startString = axiom + endString = "" + for i in range(numIters): # iterate with appling the rules + print("Iteration: {0}".format(i)) + endString = processString(startString) + startString = endString + return endString + + +def polar_to_cart(theta, r, offx, offy): + x = r * math.cos(math.radians(theta)) + y = r * math.sin(math.radians(theta)) + return tuple([x + y for x, y in zip((int(x), int(y)), (offx, offy))]) + + +def cart_to_polar(x, y): + return (math.degrees(math.atan(y / x)), math.sqrt(math.pow(x, 2) + math.pow(y, 2))) + + +def drawTree(input, oldpos): + a = 0 # angle + i = 0 # counter for processcalculation + processOld = 0 # old process + newpos = oldpos + num = [] # stack for the brackets + color = (255, 255, 255) + linesize = 1 + xmax = 0 + xmin = 0 + ymax = 0 + ymin = 0 + for ( + character + ) in input: # process for drawing the l-system by writing the string to the screen + + i += 1 # print process in percent + process = i * 100 / len(input) + if not process == processOld: + print(process, "%") + processOld = process + + if character == "A": # magic happens here + newpos = polar_to_cart(a + angleoffset, step, oldpos[0], oldpos[1]) + pygame.draw.line(screen, color, oldpos, newpos, linesize) + oldpos = newpos + elif character == "F": + newpos = polar_to_cart(a + angleoffset, step, oldpos[0], oldpos[1]) + pygame.draw.line(screen, color, oldpos, newpos, linesize) + oldpos = newpos + elif character == "B": + newpos = polar_to_cart(-a + angleoffset, -step, oldpos[0], oldpos[1]) + pygame.draw.line(screen, color, oldpos, newpos, linesize) + oldpos = newpos + elif character == "G": + newpos = polar_to_cart(a + angleoffset, step, oldpos[0], oldpos[1]) + oldpos = newpos + elif character == "a": + color = color1 + elif character == "b": + color = color2 + elif character == "c": + color = color3 + elif character == "d": + color = color4 + elif character == "1": + linesize = 1 + elif character == "2": + linesize = 2 + elif character == "3": + linesize = 3 + elif character == "4": + linesize = 4 + elif character == "+": + a += angle + elif character == "-": + a -= angle + elif character == "[": + num.append((oldpos, a)) + elif character == "]": + oldpos, a = num.pop() + if xmax < oldpos[0] - width / 2: + xmax = oldpos[0] - width / 2 + if xmin > oldpos[0] - width / 2: + xmin = oldpos[0] - width / 2 + if ymax < oldpos[1] - height / 2: + ymax = oldpos[1] - height / 2 + if ymin > oldpos[1] - height / 2: + ymin = oldpos[1] - height / 2 + crop = pygame.Surface((abs(xmax - xmin) + 100, abs(ymax - ymin) + 100)) + crop.blit( + screen, + (50, 50), + (xmin + width / 2, ymin + height / 2, xmax + width / 2, ymax + height / 2), + ) + pygame.image.save(crop, "screenshot.png") + + +if __name__ == "__main__": + # drawTree(createSystem(iterations, axiom), startpos) + tree = createSystem(iterations, axiom) + drawTree(tree, startpos) + # pygame.display.flip() + # pygame.image.save(screen, "screenshot.png") + # print "Finished" + while 1: + pass + exit() # uncommand diff --git a/linger_longer.pes b/linger_longer.pes new file mode 100644 index 0000000000000000000000000000000000000000..18bf62bcf6eff6dcdb50e819232fc918df64f182 GIT binary patch literal 5168 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 TjXdMS-7zan=~veF-|g|ABw~5G literal 0 HcmV?d00001 diff --git a/linger_longer.svg b/linger_longer.svg new file mode 100644 index 0000000..c2d133e --- /dev/null +++ b/linger_longer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests_input/convo.svg b/tests_input/convo.svg new file mode 100644 index 0000000..7efff38 --- /dev/null +++ b/tests_input/convo.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/tests_pes/convo.pes b/tests_pes/convo.pes new file mode 100644 index 0000000000000000000000000000000000000000..da198044c2eb1942527e3cc43ad1d586deeee96a GIT binary patch literal 5249 zcmeI0d2m%#62|-9us_MY}7lkcVho0E5Q~mk1a1-^?rF7e6J76 z1aMmaAxlBNJ}e8tF0Cv}K%qV=b3ut#lQghjYf1*#tB=b#a9-=mB=Dt1%G=MKl1d)_QwvO&H(BR!VbbdCt09Chtk(j`g&0^!5;mmq=K{hGI?H> zGx{p^UZvja^!d8%)3NX#3y<;e9WO=tmSlrGog_J6n@+)>f0GJw6`-K6lt6@8ywUG_7j{!O@dbv{FBL(Or8{H8OYP;ok`%N z4s#}gQ#zcu;lz!k{z&SNg7+wRk0x$3abxhuI7f9HeT<`yM}MAkNGCe8z+s(?os69Z zk7>>xok`qG`t{Mtr(PO14V!_k8T6g$Jm?qMeph?F|xwbc1akE6$N`uNhB3{L22;!YEHj{4`Qe}TEUz`b1J zel9ut^fLS}Q};Xc{|^1H(BBp6UZwt3>^1yr*k7C_pir-4uhW-0W5Gpr-6`M;z15uu ziuDe65!k8a+$CVU-s7f&y?UQJ7wppt#8n`!B5@UotK=>N`5NZV2YWOe8;-5Y`KoTA z)^M{yp4M__gM(TJzYcyqY&~oPcQM$ZjodV_Uz@s_V7E5sd~?n}>t=!N+R~i{j%geB z9dKCNy9)tx%6_z4pfUI{?k?>H-)``Ybr*o$+S^?U3N#MBanwm5K7o8m)JbAL86L^- zNTH7u`gk7ydHiAc!?45M9FVIcu_N7WI*R>KtdC}YGg>zRpca^$Vss9uA^AmmiOg}$kf200y)W5;{4RmwNG;mCB zG3j8h-fl9%9=*%tfNgrW84bSCd(CukR39Mj0aK!XHjZdCFgx^Z`hD9R(5YrNIH)twV+K0UVt*EW%%R>K>ZWr(o%8dt^U-0USp;_I65^K- zzl^-g&}%v8mb3mYeBb5V2h{(7b02Z;BkWr8t!4dV?8n%R*p2kFnf=Y|e@6Ug_<87@ zN8j7wwH;nN&3sU#d&~l`TlaEqFLC?fvES^}L)_;f?(>M52a5HWnF~tv1aT**a|(W^ z;CB|?&XV^$>*rawe_Pr=OHJ@6Y3G(t^%_2E=%^P{28Wk@?!Nh8t;%gR{^*POqxb*k z|8)gK4&N&E^#}W(DCeAY&TN?-&upZMF>gpY^C}o270iqBpz$}0H`e$W;>DX*5^mz9 zw&^R?O?PQ(+DmiOK^hrLjKq=*Lz5@C{{5GVBlS_6!q zEncEc+XNoX;ODibY6Bv|%wTyKgi*Y?iI)15@;VwnpD@0>4#uA^-t?egtZ7M8{m48> zDw$-dO1zCNAZ-k_I&-G2JOi4VUQ&hj>r?Jo+oTt5!y2Q+)1a~G8c?nS<(iPF!BH4> zkw;B$Bxo;Q3phS)+5rDE@w$?-hUxe_u`bZCdZXoL9ur{Smu`9l>>h>T*Z3;Ev z3Tm1}Vg|rCj=g@^F5*Q6xudDn5V{eBM3AC6T;`KNF zmY_Fy#=xvEg?ba0AeBu!@%kA5eevRKXl?G3Oa*}e>w>Md;&npE2DZ(XLAut)-)8%7 zCy|tDNV+f-4l@JkA(`75MA`&W#iDVvl>`bMd9vNq1`!Z@jNJydcxxoDOUw1y2(xXr z53;u~orvv01$&=4#vd=E_4(tO}?q+7?5&Y}jetu9s