Split element into their own file

This commit is contained in:
Ian Jauslin 2022-09-23 21:17:53 -04:00
parent 5835c9003c
commit c09600cd10
4 changed files with 210 additions and 175 deletions

164
src/element.py Normal file
View File

@ -0,0 +1,164 @@
## elements that polyominoes are made of
import math
import sys
from point import Point,l_infinity
from tools import isint_nonzero,sgn,in_interval
# parent class of all elements
class Element():
def __init__(self,x,y,size,**kwargs):
self.pos=Point(x,y)
self.size=size
# set position
def setpos(self,x,y):
self.pos.x=x
self.pos.y=y
# override in each subclass
# check whether an element interacts with square
def check_interaction(self,element):
return False
# override in each subclass
# whether x is in the support of the element
def in_support(self,x):
return False
# override in each subclass
# check whether an element is touching self
def check_touch(self,element):
return False
# override in each subclass
# find position along a line that comes in contact with the line going through element.pos in direction v
def move_on_line_to_stick(self,element,v):
return Point(0,0)
# override in each subclass
# move along edge of element
def move_along(self,delta,element):
return element
# square elements
class Element_square(Element):
# check whether an element interacts with square
def check_interaction(self,element):
# allow for error
return l_infinity(element.pos-self.pos)<(self.size+element.size)/2-1e-11
# whether x is in the support of the element
def in_support(self,x):
return l_infinity(self.pos-x)<=1/2
# check whether an element is touching self
def check_touch(self,element):
# allow for error
if in_interval(l_infinity(element.pos-self.pos),(self.size+element.size)/2-1e-11,(self.size+element.size)/2+1e-11):
return True
return False
# find position along a line that comes in contact with the line going through element.pos in direction v
def move_on_line_to_stick(self,element,v):
# compute intersections with four lines making up square
if v.x!=0:
if v.y!=0:
intersections=[\
Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\
Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x)),\
Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\
Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\
]
else:
intersections=[\
Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\
Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x))
]
else:
if v.y!=0:
intersections=[\
Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\
Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\
]
else:
print("error: move_on_line_to_stick called with v=0, please file a bug report with the developer",file=sys.stderr)
exit(-1)
# compute closest one, on square
closest=None
dist=math.inf
for i in range(0,len(intersections)):
# check that it is on square
if abs(intersections[i].x-self.pos.x)<=(self.size+element.size)/2+1e-11 and abs(intersections[i].y-self.pos.y)<=(self.size+element.size)/2+1e-11:
if (intersections[i]-element.pos)**2<dist:
closest=intersections[i]
dist=(intersections[i]-element.pos)**2
if closest==None:
print("error: cannot move particle at (",pos.x,",",pos.y,") to the boundary of (",self.pos.x,",",self.pos.y,") in direction (",v.x,",",v.y,")",file=sys.stderr)
exit(-1)
# return difference to pos
return closest-element.pos
# move along edge of square
def move_along(self,delta,element):
rel=element.pos-self.pos
# check if the particle is stuck in the x direction
if isint_nonzero(rel.x/((self.size+element.size)/2)):
# check y direction
if isint_nonzero(rel.y/((self.size+element.size)/2)):
# in corner
if sgn(delta.y)==-sgn(rel.y):
# stuck in x direction
return self.move_stuck_x(delta,element)
elif sgn(delta.x)==-sgn(rel.x):
# stuck in y direction
return self.move_stuck_y(delta,element)
# stuck in both directions
return element.pos
else:
# stuck in x direction
return self.move_stuck_x(delta,element)
elif isint_nonzero(rel.y/((self.size+element.size)/2)):
# stuck in y direction
return self.move_stuck_y(delta,element)
# this should never happen
else:
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
exit(-1)
# move when stuck in the x direction
def move_stuck_x(self,delta,element):
# only move in y direction
candidate=Point(0,delta.y)
# do not move past corners
rel=element.pos.y-self.pos.y
if delta.y>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.y+rel>math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.y=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.y+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.y=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
return candidate
# move when stuck in the y direction
def move_stuck_y(self,delta,element):
# onlx move in x direction
candidate=Point(delta.x,0)
# do not move past corners
rel=element.pos.x-self.pos.x
if delta.x>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.x+rel>math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.x=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.x+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.x=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
return candidate

