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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
# Copyright 2004-2008 Nanorex, Inc. See LICENSE file for details.
"""
handles.py - graphical handles used in Extrude Mode.
Deprecated for new code, since they don't use the Selobj_API
used by most handle-like things.
@author: Bruce
@version: $Id$
@copyright: 2004-2008 Nanorex, Inc. See LICENSE file for details.
TODO:
Needs cleanup.
"""
from Numeric import sqrt
from geometry.VQT import V
from geometry.VQT import vlen
from geometry.VQT import norm
from geometry.VQT import orthodist
from graphics.drawing.CS_draw_primitives import drawsphere
from utilities.constants import ave_colors
from utilities.constants import magenta
from utilities.constants import blue
class handleWithHandleSet:
"""
used to wrap handles returned from a handleset, so they can use its methods
"""
def __init__(self, handle, handleset, copy_id = None):
self.handle = handle
self.handleset = handleset
self.copy_id = copy_id
def click(self): #e now leftDown, but later, "button press" leftUp
self.handleset.click_handle( self.handle, self.copy_id)
def move(self, motion):
self.handleset.move_handle( self.handle, self.copy_id, motion )
def __repr__(self):
return "handleWithHandleSet( %r, %r, %r)" % (self.handle, self.handleset, self.copy_id)
def __str__(self):
return self.handleset.str_handle( self.handle, self.copy_id)
def leftDown_status_msg(self):
return self.handleset.leftDown_status_msg( self.handle, self.copy_id)
pass
class HandleSet:
"""
maintain a set of spheres, able to be intersected with a ray, or a 3d point
"""
color = (0.5,0.5,0.5) # default color (gray50)
radius_multiplier = 1.0 # this might be patched to some other value by our owner;
# should affect all internal computations using radii, but not returned radii inside handle tuples ####NIM
def __init__(self):
self.origin = V(0,0,0) # changed from this only by certain subclasses, in practice
self.handles = [] # list of (pos,radius,info) tuples
# handlpos and maxradius are not used now, but might be used later to optimize this.
self.handlpos = [] # list of their pos's (compare to singlpos in class Chunk (_recompute_singlpos))
self.maxradius = 0.01 # not true, but best if it's always positive, I think
#e to optimize, we might want a "compile" method which caches Array versions of these lists
def addHandle(self, pos, radius, info):
"""
add a handle of the given position, radius, and info,
and return its index, unique in this Set
"""
self.handles.append((pos,radius,info))
self.handlpos.append(pos)
if radius > self.maxradius:
self.maxradius = radius
return len(self.handles) # index of new handle #e needed?
def compile(self): #e unused??
pass #e cache Array versions of some of our lists
def move(self, offset):
self.origin = self.origin + offset
## warning: this would be wrong (due to destructive mod of a vector): self.origin += motion
def draw(self, glpane, offset = V(0,0,0), color = None, info = {}): # this code is copied/modified into a subclass, sorry
"""
draw our spheres (in practice we'll need to extend this for different sets...)
"""
## self.radius_multiplier = 1.0 # this might be changed by certain subclass's process_optional_info method
## self.process_optional_info(info) # might reset instvars that affect following code... (kluge?)
color = color or self.color
##detailLevel = 0 # just an icosahedron
detailLevel = 1 # easier to click on this way
##radius = 0.33 # the one we store might be too large? no, i guess it's ok.
#e (i might prefer an octahedron, or a miniature-convex-hull-of-extrude-unit)
offset = offset + self.origin
radius_multiplier = self.radius_multiplier
color = tuple(color) + (1.0,) ## experiment 050218: alpha factor #bruce 051230 moved outside of loop to fix bug 1250
for (pos,radius,info) in self.handles:
drawsphere(color, pos + offset, radius * radius_multiplier, detailLevel)
## def process_optional_info(self, info):
## "some subclasses should override this to let info affect draw method"
## pass
def findHandles_containing(self, point):
"""
return a list of all the handles (in arbitrary order)
which (as balls) contain the given 3d point
"""
res = []
for (pos,radius,info) in self.handles: #e revise this code if we cluster them, esp with bigger radius
if vlen(point - pos) <= radius * self.radius_multiplier:
res.append((pos,radius,info))
return res
## def findHandles_near(self, point, radius = None):
## """return a list (in arbitrary order) of pairs (dist, handle) for all the handles
## which are near the given point (within the given radius (default very large###e???),
## *or* within their own sphere-radius). #### WRONG CODE
## """
## assert 0
def findHandles_exact(self, p1, p2, cutoff = 0.0, backs_ok = 1, offset = V(0,0,0)):
"""
@return: a list of (dist, handle) pairs, in arbitrary order, which
includes, for each handle (spherical surface) hit by the ray from p1
thru p2, its front-surface intersection with the ray, unless that has
dist < cutoff and backs_ok, in which case include its back-surface
intersection (unless *that* has dist < cutoff).
"""
#e For now, just be simple, don't worry about speed.
# Someday we can preprocess self.handlpos using Numeric functions,
# like in nearSinglets and/or findSinglets
# (I have untested prototype code for this in extrude-outs.py).
hh = self.handles
res = []
v = norm(p2-p1)
# is this modifying the vector in-place, causing a bug??
## offset += self.origin # treat our handles' pos as relative to this
# I don't know, but one of the three instances of += was doing this!!!
# probably i was resetting the atom or mol pos....
offset = offset + self.origin # treat our handles' pos as relative to this
radius_multiplier = self.radius_multiplier
for (pos,radius,info) in hh:
## bug in this? pos += offset
pos = pos + offset
radius *= radius_multiplier
dist, wid = orthodist(p1, v, pos)
if radius >= wid: # the ray hits the sphere
delta = sqrt(radius*radius - wid*wid)
front = dist - delta # depth from p1 of front surface of sphere, where it's hit
if front >= cutoff:
res.append((front,(pos,radius,info)))
elif backs_ok:
back = dist + delta
if back >= cutoff:
res.append((back,(pos,radius,info)))
return res
def frontDistHandle(self, p1, p2, cutoff = 0.0, backs_ok = 1, offset = V(0,0,0), copy_id = None):
"""
@return: None, or the frontmost (dist, handle) pair, as computed by
findHandles_exact; but turn the handle into a pyobj for convenience of
caller.
"""
# check: i don't know if retval needs self.radius_multiplier...
# review: will we need to let caller know whether it was the front or
# back surface we hit? or even the exact position on the sphere? if
# so, add more data to the returned pair.
dhdh = self.findHandles_exact(p1, p2, cutoff, backs_ok, offset = offset)
if not dhdh: return None
dhdh.sort()
(dist, handle0) = dhdh[0]
handle = self.wrap_handle(handle0, copy_id)
return (dist, handle)
def wrap_handle(self, handle, copy_id):
(pos,radius,info) = handle # check format
return handleWithHandleSet( handle, self, copy_id = copy_id)
def click_handle( self, handle, copy_id):
"click one of our own handles; subclasses should override this"
pass
def move_handle( self, handle, copy_id, motion):
"move one of our own handles (e.g. when it's dragged); subclasses should override this"
pass
def str_handle( self, handle, copy_id):
"subclasses are encouraged to override this so it looks good in messages to the user"
(pos,radius,info) = handle
return "<handle pos=%r, radius=%3f, info=%r, in copy %r of %r>" % (pos,radius,info,copy_id,self)
def leftDown_status_msg( self, handle, copy_id):
"subclasses should override this"
return ""
def handle_setpos( self, ind, newpos):
"patch our arrays to change pos of just one handle"
(pos,radius,info) = self.handles[ind]
pos = newpos
self.handles[ind] = (pos,radius,info)
self.handlpos[ind] = pos #e might fail if we ever make self.compile() do something
pass
pass
class draggableHandle_HandleSet(HandleSet):
"a handleset, with behavior to let you drag the entire thing, and something else too" # used for "purple center"...
def __init__(self, color = magenta, motion_callback = None, statusmsg = "draggable handle"):
self.color = color
self.motion_callback = motion_callback
self.statusmsg = statusmsg
HandleSet.__init__(self)
def move_handle( self, handle, copy_id, motion):
self.move(motion)
if self.motion_callback:
self.motion_callback(motion)
return
def leftDown_status_msg( self, handle, copy_id):
return self.statusmsg
pass
class repunitHandleSet(HandleSet): #e this really belongs in extrudeMode.py, not in this file
"a handleset for an extrudeable unit, which lets copy_id specify which repunit is meant"
def __init__(self, *args, **kws):
self.target = kws.pop('target') # must be specified; the extrudeMode object we're helping to control
HandleSet.__init__(self, *args, **kws)
def move_handle( self, handle, copy_id, motion):
self.target.drag_repunit(copy_id, motion)
def hset_name(self, copy_id): #e move this name code to the hset itself
if copy_id:
name = "repeat unit #%d" % copy_id
else:
name = "base unit"
return name
def str_handle( self, handle, copy_id):
name = self.hset_name(copy_id)
(pos,radius,info) = handle
return "atom at pos=%r in %s" % (pos,name)
def leftDown_status_msg( self, handle, copy_id):
name = self.hset_name(copy_id)
if copy_id:
return "%s (can be dragged to change the offset)" % name
else:
return name # + " (can be dragged to move the entire model -- not implemented)"
pass
pass
class niceoffsetsHandleSet(HandleSet): #e this really belongs in extrudeMode.py, not in this file
"a handleset for holding the set of nice offsets"
#e in future, we can show things on mouseover, perhaps like:
# mouseover of nice_offset handle will light up its two singlets;
# mouseover of a singlet will light up the applicable other singlets
# and nice_offset handles; etc.
special_pos = V(0,0,0)
special_color = blue
def __init__(self, *args, **kws):
self.target = kws.pop('target') # must be specified; the extrudeMode object we're helping to control
HandleSet.__init__(self, *args, **kws)
def leftDown_status_msg( self, handle, copy_id):
(pos,radius,info) = handle
i1,i2 = info
# this text is meant for the present situation, where we jump on mousedown;
# better behavior would be buttonlike (jump on mouseup, iff still over handle),
# and the text should be adjusted when we implement that #e
return "overlapped bondpoints base#%d and rep#%d" % (i1,i2)
def click_handle( self, handle, copy_id):
self.target.click_nice_offset_handle(handle)
## def process_optional_info(self, info):
## bond_tolerance = info.get('bond_tolerance',1.0)
## self.radius_multiplier = bond_tolerance # affects draw() method
def draw(self, glpane, offset = V(0,0,0), color = None, info = {}): # modified copy of superclass draw method
"draw our spheres (in practice we'll need to extend this for different sets...)"
## self.radius_multiplier = 1.0 # this might be changed by certain subclass's process_optional_info method
## self.process_optional_info(info) # might reset instvars that affect following code... (kluge?)
color = color or self.color
##detailLevel = 0 # just an icosahedron
detailLevel = 1 # easier to click on this way
##radius = 0.33 # the one we store might be too large? no, i guess it's ok.
#e (i might prefer an octahedron, or a miniature-convex-hull-of-extrude-unit)
offset = offset + self.origin
radius_multiplier = self.radius_multiplier
special_pos = self.special_pos # patched in ###nim?
special_pos = special_pos + offset #k?? or just self.origin??
special_color = self.special_color # default is used
## count = 0
for (pos,radius,info) in self.handles:
radius *= radius_multiplier
pos = pos + offset
dist = vlen(special_pos - pos)
if dist <= radius:
color2 = ave_colors( 1.0 - dist/radius, special_color, color )
## count += 1
else:
color2 = color
## experiment 050218: add alpha factor to color
color2 = tuple(color2) + (0.25,)
drawsphere(color2, pos, radius, detailLevel)
## self.color2_count = count # kluge, probably not used since should equal nbonds
return
pass
# we'll use the atoms of the mols
# and a sep set or two
# of special ones
# and singlet-pair spheres (maybe variabe size)
# ok to only use spheres for now
# if the api could permit changing that
# we don't want to force every stored handle to be an object!
# ..
# each handleset can compute, for one of its handles:
# what to do on mouseover
# change their color
# give them a status message
# (change cursor?)
# what to do if you click, drag, mouseup (click - like button) (true button down/off/up behavior would be good)
# what to do for a long drag
# just these kinds for now:
# rep unit atoms
# base unit atoms
# magenta
# white
# end
|