12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112 |
- #!/usr/bin/env python
-
- # Copyright (C) 2004 Paul Harrison
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
- """ Ghost Diagrams
-
- This program takes sets of tiles that connect together in certain
- ways, and looks for the patterns these tiles imply. The patterns
- are often surprising.
-
-
- This software is currently somewhat alpha.
-
- Tile set specification:
-
- A tile set specification is a list of 6- or 4-character strings,
- eg 'B Aa ', 'b Aa '
-
- Each character represents a tile edge. Letters (abcd, ABCD)
- match with their opposite case. Numbers match with themselves.
-
- A number of extra paramters can also be supplied:
-
- border : True/False : draw tile borders or not
- thickness : the thickness of the border
- width : minimum width of diagram
- height : minimum height of diagram
- colors : a list of colors
- [ background color, edge color, tile 1 color, tile 2 color... ]
- grid : True/False : draw a grid
- labels : True/False : draw labels for each tile under diagram
-
- eg 'B Aa ', 'b Aa ', width=1000, height=1000, thickness=0.5, colors=['#000','#000','#fff','#f00']
-
- Change log:
-
- 0.1 -- initial release
- 0.2 -- don't segfault on empty tiles
- 0.3 -- random keeps trying till it finds something that will grow
- optimization (options_cache)
- 0.4 -- assembly algorithm tweaks
- random tile set tweaks
- 0.5 -- Patch by Jeff Epler
- - allow window resizing
- - new connection types (33,44,cC,dD)
- - DNA tile set
- widget to set size of tiles
- no repeated tiles in random
- improvements to assembler
- 0.6 -- Use Bezier curves
- Parameters to set width, height, thickness, color
- Save images
- 0.7 -- Allow square tiles
- Smarter assembler
- Animate assembly
- 0.8 -- Knotwork
- Don't fill all of memory
- Use psyco if available
-
-
- TODO: don't backtrack areas outside current locus
- (difficulty: accidentally creating disconnected islands)
-
- TODO: (blue sky) 3D, third dimension == time
- TODO: allowances: 3 of this, 2 of that, etc.
- """
-
- try:
- import psyco
- psyco.profile()
- except:
- pass
-
- __version__ = '0.8'
-
- import sys, os, random, gtk, pango, gobject, string, math, sets
-
- class Point:
- def __init__(self, x,y):
- self.x = x
- self.y = y
-
- def __add__(self, other): return Point(self.x+other.x,self.y+other.y)
- def __sub__(self, other): return Point(self.x-other.x,self.y-other.y)
- def __mul__(self, factor): return Point(self.x*factor, self.y*factor)
- def length(self): return (self.y*self.y + self.x*self.x) ** 0.5
- def int_xy(self): return int(self.x+0.5), int(self.y+0.5)
- def left90(self): return Point(-self.y, self.x)
-
-
- # ========================================================================
- # Constants
-
- # Hexagonal connection pattern:
- #
- # o o
- # o * o
- # o o
- #
- # (all points remain on a square grid, but a regular hexagon pattern
- # can be formed by a simple linear transformation)
-
- connections_6 = [ (-1, 0, 3), (-1, 1, 4), (0, 1, 5), (1, 0, 0), (1, -1, 1), (0, -1, 2) ]
- # [ (y, x, index of reverse connection) ]
- x_mapper_6 = Point(1.0, 0.0)
- y_mapper_6 = Point(0.5, 0.75**0.5)
-
- connections_4 = [ (-1,0,2), (0,1,3), (1,0,0), (0,-1,1) ]
- x_mapper_4 = Point(1.0, 0.0)
- y_mapper_4 = Point(0.0, 1.0)
-
-
-
- # What edge type connects with what?
- # (a tile is represented as a string of 6 characters representing the 6 edges)
- compatabilities = {
- ' ':' ',
- 'A':'a', 'a':'A', 'B':'b', 'b':'B', 'c':'C', 'C':'c', 'd':'D', 'D':'d',
- '1':'1', '2':'2', '3':'3', '4':'4', '-':'-'
- }
-
-
- # Amount of oversampling in drawing
- factor = 3
-
-
- # Some cool tile sets people have found
- catalogue = [
- "' 33Aa', ' 33 Aa'",
- "' 33Aa/#000', ' 33 Aa/#000', colors=['#fff','#fff'], border=0, grid=0",
- "'ab A ', 'B C ', 'B c ', 'B D ', 'B d '",
- "'d D 4 ', 'd D ', '44 '",
- "'AaAa '",
- "'aA ', 'AaAa '",
- "' bB ', 'bAaB ', 'aAaA '",
- "'B Aa ', 'b Aa '",
- "'44 ', '11 4 '",
- "'3 3 3 ', '33 '",
- "'1 1 1 ', '2 12 '",
- "' a ', 'a AA '",
- "' AAaa ', 'a A '",
- "' a A ', 'Aaa A'",
- "'a a ', ' aAA A'",
- "' a A a', ' A '",
- "' AA A', 'a a a '",
- "'a aa ', ' AA'",
- "'A A a ', 'a a '",
- "'A A a ', 'a a '",
- "' a 4', 'a4 44A', '4A '",
- "'a2 A ', ' a2 A2'",
- "'a 2a2 ', ' A A', ' 2'",
- "'141 ', '4 4 ', '1 1 '",
- "' 22 22', '22 '",
- "' Aaa ', 'A1A ', 'a 1AAa'",
- "'aA a2 ', '2A ', ' 2 A'",
- "' bB1 ', ' b B '",
- "'BbB 1 ', ' b'",
- "'b b b', ' BbB '",
- "'aA1 ', ' AA ', 'a 2 '",
- "' a 4 ', ' 4 4 ', ' A441'",
- "'212111', ' 1 2 '",
- "'22222a', '22 A22'",
- "'2 222 ', '2 B2', ' b 2'",
- "' 21221', ' 221', ' 2 2'",
- "' a a a', ' A A'",
- "' Dd cA', ' d D', ' a C'",
- "' CCCc', ' 3Ca A', ' 3 c', ' c'",
- "' C dDc', ' CC C', ' ccC'",
- "' Aa Cc', ' c', ' C'",
- "' CcDdC', ' cC c', ' C'",
- "' CcCc', ' CcC c', ' c C'",
- "'A 1 1 ','a1 B','b 1 '",
- "'aa aa /#fff', 'AA /#fff', 'A A /#fff', grid=0, border=0, thickness=0.3",
- #"'bb bb ', 'BB ', 'B B '",
- "' 44B4D', ' dbB4b', ' 44D d', ' 44'",
- "' d3 3', ' D D'",
- "' cc c', ' C C c'",
- "'AaAaaa', ' 1 Aa', ' A'",
- "'d D 3 ', 'dD ', '3 '",
- "'a 1 A ', 'a A '",
- "'cCCcCC', 'cccC ', 'c C C'",
- "'A44444', 'a4 4', '4 4 '",
- "'acaACA', 'acbBCB', 'bcaBCB', 'bcbACA'",
- "'A ab ', 'B ab ', 'A a ', 'B b ', 'ABd D'", #Tree
- "'d AD ', ' a A ', 'a A ', 'aa A '", #Counter (?)
- "'bBbBBB', 'bb ', 'b B '",
- "'a AA A', 'a a '",
- "'cC a A', 'a A '",
- "'bbB B', 'b BBB ', 'bb '",
- "'cCc C ', 'cC c C'",
- "'d4 Dd ', 'd D ', 'DD '",
- "' 111'",
- "'abA ', 'B C ', 'B c ', 'B D ', 'B d '",
- "'4A4a', ' a4', ' A B', ' Ab'",
- "'acAC', 'adBD', 'bcBD', 'bdAC'",
- "'1111', ' 1'",
- "' bbb', ' BB'",
- "'1B1B', 'a A ', ' bA ', 'ab B'",
- ]
-
-
- default_colors=['#fff','#000', '#8ff', '#f44', '#aaf','#449', '#ff0088', '#ff4088', '#ff4040', '#ff00ff', '#40c0ff']
-
- default_thickness = 3.0/16
-
-
- # ========================================================================
- # Utility functions
-
- def bezier(a,b,c,d):
- result = [ ]
- n = 12
- for i in xrange(1,n):
- u = float(i) / n
- result.append(
- a * ((1-u)*(1-u)*(1-u)) +
- b * (3*u*(1-u)*(1-u)) +
- c * (3*u*u*(1-u)) +
- d * (u*u*u)
- )
- return result
-
-
- def normalize(form):
- best = form
- for i in xrange(len(form)-1):
- form = form[1:] + form[0]
- if form > best: best = form
- return best
-
-
- # =========================================================================
-
- class Config:
- def __init__(self, *forms, **kwargs):
- self.colors = kwargs.get("colors", [ ])
- self.colors += default_colors[len(self.colors):]
-
- self.border = kwargs.get("border", True)
- self.thickness = kwargs.get("thickness", default_thickness)
- self.width = kwargs.get("width", -1)
- self.height = kwargs.get("height", -1)
-
- self.grid = kwargs.get("grid", True)
- self.labels = kwargs.get("labels", False)
-
- forms = list(forms)
-
- if len(forms) < 1: raise "error"
-
- for item in forms:
- if type(item) != type(""):
- raise "error"
-
- for i in xrange(len(forms)):
- if "/" in forms[i]:
- forms[i], self.colors[i%(len(self.colors)-2)+2] = forms[i].split("/",1)
-
- if len(forms[0]) == 4:
- self.connections = connections_4
- self.x_mapper = x_mapper_4
- self.y_mapper = y_mapper_4
- else:
- self.connections = connections_6
- self.x_mapper = x_mapper_6
- self.y_mapper = y_mapper_6
-
- for item in forms:
- if len(item) != len(self.connections):
- raise "error"
- for edge in item:
- if edge not in compatabilities:
- raise "error"
-
- self.forms = forms
-
- # ========================================================================
-
-
- class Assembler:
- def __init__(self, connections, compatabilities, forms, point_set):
- self.connections = connections # [(y,x,index of reverse connection)]
- self.compatabilities = compatabilities # { edge-char -> edge-char }
- self.point_set = point_set # (y,x) -> True
-
- self.basic_forms = forms # ['edge types']
- self.forms = [ ] # ['edge types']
- self.form_id = [ ] # [original form number]
- self.rotation = [ ] # [rotation from original]
-
- for id, form in enumerate(forms):
- current = form
- for i in xrange(len(self.connections)):
- if current not in self.forms:
- self.forms.append(current)
- self.form_id.append(id)
- self.rotation.append(i)
- current = current[1:] + current[0]
-
- self.tiles = { } # (y,x) -> form number
-
- self.dirty = { } # (y,x) -> True -- Possible sites for adding tiles
-
- self.options_cache = { } # pattern -> [form_ids]
-
- self.dead_loci = sets.Set([ ]) # [ {(y,x)->form number} ]
-
- self.history = [ ]
-
- self.total_y = 0
- self.total_x = 0
-
- self.changes = { }
-
- def put(self, y,x, value):
- if (y,x) in self.changes:
- if value == self.changes[(y,x)]:
- del self.changes[(y,x)]
- else:
- self.changes[(y,x)] = self.tiles.get((y,x),None)
-
-
- if (y,x) in self.tiles:
- self.total_y -= y
- self.total_x -= x
-
- if value == None:
- if (y,x) not in self.tiles: return
- del self.tiles[(y,x)]
- self.dirty[(y,x)] = True
- else:
- self.tiles[(y,x)] = value
- self.total_y += y
- self.total_x += x
-
- for oy, ox, opposite in self.connections:
- y1 = y + oy
- x1 = x + ox
- if (y1,x1) not in self.tiles and (y1, x1) in self.point_set:
- self.dirty[(y1,x1)] = True
-
- def get_pattern(self, y,x):
- result = ''
- for oy, ox, opposite in self.connections:
- y1 = y + oy
- x1 = x + ox
- if self.tiles.has_key((y1,x1)):
- result += self.compatabilities[self.forms[self.tiles[(y1,x1)]][opposite]]
- #elif (y1,x1) not in self.point_set:
- # result += ' '
- else:
- result += '.'
-
- return result
-
- def fit_ok(self, pattern,form_number):
- form = self.forms[form_number]
- for i in xrange(len(self.connections)):
- if pattern[i] != '.' and pattern[i] != form[i]:
- return False
-
- return True
-
- def options(self, y,x):
- pattern = self.get_pattern(y,x)
- if pattern in self.options_cache:
- result = self.options_cache[pattern]
-
- result = [ ]
- for i in xrange(len(self.forms)):
- if self.fit_ok(pattern,i):
- result.append(i)
- result = tuple(result)
-
- self.options_cache[pattern] = result
-
- return result
-
- def locus(self, y,x, rotation=0):
- visited = { }
- neighbours = { }
- todo = [ ((y,x), (0,0)) ]
- result = [ ]
-
- min_y = 1<<30
- min_x = 1<<30
-
- while todo:
- current, offset = todo.pop(0)
- if current in visited: continue
- visited[current] = True
-
- any = False
- new_todo = [ ]
- for i, (oy, ox, opposite) in enumerate(self.connections):
- neighbour = (current[0]+oy, current[1]+ox)
- if neighbour in self.point_set:
- if neighbour in self.tiles:
- any = True
- neighbours[neighbour] = True
- min_y = min(min_y, offset[0])
- min_x = min(min_x, offset[1])
- result.append( (offset, opposite,
- self.forms[self.tiles[neighbour]][opposite]) )
- else:
- temp = self.connections[(i+rotation) % len(self.connections)]
- new_offset = (offset[0]+temp[0], offset[1]+temp[1])
- new_todo.append((neighbour, new_offset))
-
- if not any and len(self.connections) == 4:
- for oy, ox in ((-1,-1), (-1,1), (1,-1), (1,1)):
- neighbour = (current[0]+oy, current[1]+ox)
- if neighbour in self.tiles:
- any = True
- break
-
- if any:
- todo.extend(new_todo)
-
- result = [ (yy-min_y,xx-min_x,a,b) for ((yy,xx),a,b) in result ]
-
- return sets.ImmutableSet(result), visited, neighbours
-
-
- def filter_options(self, y,x,options):
- result = [ ]
- for i in options:
- self.tiles[(y,x)] = i
- visiteds = [ ]
-
- for oy, ox, oppoiste in self.connections:
- y1 = y+oy
- x1 = x+ox
-
- ok = True
- if (y1,x1) not in self.tiles and (y1,x1) in self.point_set:
- for visited in visiteds:
- if (y1,x1) in visited:
- ok = False
- break
- if ok:
- locus, visited, _ = self.locus(y1,x1)
- visiteds.append(visited)
- if locus is not None and locus in self.dead_loci:
- break
- else:
- result.append(i)
-
- del self.tiles[(y,x)]
-
- return result
-
- def any_links_to(self, y,x):
- for oy, ox, opposite in self.connections:
- y1 = y + oy
- x1 = x + ox
- if (y1, x1) in self.tiles:
- if self.forms[self.tiles[(y1,x1)]][opposite] != ' ':
- return True
- return False
-
- def prune_dead_loci(self):
- for item in list(self.dead_loci):
- if random.randrange(2):
- self.dead_loci.remove(item)
-
- def iterate(self):
- if not self.tiles:
- self.put(0,0,0)
- self.history.append((0,0))
- return True
-
- mid_y = 0.0
- mid_x = 0.0
- for y, x in self.dirty.keys():
- if (y,x) in self.tiles or not self.any_links_to(y,x):
- del self.dirty[(y,x)]
- continue
- mid_y += y
- mid_x += x
-
- if not self.dirty:
- return False
-
- mid_y /= len(self.dirty)
- mid_x /= len(self.dirty)
-
- point_list = [ ]
- for y, x in self.dirty.keys():
- yy = y - mid_y
- xx = x - mid_x
- sorter = ((yy*2)**2+(xx*2+yy)**2)
- point_list.append( (sorter,y,x) )
-
- point_list.sort()
-
- best = None
-
- for sorter, y, x in point_list:
- options = self.options(y,x)
-
- if len(options) < 2:
- score = 0
- else:
- score = 1
-
- if best == None or score < best_score:
- best = options
- best_score = score
- best_x = x
- best_y = y
- if score == 0: break
-
- if best == None: return False
-
- best = self.filter_options(best_y,best_x,best)
-
- if len(best) > 0:
- self.put(best_y,best_x,random.choice(best))
- self.history.append((best_y,best_x))
- return True
-
- #otherwise, backtrack:
-
- for i in xrange(len(self.connections)):
- locus, _, relevant = self.locus(best_y,best_x,i)
- if locus is None: break
- self.dead_loci.add(locus)
- if len(locus) > 8: break
-
- if len(self.dead_loci) > 10000:
- self.prune_dead_loci()
-
- # Shape of distribution
- autism = 1.0 # 1.0 == normal, >1.0 == autistic (just a theory :-) )
-
- # Overall level
- adhd = 2.0 # Lower == more adhd
-
- n = 1
- while n < len(self.tiles)-1 and random.random() < ( n/(n+autism) )**adhd:
- n += 1
-
- while self.history and (n > 0 or
- self.locus(best_y,best_x)[0] in self.dead_loci):
- item = self.history.pop(-1)
- self.put(item[0],item[1],None)
- n -= 1
-
- if not self.tiles:
- return False
-
- return True
-
-
-
-
- # ========================================================================
-
-
- class Interface:
- def __init__(self):
- self.iteration = 0
- self.idle_enabled = False
-
- self.drawing = gtk.DrawingArea()
- self.drawing.unset_flags(gtk.DOUBLE_BUFFERED)
- #self.drawing.add_events(gtk.gdk.BUTTON_PRESS_MASK)
- self.drawing.connect('expose-event', self.on_expose)
- self.drawing.connect('size-allocate', self.on_size)
- #self.drawing.connect('button-press-event', self.on_click)
-
- scroller = gtk.ScrolledWindow()
- scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scroller.add_with_viewport(self.drawing)
-
- self.combo = gtk.Combo()
- for item in catalogue:
- item = gtk.ListItem(item)
- item.get_children()[0].modify_font(pango.FontDescription("mono"))
- item.show()
- self.combo.list.append_items([item])
- self.combo.list.connect('select-child', lambda widget, child: self.reset())
- self.combo.entry.modify_font(pango.FontDescription("mono"))
-
- knot_box = gtk.CheckButton("knotwork")
- knot_box.set_active(False)
- self.knot = False
- knot_box.connect("toggled", self.on_knot_changed)
-
- scale_label = gtk.Label(' Size: ')
- scale = gtk.SpinButton()
- scale.set_digits(1)
- scale.set_increments(1.0,1.0)
- scale.set_range(3.0, 50.0)
- scale.set_value(10.0)
- scale.connect('value-changed', self.on_set_scale)
-
- random_button = gtk.Button(' Random ')
- random_button.connect('clicked', lambda widget: self.random())
-
- reset = gtk.Button(' Restart ')
- reset.connect('clicked', lambda widget: self.reset())
-
- save = gtk.Button(' Save image... ')
- save.connect('clicked', self.on_save)
-
- hbox = gtk.HBox(False,5)
- hbox.set_border_width(3)
- hbox.pack_start(knot_box, False, False, 0)
- hbox.pack_end(save, False,False,0)
- hbox.pack_end(reset, False,False,0)
- hbox.pack_end(random_button, False,False,0)
- hbox.pack_end(scale, False,False,0)
- hbox.pack_end(scale_label, False,False,0)
-
- vbox = gtk.VBox(False,5)
- vbox.pack_start(self.combo, False,False,0)
- vbox.pack_start(hbox, False,False,0)
- vbox.pack_start(scroller, True,True,0)
-
- self.window = gtk.Window()
- self.window.set_default_size(600,650)
- #self.window.set_default_size(200,200)
- self.window.set_title('Ghost Diagrams')
- self.window.add(vbox)
- self.window.connect('destroy', lambda widget: gtk.main_quit())
-
- self.drawing.realize()
-
- self.gc = gtk.gdk.GC(self.drawing.window)
-
- self.pixbuf_ready = False
- self.render_iterator = None
-
- self.randomizing = False
-
- self.set_scale(scale.get_value())
-
- def on_size(self, widget, event):
- self.pixbuf_ready = False
-
- self.width = event.width
- self.height = event.height
- self.pixmap = gtk.gdk.Pixmap(self.drawing.window, self.width*factor,self.height*factor)
-
- self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
- self.width*factor,self.height*factor)
- self.scaled_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
- self.width,self.height)
- self.reset()
-
- def on_set_scale(self, widget):
- self.set_scale(widget.get_value())
- self.reset()
-
- def set_scale(self, value):
- self.scale = value
-
- def on_knot_changed(self, widget):
- self.knot = widget.get_active()
- self.reset()
-
- def reset(self):
- try:
- self.config = eval('Config('+self.combo.entry.get_text()+')')
- except:
- import traceback
- traceback.print_exc()
- self.config = Config(" ", grid=False, colors=["#f66"])
-
- colormap = self.drawing.get_colormap()
- self.colors = [ colormap.alloc_color(item) for item in self.config.colors ]
-
- point_set = { }
- yr = int( self.height/self.scale/4 )
- xr = int( self.width/self.scale/4 )
- if self.config.labels:
- bound = self.scale * 3
- for y in xrange(-yr,yr):
- for x in xrange(-yr,xr):
- point = self.pos(x*2,y*2)
- if point.x > bound and point.x < self.width-bound and \
- point.y > bound and point.y < self.height-bound-90:
- point_set[(y,x)] = True
- else:
- bound = self.scale * 3
- for y in xrange(-yr,yr):
- for x in xrange(-yr,xr):
- point = self.pos(x*2,y*2)
- if point.x > -bound and point.x < self.width+bound and \
- point.y > -bound and point.y < self.height+bound:
- point_set[(y,x)] = True
-
-
- self.pixbuf_ready = False
- self.render_iterator = None
- self.randomizing = False
- self.iteration = 0
- self.drawing.queue_draw()
- self.shapes = { }
- self.polys = { }
- self.assembler = Assembler(self.config.connections, compatabilities,
- self.config.forms, point_set)
- if not self.idle_enabled:
- gobject.idle_add(self.idle)
- self.idle_enabled = True
-
- self.drawing.set_size_request(self.config.width, self.config.height)
- self.drawing.queue_draw()
-
-
- def run(self):
- self.window.show_all()
- gtk.main()
-
-
- def idle(self):
- if self.render_iterator:
- try:
- self.render_iterator.next()
- except StopIteration:
- self.render_iterator = None
- return True
-
- if not self.idle_enabled:
- return False
-
- self.idle_enabled = self.assembler.iterate()
-
- self.iteration += 1
-
- if self.randomizing and \
- (self.iteration == 100 or not self.idle_enabled):
- self.randomizing = False
-
- forms_present = { }
- for item in self.assembler.tiles.values():
- forms_present[self.assembler.form_id[item]] = 1
-
- if len(self.assembler.tiles) < 10 \
- or len(forms_present) < len(self.assembler.basic_forms):
- self.idle_enabled = False
- self.random(True)
- return False
-
- if not self.idle_enabled or len(self.assembler.changes) >= 8:
- changes = self.assembler.changes
- self.assembler.changes = { }
- for y,x in changes:
- old = changes[(y,x)]
- if old is not None:
- self.draw_poly(y,x,old,1, self.drawing.window, True)
- for y,x in changes:
- new = self.assembler.tiles.get((y,x),None)
- if new is not None:
- self.draw_poly(y,x,new,1, self.drawing.window, False)
-
- if not self.idle_enabled and not self.assembler.dirty:
- self.render_iterator = self.make_render_iter()
-
- return True
-
-
- def pos(self, x,y, center=True):
- result = (self.config.x_mapper*x + self.config.y_mapper*y) * (self.scale*2)
- if center:
- return result + Point(self.width/2.0,self.height/2.0)
- else:
- return result
-
- def make_shape(self, form_number):
- if form_number in self.shapes:
- return self.shapes[form_number]
-
- result = [ ]
- connections = { }
-
- for i in xrange(len(self.assembler.connections)):
- yy, xx = self.assembler.connections[i][:2]
- symbol = self.assembler.forms[form_number][i]
- if symbol in ' -': continue
-
- edge = self.pos(xx,yy,0)
- out = edge
- left = out.left90()
-
- if symbol in 'aA1':
- r = 0.4
- #r = 0.6
- elif symbol in 'bB2':
- r = 0.3
- elif symbol in 'cC3':
- r = 0.225
- else:
- r = 0.15
-
- if symbol in 'ABCD':
- poke = 0.3 #r
- elif symbol in 'abcd':
- poke = -0.3 #-r
- else:
- poke = 0.0
-
- points = [
- edge + left*-r,
- edge + out*poke,
- edge + left*r,
- ]
-
- result.append( (out * (1.0/out.length()), points, 0.5)) #0.625))
- connections[i] = points
- # Note: set constant to ~0.35 for old-style circular look
-
- if len(result) == 1:
- point = result[0][0]*(self.scale*-0.7)
- result.append( (result[0][0].left90()*-1.0, [point], 0.8) )
- result.append( (result[0][0].left90(), [point], 0.8) )
-
- poly = [ ]
- for i in xrange(len(result)):
- a = result[i-1][1][-1]
- d = result[i][1][0]
- length = (d-a).length() * ((result[i][2]+result[i-1][2])*0.5)
- b = a - result[i-1][0]*length
- c = d - result[i][0]*length
- poly.extend(bezier(a,b,c,d))
- poly.extend(result[i][1])
-
- links = [ ]
- if self.knot:
- form = self.assembler.forms[form_number]
- items = connections.keys()
- cords = [ ]
-
- if len(items)%2 != 0:
- for item in items[:]:
- if (item+len(form)/2) % len(form) not in items and \
- (item+len(form)/2+1) % len(form) not in items and \
- (item+len(form)/2-1) % len(form) not in items:
- items.remove(item)
-
- if len(items)%2 != 0:
- for i in xrange(len(form)):
- if form[i] not in ' -' and \
- form.count(form[i]) == 1 and \
- (compatabilities[form[i]] == form[i] or \
- form.count(compatabilities[form[i]])%2 == 0):
- items.remove(i)
-
- if len(items)%2 != 0:
- for item in items[:]:
- if (item+len(form)/2) % len(form) not in items:
- items.remove(item)
-
- if len(items)%2 == 0:
- rot = self.assembler.rotation[form_number]
- mod = len(self.assembler.connections)
- items.sort(lambda a,b: cmp((a+rot)%mod,(b+rot)%mod))
- step = len(items)/2
-
- for ii in xrange(len(items)/2):
- i = items[ii]
- j = items[ii-step]
- cords.append((i,j))
-
- for i,j in cords:
- a = connections[i]
- b = connections[j]
- a_in = (a[-1]-a[0]).left90()
- a_in = a_in*(self.scale*1.25/a_in.length())
- b_in = (b[-1]-b[0]).left90()
- b_in = b_in*(self.scale*1.25/b_in.length())
- a = [(a[0]+a[1])*0.5,a[1],(a[-2]+a[-1])*0.5]
- b = [(b[0]+b[1])*0.5,b[1],(b[-2]+b[-1])*0.5]
- bez1 = bezier(a[-1],a[-1]+a_in,b[0]+b_in,b[0])
- bez2 = bezier(b[-1],b[-1]+b_in,a[0]+a_in,a[0])
- linker = a + bez1 + b + bez2
- links.append((linker,a[-1:]+bez1+b[:1],b[-1:]+bez2+a[:1]))
-
- self.shapes[form_number] = poly, links
- return poly, links
-
- def draw_poly(self, y,x,form_number,factor, drawable, erase=False):
- id = (y,x,form_number,factor)
-
- if id not in self.polys:
- def intify(points): return [ ((middle+point)*factor).int_xy() for point in points ]
-
- middle = self.pos(x*2,y*2)
-
- poly, links = self.make_shape(form_number)
- poly = intify(poly)
- links = [ (intify(link), intify(line1), intify(line2)) for link,line1,line2 in links ]
-
- self.polys[id] = poly, links
- else:
- poly, links = self.polys[id]
-
- if len(poly) > 0:
- if erase:
- color = 0
- else:
- color = self.assembler.form_id[form_number] % (len(self.colors)-2) + 2
-
- self.gc.set_rgb_fg_color(self.colors[color])
- drawable.draw_polygon(self.gc, True, poly)
-
- if self.knot:
- self.gc.set_line_attributes(max(factor,int(self.scale*factor * self.config.thickness)),
- gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
- for link, line1, line2 in links:
- if not erase: self.gc.set_rgb_fg_color(self.colors[1])
- drawable.draw_polygon(self.gc, True, link)
- if not erase: self.gc.set_rgb_fg_color(self.colors[color])
- #drawable.draw_polygon(self.gc, False, link)
- drawable.draw_lines(self.gc, line1)
- drawable.draw_lines(self.gc, line2)
- #drawable.draw_line(self.gc, *(connections[i][-1]+connections[j][0]))
- #drawable.draw_line(self.gc, *(connections[j][-1]+connections[i][0]))
-
- if self.config.border:
- self.gc.set_line_attributes(max(factor,int(self.scale*factor * self.config.thickness)),
- gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
- if not erase: self.gc.set_rgb_fg_color(self.colors[1])
- self.pixmap.draw_polygon(self.gc, False, poly)
- drawable.draw_polygon(self.gc, False, poly)
-
- def make_render_iter(self):
- yield None
-
- if not self.assembler.tiles:
- self.pixbuf_ready = False
- return
-
- self.gc.set_rgb_fg_color(self.colors[0])
- self.pixmap.draw_rectangle(self.gc, True, 0,0,self.width*factor,self.height*factor)
-
- if self.config.labels and False:
- font = pango.FontDescription("mono bold 36")
- self.gc.set_rgb_fg_color(self.colors[1])
-
- for i, form in enumerate(self.assembler.basic_forms):
- layout = self.drawing.create_pango_layout(" "+form.replace(" ","-")+" ")
- layout.set_font_description(font)
- x = (i+1)*(len(form)+3)*30
- y = (self.height-70) *factor
- width, height = layout.get_pixel_size()
- self.pixmap.draw_rectangle(self.gc, True, x-6,y-6, width+12,height+12)
- self.pixmap.draw_layout(self.gc, x,y, layout, self.colors[1], self.colors[i+2])
-
- if self.config.grid:
- colormap = self.drawing.get_colormap()
- self.gc.set_rgb_fg_color(colormap.alloc_color("#eee"))
- self.gc.set_line_attributes(factor,
- gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
- f = 4.0 / len(self.config.connections)
- for (y,x) in self.assembler.point_set.keys():
- poly = [ ]
- for i in xrange(len(self.config.connections)):
- a = self.config.connections[i-1]
- b = self.config.connections[i]
- poly.append((self.pos(x*2+(a[0]+b[0])*f,y*2+(a[1]+b[1])*f)*factor).int_xy())
-
- self.pixmap.draw_polygon(self.gc, False, poly)
- yield None
-
- for (y,x), form_number in self.assembler.tiles.items():
- self.draw_poly(y,x,form_number,factor, self.pixmap)
- yield None
-
- self.pixbuf.get_from_drawable(self.pixmap, self.pixmap.get_colormap(),
- 0,0,0,0, self.width*factor,self.height*factor)
-
- yield None
-
- self.pixbuf.scale(self.scaled_pixbuf, 0,0,self.width,self.height,
- 0,0,1.0/factor,1.0/factor,gtk.gdk.INTERP_BILINEAR)
-
- self.pixbuf_ready = True
- self.drawing.queue_draw()
-
- def on_expose(self, widget, event):
- self.assembler.changes = dict([ (item, None) for item in self.assembler.tiles ])
-
- if self.pixbuf_ready:
- self.drawing.window.draw_pixbuf(self.gc, self.scaled_pixbuf, 0,0,0,0,-1,-1)
- else:
- self.gc.set_rgb_fg_color(self.colors[0])
- self.drawing.window.draw_rectangle(self.gc, True, 0,0,self.width,self.height)
-
- def random(self, same_form=False):
- if same_form:
- n = len(self.assembler.basic_forms)
- sides = len(self.assembler.basic_forms[0])
- else:
- n = random.choice([1,1,2,2,2,3,3,3,4])
- if self.knot:
- sides = 6
- else:
- sides = random.choice([4,6])
-
-
- while True:
- if self.knot:
- edge_counts = [ random.choice(range(2,sides+1,2))
- for i in xrange(n) ]
- else:
- edge_counts = [ random.choice(range(1,sides+1))
- for i in xrange(n) ]
-
- edge_counts.sort()
- edge_counts.reverse()
- if edge_counts[0] != 1: break
-
- while True:
- try:
- result = [ ]
- previous = '1234' + 'aAbBcCdD' #* 3
- for edge_count in edge_counts:
- item = [' ']*(sides-edge_count)
- for j in xrange(edge_count):
- selection = random.choice(previous)
- previous += compatabilities[selection]*6 #12
- item.append(selection)
-
- random.shuffle(item)
- item = normalize(string.join(item,''))
- if item in result: raise "repeat"
- result.append(item)
-
- all = string.join(result,'')
- for a, b in compatabilities.items():
- if a in all and b not in all: raise "repeat"
-
- break
- except "repeat":
- pass
-
- self.combo.entry.set_text(repr(result)[1:-1])
- self.reset()
- self.randomizing = True
-
- def on_save(self, widget):
- selecter = gtk.FileSelection('Save image')
- selecter.set_filename('diagram.png')
-
- def on_ok(widget):
- filename = selecter.get_filename()
- selecter.destroy()
-
- if self.pixbuf_ready:
- self.scaled_pixbuf.save(filename, 'png')
- else:
- pass
- # TODO: show error
-
- def on_cancel(widget):
- selecter.destroy()
-
- selecter.ok_button.connect('clicked', on_ok)
- selecter.cancel_button.connect('clicked', on_ok)
-
- selecter.show()
-
-
-
- if __name__ == '__main__':
- Interface().run()
- sys.exit(0)
-
- # Just some phd stuff...
- interface = Interface()
- interface.window.show_all()
-
- base = 2
- chars = " 1"
- n = len(connections)
- done = { }
-
- for i in xrange(1, base ** n):
- result = ""
- for j in xrange(n):
- result += chars[(i / (base ** j)) % base]
- if normalize(result) in done or normalize(result.swapcase()) in done: continue
- print result
- done[normalize(result)] = True
-
- interface.combo.entry.set_text("'"+result+"', width=350, height=400")
- interface.reset()
-
- while gtk.events_pending():
- gtk.main_iteration()
-
- if interface.assembler.dirty:
- print "--- failed"
- continue
-
- interface.scaled_pixbuf.save("/tmp/T" + result.replace(" ","-") + ".png", "png")
|