View File

@ -6,7 +6,6 @@ from kivy.graphics import Color,Line
from point import Point
from polyomino import Cross
from polyomino import Square_element
from tools import remove_fromlist
@ -71,7 +70,7 @@ class Painter(Widget):
# snap all existing particles to grid
for particle in self.particles:
delta=self.lattice.nearest_delta(particle.squares[0].pos)
delta=self.lattice.nearest_delta(particle.elements[0].pos)
if not self.check_interaction_any(particle,delta):
particle.move(delta)
@ -104,7 +103,7 @@ class Painter(Widget):
# draw grids
for particle in self.particles:
if particle.grid>0:
self.draw_grid(particle.squares[0].pos,particle.grid)
self.draw_grid(particle.elements[0].pos,particle.grid)
for particle in self.particles:
particle.draw(self,alpha=0.5)
@ -217,7 +216,7 @@ class Painter(Widget):
new=Cross(touchx,touchy)
# snap to lattice
if self.lattice!=None:
new.move(self.lattice.nearest_delta(new.squares[0].pos))
new.move(self.lattice.nearest_delta(new.elements[0].pos))
if not self.check_interaction_any(new,Point(0,0)):
# add to list
@ -238,7 +237,7 @@ class Painter(Widget):
# record relative position of click with respect to reference
if self.undermouse!=None:
self.offset=Point(touchx,touchy)-self.undermouse.squares[0].pos
self.offset=Point(touchx,touchy)-self.undermouse.elements[0].pos
# no modifiers
if self.modifiers==[]:
@ -291,7 +290,7 @@ class Painter(Widget):
# only move on left click
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
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.squares[0].pos),0)
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.elements[0].pos),0)
# snap to lattice
if self.lattice!=None:
@ -347,10 +346,10 @@ class Painter(Widget):
# check whether a candidate particle element with any of the unselected particles
def check_interaction_unselected_element(self,element,offset):
for particle in self.unselected:
for square in particle.squares:
for elt in particle.elements:
# add offset
element.pos+=offset
if square.check_interaction(element):
if elt.check_interaction(element):
# reset offset
element.pos-=offset
return True
@ -365,7 +364,7 @@ class Painter(Widget):
# actual_delta is the smallest (componentwise) of all the computed delta's
actual_delta=Point(math.inf,math.inf)
for particle in self.selected:
for element in particle.squares:
for element in particle.elements:
# compute adjustment move due to unselected obstacles
adjusted_delta=self.adjust_move_element(delta,element,0)
# only keep the smallest delta's (in absolute value)
@ -391,7 +390,7 @@ class Painter(Widget):
# whether newpos is acceptable
accept_newpos=True
for other in self.unselected:
for obstacle in other.squares:
for obstacle in other.elements:
# move would make element overlap with obstacle
element.pos+=delta
if obstacle.check_interaction(element):
@ -448,7 +447,7 @@ class Painter(Widget):
for particle in self.particles:
if type(particle)==Cross:
ff.write("{:d};".format(CROSS_INDEX))
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.squares[0].pos.x,particle.squares[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()
# read configuration from file
@ -561,7 +560,7 @@ class Painter(Widget):
for particle in self.particles:
if type(particle)==Cross:
ff.write("\cross{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
ff.write("{{({:05.2f},{:05.2f})}};\n".format(particle.squares[0].pos.x-self.particles[0].squares[0].pos.x,particle.squares[0].pos.y-self.particles[0].squares[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{document}\n")

View File

@ -1,15 +1,14 @@
import math
import sys
# a polyomino is a collection of elements, defined in elements.py
from kivy.graphics import Color,Line,Rectangle
from point import Point,l_infinity
from tools import isint_nonzero,sgn,in_interval
from point import l_infinity
from element import Element_square
# parent class of all polyominos
class Polyomino():
def __init__(self,**kwargs):
# square elements that make up the polyomino
self.squares=kwargs.get("squares",[])
# elements that make up the polyomino
self.elements=kwargs.get("elements",[])
self.color=kwargs.get("color",(0,0,1))
self.selected=False
@ -28,8 +27,8 @@ class Polyomino():
# darken selected
Color(r/2,g/2,b/2,alpha)
for square in self.squares:
Rectangle(pos=(painter.pos_tocoord_x(square.pos.x-0.5*square.size),painter.pos_tocoord_y(square.pos.y-0.5*square.size)),size=(square.size*painter.base_size,square.size*painter.base_size))
for element in self.elements:
Rectangle(pos=(painter.pos_tocoord_x(element.pos.x-0.5*element.size),painter.pos_tocoord_y(element.pos.y-0.5*element.size)),size=(element.size*painter.base_size,element.size*painter.base_size))
# draw boundary
self.stroke(painter)
@ -42,62 +41,62 @@ class Polyomino():
# white
Color(1,1,1)
for square in self.squares:
for element in self.elements:
Line(points=(
*(coordx-0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size),
*(coordx-0.5*square.size*painter.base_size,coordy+0.5*square.size*painter.base_size),
*(coordx+0.5*square.size*painter.base_size,coordy+0.5*square.size*painter.base_size),
*(coordx+0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size),
*(coordx-0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size)
*(coordx-0.5*element.size*painter.base_size,coordy-0.5*element.size*painter.base_size),
*(coordx-0.5*element.size*painter.base_size,coordy+0.5*element.size*painter.base_size),
*(coordx+0.5*element.size*painter.base_size,coordy+0.5*element.size*painter.base_size),
*(coordx+0.5*element.size*painter.base_size,coordy-0.5*element.size*painter.base_size),
*(coordx-0.5*element.size*painter.base_size,coordy-0.5*element.size*painter.base_size)
))
# move by delta
def move(self,delta):
for square in self.squares:
square.pos+=delta
for element in self.elements:
element.pos+=delta
# whether x is in the support of the polyomino
def in_support(self,x):
for square in self.squares:
if l_infinity(square.pos-x)<=1/2:
for element in self.elements:
if element.in_support(x):
return True
return False
# check whether self interacts with candidate if candidate were moved by offset
def check_interaction(self,candidate,offset):
for square1 in self.squares:
for square2 in candidate.squares:
for element1 in self.elements:
for element2 in candidate.elements:
# add offset
square2.pos+=offset
if square1.check_interaction(square2):
element2.pos+=offset
if element1.check_interaction(element2):
# reset offset
square2.pos-=offset
element2.pos-=offset
return True
# reset offset
square2.pos-=offset
element2.pos-=offset
return False
# square
class Square(Polyomino):
def __init__(self,x,y,**kwargs):
super(Square,self).__init__(**kwargs,squares=[Square_element(x,y,size=kwargs.get("size",1.0))])
super(Square,self).__init__(**kwargs,elements=[Element_square(x,y,size=kwargs.get("size",1.0))])
# cross
class Cross(Polyomino):
def __init__(self,x,y,**kwargs):
super(Cross,self).__init__(**kwargs,squares=[\
Square_element(x,y,1),\
Square_element(x+1,y,1),\
Square_element(x-1,y,1),\
Square_element(x,y+1,1),\
Square_element(x,y-1,1)\
super(Cross,self).__init__(**kwargs,elements=[\
Element_square(x,y,1),\
Element_square(x+1,y,1),\
Element_square(x-1,y,1),\
Element_square(x,y+1,1),\
Element_square(x,y-1,1)\
])
# redefine stroke to avoid lines between touching squares
# redefine stroke to avoid lines between touching elements
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(self.squares[0].pos.x)
coordy=painter.pos_tocoord_y(self.squares[0].pos.y)
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=(
@ -116,130 +115,3 @@ class Cross(Polyomino):
*(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size),
))
# square building block of polyominos
class Square_element():
def __init__(self,x,y,size,**kwargs):
self.pos=Point(x,y)
self.size=size
# set position
def setpos(self,x,y):
self.pos.x=x
self.pos.y=y
# check whether an element interacts with square
def check_interaction(self,element):
# allow for error
return l_infinity(element.pos-self.pos)<(self.size+element.size)/2-1e-11
# check whether an element is touching self
def check_touch(self,element):
# allow for error
if in_interval(l_infinity(element.pos-self.pos),(self.size+element.size)/2-1e-11,(self.size+element.size)/2+1e-11):
return True
return False
# find position along a line that comes in contact with the line going through element.pos in direction v
def move_on_line_to_stick(self,element,v):
# compute intersections with four lines making up square
if v.x!=0:
if v.y!=0:
intersections=[\
Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\
Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x)),\
Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\
Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\
]
else:
intersections=[\
Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\
Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x))
]
else:
if v.y!=0:
intersections=[\
Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\
Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\
]
else:
print("error: move_on_line_to_stick called with v=0, please file a bug report with the developer",file=sys.stderr)
exit(-1)
# compute closest one, on square
closest=None
dist=math.inf
for i in range(0,len(intersections)):
# check that it is on square
if abs(intersections[i].x-self.pos.x)<=(self.size+element.size)/2+1e-11 and abs(intersections[i].y-self.pos.y)<=(self.size+element.size)/2+1e-11:
if (intersections[i]-element.pos)**2<dist:
closest=intersections[i]
dist=(intersections[i]-element.pos)**2
if closest==None:
print("error: cannot move particle at (",pos.x,",",pos.y,") to the boundary of (",self.pos.x,",",self.pos.y,") in direction (",v.x,",",v.y,")",file=sys.stderr)
exit(-1)
# return difference to pos
return closest-element.pos
# move along edge of square
def move_along(self,delta,element):
rel=element.pos-self.pos
# check if the particle is stuck in the x direction
if isint_nonzero(rel.x/((self.size+element.size)/2)):
# check y direction
if isint_nonzero(rel.y/((self.size+element.size)/2)):
# in corner
if sgn(delta.y)==-sgn(rel.y):
# stuck in x direction
return self.move_stuck_x(delta,element)
elif sgn(delta.x)==-sgn(rel.x):
# stuck in y direction
return self.move_stuck_y(delta,element)
# stuck in both directions
return element.pos
else:
# stuck in x direction
return self.move_stuck_x(delta,element)
elif isint_nonzero(rel.y/((self.size+element.size)/2)):
# stuck in y direction
return self.move_stuck_y(delta,element)
# this should never happen
else:
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
exit(-1)
# move when stuck in the x direction
def move_stuck_x(self,delta,element):
# only move in y direction
candidate=Point(0,delta.y)
# do not move past corners
rel=element.pos.y-self.pos.y
if delta.y>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.y+rel>math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.y=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.y+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.y=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
return candidate
# move when stuck in the y direction
def move_stuck_y(self,delta,element):
# onlx move in x direction
candidate=Point(delta.x,0)
# do not move past corners
rel=element.pos.x-self.pos.x
if delta.x>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.x+rel>math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.x=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.x+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
# stick to corner
candidate.x=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
return candidate

View File

@ -45,9 +45,9 @@ class Status_bar(Label):
spaces=int(self.width/self.char_width)-len(self.raw_text)-13
if spaces>0:
if self.app.painter.reference==None:
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].squares[0].pos.x,self.app.painter.selected[0].squares[0].pos.y)
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].elements[0].pos.x,self.app.painter.selected[0].elements[0].pos.y)
else:
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].squares[0].pos.x-self.app.painter.reference.squares[0].pos.x,self.app.painter.selected[0].squares[0].pos.y-self.app.painter.reference.squares[0].pos.y)
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].elements[0].pos.x-self.app.painter.reference.elements[0].pos.x,self.app.painter.selected[0].elements[0].pos.y-self.app.painter.reference.elements[0].pos.y)
# do not wrap
self.text=self.raw_text[:min(len(self.raw_text),int(self.width/self.char_width))]