1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
"""
TrivalentGraphModel.py
$Id$
bug: rubber edges stopped working...
"""
from demoapp.geometry.vectors import V, dot, vlen, unitVector, rotate2d_90
import pyglet
from demoapp.graphics.colors import black
from demoapp.graphics.drawing import drawline2d
from demoapp.models.TrivalentGraph_Graphics import NODE_RADIUS, draw_Node
# ==
class ModelComponent(object): #refile
def hit_test(self, x, y):
return False
class Node(ModelComponent):
radius = NODE_RADIUS
def __init__(self, model, x, y):
self.model = model
self.x = x
self.y = y
self.edges = [] # sequence or set of our edges
def destroy(self):
for e in self.edges[:]:
e.destroy()
self.edges = []
del self.model.nodes[self]
def new_edge_ok(self):
return len(self.edges) < 3
def new_edge_ok_to(self, node):
return self.new_edge_ok() and not self.connected_to(node)
def connected_to(self, node):
for edge in self.edges:
if node in edge.nodes:
return True
return False
def draw(self, highlight_color = None):
draw_Node( self.pos,
self.radius,
numedges = len(self.edges),
highlight_color = highlight_color)
def get_pos(self):
return V(self.x, self.y)
def set_pos(self, pos):
self.x, self.y = pos
pos = property(get_pos, set_pos)
def hit_test(self, x, y): # modified from soundspace.py; made 2d; could improve using vectors.py functions
dx, dy = [a - b for a, b in zip(self.pos, (x, y))]
if dx * dx + dy * dy < self.radius * self.radius:
return -dx, -dy, # return relative position within object
pass
class Edge(ModelComponent):
def __init__(self, model, n1, n2):
assert isinstance(n1, Node)
assert isinstance(n2, Node)
assert n1 is not n2
assert not n1.connected_to(n2)
assert not n2.connected_to(n1)
self.model = model
self.nodes = (n1, n2) # sequence or set of our nodes
for n in self.nodes:
n.edges.append(self)
def destroy(self): # (what about undo?)
del self.model.edges[self]
for n in self.nodes:
n.edges.remove(self)
self.nodes = ()
def draw(self):
drawline2d(black, self.n1.pos, self.n2.pos) # refactor, use in rubber_edge: draw_Edge
@property
def n1(self):
return self.nodes[0]
@property
def n2(self):
return self.nodes[1]
def other(self, node):
if self.nodes[0] is node:
return self.nodes[1]
else:
assert self.nodes[1] is node
return self.nodes[0]
pass
def hit_test(self, x, y):
HALO_RADIUS = 2 # or maybe 1?
p1 = self.nodes[0].pos
p2 = self.nodes[1].pos
direction_along_edge = unitVector(p2 - p1)
unit_normal_to_edge = rotate2d_90(direction_along_edge)
vec = V(x,y) - p1
distance_from_edge_line = abs(dot(vec, unit_normal_to_edge))
if distance_from_edge_line > HALO_RADIUS:
return False
distance_along_edge_line = dot(vec, direction_along_edge)
if HALO_RADIUS <= distance_along_edge_line <= vlen(p2 - p1) - HALO_RADIUS:
# For our purposes, exclude points too near the endpoints --
# matches to a rectangle centered on the edge and not within
# halo radius (along the edge) of the endpoints. (If node radius
# is larger, near-endpoint behavior won't matter anyway, since we
# treat nodes as being in front of edges.)
return True
return False
pass
class TrivalentGraphModel(object):
# REVISE: rename, then split out superclass Model
"""
model for a partial or complete trivalent graph
"""
Node = Node
Edge = Edge
def __init__(self):
self.nodes = {}
self.edges = {}
def cmd_addNode(self, x, y):
n = self.Node(self, x, y)
self.nodes[n] = n
#print "added", n
return n
def cmd_deleteNode(self, n):
for e in list(n.edges):
e.destroy()
del self.nodes[n]
return True # only needed due to an assert that is wrong in general
def cmd_addEdge(self, n1, n2):
e = self.Edge(self, n1, n2)
self.edges[e] = e
#print "added", e
return e
def cmd_addNodeAndConnectFrom(self, x, y, node):
# 'From' in cmdname is because node order within edge
# might matter someday, for a directed graph
node2 = self.cmd_addNode(x, y)
self.cmd_addEdge(node, node2)
return node2
def cmd_addNodeOnEdge(self, x, y, edge):
node2 = self.cmd_addNode(x, y) # todo: position it exactly on edge? if so, worry if mouse still over it re KLUGE elsewhere
n1, n3 = list(edge.nodes) # seq or set; in order, if that matters
edge.destroy()
self.cmd_addEdge(n1, node2)
self.cmd_addEdge(node2, n3)
return node2
def cmd_addNodeOnEdgeAndConnectFrom(self, x, y, edge, node):
node2 = self.cmd_addNodeOnEdge(x, y, edge)
self.cmd_addEdge(node, node2)
return node2
def cmd_MergeNodes(self, node1, node2):
"merge node1 with node2 (retaining properties of node2, e.g. .pos)"
node1_neighbors = [e.other(node1) for e in node1.edges] # might include node2
node1.destroy()
for n in node1_neighbors:
if n is not node2 and not n.connected_to(node2):
self.cmd_addEdge(n, node2)
return True
def hit_test_objects(self): # front to back
for node in self.nodes.itervalues():
yield node
for edge in self.edges.itervalues():
yield edge
def drawn_objects(self):
return self.hit_test_objects() #stub
def draw(self):
for obj in self.drawn_objects():
obj.draw()
pass
|