Compare commits

...

11 Commits

Author SHA1 Message Date
c51efebf95 Corrected error message 2024-12-05 11:56:44 -05:00
ea1ac6490a Fix write: color 2024-02-26 10:26:16 -05:00
3d681da551 Export other shapes 2024-02-26 10:04:57 -05:00
5864dc5c06 Coarse grain mouse input with lattice 2024-02-26 10:01:50 -05:00
a152af6bac Fix open empty file 2024-02-24 10:44:11 -05:00
121b8fe4b0 2squares 2024-02-22 17:19:52 -05:00
1cd4e0fdc6 Fix lattice_points for disk 2024-02-22 17:08:42 -05:00
a48457de29 Adjust color of voronoi depending on number of neighbors 2024-02-22 16:50:04 -05:00
4a6d2a1758 More visible Voronoi cells 2024-02-22 16:42:57 -05:00
29355191d6 Staircases 2024-02-22 16:41:45 -05:00
7e44418829 voronoi cells for disks 2024-02-22 16:31:10 -05:00
6 changed files with 145 additions and 26 deletions

View File

@ -28,3 +28,21 @@
\fill[color=#1]#2++(0.5,0.5)--++(0,1)--++(-1,0)--++(0,-1)--++(-1,0)--++(0,-1)--++(1,0)--++(0,-1)--++(1,0)--++(0,1)--++(1,0)--++(0,1)--++(-1,0); \fill[color=#1]#2++(0.5,0.5)--++(0,1)--++(-1,0)--++(0,-1)--++(-1,0)--++(0,-1)--++(1,0)--++(0,-1)--++(1,0)--++(0,1)--++(1,0)--++(0,1)--++(-1,0);
\draw[color=black]#2++(0.5,0.5)--++(0,1)--++(-1,0)--++(0,-1)--++(-1,0)--++(0,-1)--++(1,0)--++(0,-1)--++(1,0)--++(0,1)--++(1,0)--++(0,1)--++(-1,0); \draw[color=black]#2++(0.5,0.5)--++(0,1)--++(-1,0)--++(0,-1)--++(-1,0)--++(0,-1)--++(1,0)--++(0,-1)--++(1,0)--++(0,1)--++(1,0)--++(0,1)--++(-1,0);
} }
% 3-staircase (color #1, position #2)
\def\staircase#1#2{
\fill[color=#1]#2++(-0.5,-0.5)--++(3,0)--++(0,1)--++(-1,0)--++(0,1)--++(-1,0)--++(0,1)--++(-1,0)--++(0,-3);
\draw[color=black]#2++(-0.5,-0.5)--++(3,0)--++(0,1)--++(-1,0)--++(0,1)--++(-1,0)--++(0,1)--++(-1,0)--++(0,-3);
}
% disk (color #1, position #2)
\def\disk#1#2{
\fill[color=#1]#2circle(2.5);
\draw[color=black]#2circle(2.5);
}
% square (color #1, position #2)
\def\square#1#2{
\fill[color=#1]#2++(-1,-1)--++(0,2)--++(2,0)--++(0,-2)--cycle;
\draw[color=black]#2++(-1,-1)--++(0,2)--++(2,0)--++(0,-2)--cycle;
}

View File

@ -20,7 +20,7 @@ import os.path
import filecheck import filecheck
import colors import colors
from polyomino import Cross,Disk from polyomino import Cross,Disk,Staircase,Square2
class Command_prompt(Label): class Command_prompt(Label):
@ -388,8 +388,12 @@ class Command_prompt(Label):
self.app.painter.shape=Cross self.app.painter.shape=Cross
elif argv[2]=="disk": elif argv[2]=="disk":
self.app.painter.shape=Disk self.app.painter.shape=Disk
elif argv[2]=="staircase":
self.app.painter.shape=Staircase
elif argv[2]=="2square":
self.app.painter.shape=Square2
else: else:
self.message="error: unrecognized shape '"+argv[2]+"'; supported shapes are cross|disk" self.message="error: unrecognized shape '"+argv[2]+"'; supported shapes are cross|disk|staircase|2square"
return return
# toggle grid # toggle grid

View File

@ -282,3 +282,16 @@ class Element_circle(Element):
def move_along(self,delta,element): def move_along(self,delta,element):
x=element.pos-self.pos+delta x=element.pos-self.pos+delta
return x/l_2(x)*(element.size+self.size)/2+self.pos-element.pos return x/l_2(x)*(element.size+self.size)/2+self.pos-element.pos
# for use with lattices
# list of lattice points covered by square
def lattice_points(self,lattice):
out=[]
dx=math.floor(0.5*self.size/lattice.spacing)
for i in range(-dx,dx+1):
for j in range(-dx,dx+1):
if lattice.spacing*lattice.spacing*(i*i+j*j)<=self.size*self.size/4:
out.append(Point(self.pos.x+i*lattice.spacing,self.pos.y+j*lattice.spacing))
return out

View File

@ -61,10 +61,10 @@ def read_cli():
else: else:
openfile=arg openfile=arg
(ret,message)=filecheck.check_edit(openfile) (ret,message)=filecheck.check_edit(openfile)
preread_conf(openfile)
if ret<0: if ret<0:
print(message,file=sys.stderr) print(message,file=sys.stderr)
exit(-1) exit(-1)
preread_conf(openfile)
# read command line arguments from configuration file # read command line arguments from configuration file
def preread_conf(file): def preread_conf(file):
@ -72,8 +72,7 @@ def preread_conf(file):
try: try:
ff=open(file,"r") ff=open(file,"r")
except: except:
print("error: could not read file '"+file+"' (this should not happen and is probably a bug)", file=sys.stderr) return
exit(-1)
# counter # counter
i=0 i=0

View File

@ -21,7 +21,7 @@ from kivy.core.window import Window
from kivy.graphics import Color,Line,Rectangle from kivy.graphics import Color,Line,Rectangle
from point import Point from point import Point
from polyomino import Cross,Disk from polyomino import Cross,Disk,Staircase,Square2
from tools import remove_fromlist from tools import remove_fromlist
@ -176,41 +176,43 @@ class Painter(Widget):
while self.pos_tocoord_x(xx)<self.width: while self.pos_tocoord_x(xx)<self.width:
yy=pos.y yy=pos.y
while self.pos_tocoord_y(yy)<self.height: while self.pos_tocoord_y(yy)<self.height:
if self.is_in_voronoi(xx,yy,particle): self.draw_voronoi_site(xx,yy,particle.color,self.is_in_voronoi(xx,yy,particle))
self.draw_voronoi_site(xx,yy,particle.color)
yy+=self.lattice.spacing yy+=self.lattice.spacing
yy=pos.y-self.lattice.spacing yy=pos.y-self.lattice.spacing
while self.pos_tocoord_y(yy)>0: while self.pos_tocoord_y(yy)>0:
if self.is_in_voronoi(xx,yy,particle): self.draw_voronoi_site(xx,yy,particle.color,self.is_in_voronoi(xx,yy,particle))
self.draw_voronoi_site(xx,yy,particle.color)
yy-=self.lattice.spacing yy-=self.lattice.spacing
xx+=self.lattice.spacing xx+=self.lattice.spacing
xx=pos.x-self.lattice.spacing xx=pos.x-self.lattice.spacing
while self.pos_tocoord_x(xx)>0: while self.pos_tocoord_x(xx)>0:
yy=pos.y yy=pos.y
while self.pos_tocoord_y(yy)<self.height: while self.pos_tocoord_y(yy)<self.height:
if self.is_in_voronoi(xx,yy,particle): self.draw_voronoi_site(xx,yy,particle.color,self.is_in_voronoi(xx,yy,particle))
self.draw_voronoi_site(xx,yy,particle.color)
yy+=self.lattice.spacing yy+=self.lattice.spacing
yy=pos.y-self.lattice.spacing yy=pos.y-self.lattice.spacing
while self.pos_tocoord_y(yy)>0: while self.pos_tocoord_y(yy)>0:
if self.is_in_voronoi(xx,yy,particle): self.draw_voronoi_site(xx,yy,particle.color,self.is_in_voronoi(xx,yy,particle))
self.draw_voronoi_site(xx,yy,particle.color)
yy-=self.lattice.spacing yy-=self.lattice.spacing
xx-=self.lattice.spacing xx-=self.lattice.spacing
# check whether a site is in the Voronoi cell of a particle # check whether a site is in the Voronoi cell of a particle
def is_in_voronoi(self,x,y,particle): def is_in_voronoi(self,x,y,particle):
d_to_particle=self.lattice.distance_to_particle(x,y,particle) d_to_particle=self.lattice.distance_to_particle(x,y,particle)
# count how many are in voronoi cell
count=1
# TODO: start with a particle that is close to x,y # TODO: start with a particle that is close to x,y
for q in self.particles: for q in self.particles:
if q!=particle and self.lattice.distance_to_particle(x,y,q)<d_to_particle: dd=self.lattice.distance_to_particle(x,y,q)
return False if q!=particle and dd<d_to_particle:
return True return 0
if dd==d_to_particle:
count+=1
return count
# draw a site in a Voronoi cell # draw a site in a Voronoi cell
def draw_voronoi_site(self,x,y,color): def draw_voronoi_site(self,x,y,color,count):
Color(color[0],color[1],color[2],0.25) if count==0:
#Color(1,0,0,alpha=0.25) return
Rectangle(pos=(self.pos_tocoord_x(x-0.5),self.pos_tocoord_y(y-0.5)),size=(self.base_size,self.base_size)) Color(color[0],color[1],color[2],1-count*0.1)
Rectangle(pos=(self.pos_tocoord_x(x-0.5*self.lattice.spacing),self.pos_tocoord_y(y-0.5*self.lattice.spacing)),size=(self.base_size*self.lattice.spacing,self.base_size*self.lattice.spacing))
@ -313,6 +315,9 @@ class Painter(Widget):
# record relative position of click with respect to reference # record relative position of click with respect to reference
if self.undermouse!=None: if self.undermouse!=None:
self.offset=Point(touchx,touchy)-self.undermouse.elements[0].pos self.offset=Point(touchx,touchy)-self.undermouse.elements[0].pos
# snap to lattice
if self.lattice!=None:
self.offset=self.lattice.nearest(self.offset)
# no modifiers # no modifiers
if self.modifiers==[]: if self.modifiers==[]:
@ -359,13 +364,17 @@ class Painter(Widget):
# only respond to touch in drawing area # only respond to touch in drawing area
if self.collide_point(*touch.pos): if self.collide_point(*touch.pos):
# convert to logical # convert to logical
touchx=self.coord_topos_x(touch.x) touchc=Point(self.coord_topos_x(touch.x),self.coord_topos_y(touch.y))
touchy=self.coord_topos_y(touch.y)
# snap to lattice
if self.lattice!=None:
touchc=self.lattice.nearest(touchc)
# only move on left click # only move on left click
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None: if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
# attempted move determined by the relative position to the relative position of click within self.undermouse # attempted move determined by the relative position to the relative position of click within self.undermouse
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.elements[0].pos),0) delta=self.adjust_move(touchc-(self.offset+self.undermouse.elements[0].pos),0)
# snap to lattice # snap to lattice
if self.lattice!=None: if self.lattice!=None:
@ -536,10 +545,16 @@ class Painter(Widget):
# save state (particle shape, zoom, lattice) # save state (particle shape, zoom, lattice)
if self.shape==Cross: if self.shape==Cross:
ff.write("%shape=cross\n") ff.write("%shape=cross\n")
else: elif self.shape==Disk:
ff.write("%shape=disk\n") ff.write("%shape=disk\n")
elif self.shape==Staircase:
ff.write("%shape=staircase\n")
elif self.shape==Square2:
ff.write("%shape=2square\n")
else:
print("bug: unrecognized shape in write: '"+str(self.shape)+"'")
ff.write("%zoom={:1.1f}\n".format(self.base_size/50)) ff.write("%zoom={:1.1f}\n".format(self.base_size/50))
ff.write("%color={:d},{:d},{:d}\n".format(self.color[0],self.color[1],self.color[2])) ff.write("%color={:1.1f},{:1.1f},{:1.1f}\n".format(self.color[0],self.color[1],self.color[2]))
if self.lattice != None: if self.lattice != None:
ff.write("%lattice="+self.lattice.type+':'+str(self.lattice.spacing)+"\n") ff.write("%lattice="+self.lattice.type+':'+str(self.lattice.spacing)+"\n")
for particle in self.particles: for particle in self.particles:
@ -547,6 +562,10 @@ class Painter(Widget):
ff.write("{:d};".format(CROSS_INDEX)) ff.write("{:d};".format(CROSS_INDEX))
elif type(particle)==Disk: elif type(particle)==Disk:
ff.write("{:d};".format(DISK_INDEX)) ff.write("{:d};".format(DISK_INDEX))
elif type(particle)==Staircase:
ff.write("{:d};".format(STAIRCASE_INDEX))
elif type(particle)==Square2:
ff.write("{:d};".format(SQUARE2_INDEX))
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.elements[0].pos.x,particle.elements[0].pos.y,particle.color[0],particle.color[1],particle.color[2])) ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.elements[0].pos.x,particle.elements[0].pos.y,particle.color[0],particle.color[1],particle.color[2]))
ff.close() ff.close()
@ -649,6 +668,10 @@ class Painter(Widget):
candidate=Cross(pos.x,pos.y,color=color) candidate=Cross(pos.x,pos.y,color=color)
elif particle_type==DISK_INDEX: elif particle_type==DISK_INDEX:
candidate=Disk(pos.x,pos.y,color=color) candidate=Disk(pos.x,pos.y,color=color)
elif particle_type==STAIRCASE_INDEX:
candidate=Staircase(pos.x,pos.y,color=color)
elif particle_type==SQUARE2_INDEX:
candidate=Square2(pos.x,pos.y,color=color)
else: else:
print("warning: ignoring line "+str(i)+" in file '"+file+"': unrecognized particle type: '"+entries[0]+"'",file=sys.stderr) print("warning: ignoring line "+str(i)+" in file '"+file+"': unrecognized particle type: '"+entries[0]+"'",file=sys.stderr)
continue continue
@ -685,6 +708,12 @@ class Painter(Widget):
for particle in self.particles: for particle in self.particles:
if type(particle)==Cross: if type(particle)==Cross:
ff.write("\cross{"+colors.closest_color(particle.color,colors.xcolor_names)+"}") ff.write("\cross{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
elif type(particle)==Disk:
ff.write("\disk{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
elif type(particle)==Staircase:
ff.write("\staircase{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
elif type(particle)==Square2:
ff.write("\square{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
ff.write("{{({:05.2f},{:05.2f})}};\n".format(particle.elements[0].pos.x-self.particles[0].elements[0].pos.x,particle.elements[0].pos.y-self.particles[0].elements[0].pos.y)) ff.write("{{({:05.2f},{:05.2f})}};\n".format(particle.elements[0].pos.x-self.particles[0].elements[0].pos.x,particle.elements[0].pos.y-self.particles[0].elements[0].pos.y))
ff.write("\\end{tikzpicture}\n") ff.write("\\end{tikzpicture}\n")
@ -702,5 +731,7 @@ class Painter(Widget):
# global variables (used like precompiler variables) # global variables (used like precompiler variables)
CROSS_INDEX=1 CROSS_INDEX=1
DISK_INDEX=2 DISK_INDEX=2
STAIRCASE_INDEX=3
SQUARE2_INDEX=4

View File

@ -123,3 +123,57 @@ class Cross(Polyomino):
class Disk(Polyomino): class Disk(Polyomino):
def __init__(self,x,y,**kwargs): def __init__(self,x,y,**kwargs):
super(Disk,self).__init__(**kwargs,elements=[Element_circle(x,y,size=kwargs.get("size",1.0))]) super(Disk,self).__init__(**kwargs,elements=[Element_circle(x,y,size=kwargs.get("size",1.0))])
# 3-staircase
class Staircase(Polyomino):
def __init__(self,x,y,**kwargs):
super(Staircase,self).__init__(**kwargs,elements=[\
Element_square(x,y+1,1,aspect=3),\
Element_square(x+1,y,1),\
Element_square(x+1,y+1,1),\
Element_square(x+2,y,1)\
])
# redefine stroke to avoid lines between touching elements
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(self.elements[0].pos.x)
coordy=painter.pos_tocoord_y(self.elements[0].pos.y)
Color(1,1,1)
Line(points=(
*(coordx-0.5*painter.base_size,coordy-1.5*painter.base_size),
*(coordx+2.5*painter.base_size,coordy-1.5*painter.base_size),
*(coordx+2.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy-1.5*painter.base_size),
))
# 2-square
class Square2(Polyomino):
def __init__(self,x,y,**kwargs):
super(Square2,self).__init__(**kwargs,elements=[\
Element_square(x,y,1),\
Element_square(x+1,y,1),\
Element_square(x,y+1,1),\
Element_square(x+1,y+1,1)\
])
# redefine stroke to avoid lines between touching elements
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(self.elements[0].pos.x)
coordy=painter.pos_tocoord_y(self.elements[0].pos.y)
Color(1,1,1)
Line(points=(
*(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size),
))