-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathgtool.py
430 lines (379 loc) · 15.4 KB
/
gtool.py
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
#!/usr/bin/python3
from geo_object import *
from tool_step import ToolStep
from movable_tools import Intersection
import itertools
def run_tuple(f, *args):
if isinstance(f, tuple):
args += f[1:]
f = f[0]
f(*args)
"""
ObjSelector is a class for finding an object in the picture by coordinates
the result is a pair (graphical_index, numerical_representation).
The graphical_index determines e.g. the name of the object, see graphical_env.py.
"""
class ObjSelector:
def __init__(self):
self.find_radius_pix = 20 # radius for selection in pixels
def _update_find_radius(self):
self.find_radius = self.find_radius_pix / self.viewport.scale
# find object from a given list
def coor_to_obj(self, coor, l, filter_f = None):
self._update_find_radius()
if filter_f is not None:
l = ((obj,objn) for (obj,objn) in l if filter_f(obj, objn))
candidates = (
(objn.dist_from(coor), obj, objn)
for (obj,objn) in l
)
d,obj,objn = min(candidates, key = lambda x: x[0], default = (0,None,None))
if d >= self.find_radius: return None, None
return obj, objn
def coor_to_point(self, coor, **kwargs):
return self.coor_to_obj(coor, self.vis.selectable_points, **kwargs)
def coor_to_line(self, coor, **kwargs):
return self.coor_to_obj(coor, self.vis.selectable_lines, **kwargs)
def coor_to_circle(self, coor, **kwargs):
return self.coor_to_obj(coor, self.vis.selectable_circles, **kwargs)
def coor_to_cl(self, coor, **kwargs):
return self.coor_to_obj(
coor,
itertools.chain(self.vis.selectable_lines,self.vis.selectable_circles),
**kwargs
)
# sequential attempts of coor_to_*
def coor_to_attempts(self, coor, *selectors, **kwargs):
for selector in selectors:
obj,objn = selector(coor, **kwargs)
if obj is not None: return obj,objn
return None,None
def coor_to_pcl(self, coor, **kwargs):
return self.coor_to_attempts(coor, self.coor_to_point, self.coor_to_cl, **kwargs)
def coor_to_pl(self, coor, **kwargs):
return self.coor_to_attempts(coor, self.coor_to_point, self.coor_to_line, **kwargs)
def coor_to_pc(self, coor, **kwargs):
return self.coor_to_attempts(coor, self.coor_to_point, self.coor_to_circle, **kwargs)
# when activated
def enter(self, viewport):
self.viewport = viewport
self.env = viewport.env
self.vis = viewport.vis
# when deactivated
def leave(self):
pass
"""
HighlightList is a general class for extra gui elements
(selections, helpers, proposals) drawn by the gtool.
These elements are often build incrementaly,
but some are permanent (already selected items), others are
temporary (the current object under mouse).
Therefore, there is the saved list of the permanent object
(the initial setting before investigating mouse cursor), and
the current list l. When confirmed (click), the current list
becomes the initial list depending on whether the new objects
were added with the "permanent" attribute.
"""
class HighlightList:
def __init__(self):
self.l = []
self.saved = []
def to_list(self):
return [x for (x,permanent) in self.l]
def add(self, *args, permanent = False):
self.l.extend((arg, permanent) for arg in args)
def load(self):
self.l = list(self.ini)
def save(self):
self.ini = [x for x in self.l if x[1]]
def reset_save(self): self.ini = []
def pop(self): return self.l.pop()
def remove(self, x): return self.l.remove((x, True))
def remove_if(self, cond):
self.l = [
(x,perm) for (x,perm) in self.l if not cond(x)
]
"""
The main class for a GUI Tool is GTool, its descendants are used for all the GUI tools
such as point, parallel, move, hide, ...
At every point, there is an update function which is called primarily on mouse movement.
The update function takes the current mouse coordinates (in geometrical coordinates), and
potentionally further extra parameters. It searches for interesting
objects under the mouse cursor and determines what should be done with them.
At the beginning (or after tool restart), the update function is set to be "self.update_basic".
The update function can update the hl_* lists (typically by calling hl_propose, hl_select, hl_add_helper),
and set the following attributes
* self.confirm = function (possibly with arguments) that should be called after click
* self.confirm_next = the next update function after a click (if not set, the tool is reseted after click)
* self.drag_start = function called on clicking followed by a drag
* self.drag = update function during dragging (if not set, dragging is disabled)
"""
class GTool(ObjSelector):
icon_name = None
key_shortcut = None
label = None
def get_icon_name(self): return self.icon_name
def get_cursor(self): return self.icon_name
def get_key_shortcut(self): return self.key_shortcut
def get_label(self):
return "{} ({})".format(self.label, self.key_shortcut.upper())
def __init__(self):
ObjSelector.__init__(self)
self.distance_to_drag_pix = 5
self.hl_proposals = HighlightList()
self.hl_selected = HighlightList()
self.hl_helpers = HighlightList()
self.hl_lists = [self.hl_proposals, self.hl_selected, self.hl_helpers]
def reset(self): # called for example on right click
self.dragged = False
self.click_coor = None
self._hl_reset()
self._pre_update()
self._hl_update()
self.update = self.update_basic
run_tuple(self.on_reset)
### basic manipulation with additional GUI elements
def _hl_reset(self):
for hl_list in self.hl_lists: hl_list.reset_save()
def _hl_load(self):
for hl_list in self.hl_lists: hl_list.load()
def _hl_save(self):
for hl_list in self.hl_lists: hl_list.save()
def _hl_update(self):
self.vis.update_hl_selected(self.hl_selected.to_list())
self.vis.update_hl_proposals(self.hl_proposals.to_list())
self.vis.update_hl_helpers(self.hl_helpers.to_list())
def cursor_away(self):
self._hl_load()
self._hl_update()
# object selection -- combining ObjSelector and HighlightList hl_selected
# the selected object is returned and also added to self.hl_selected
def select_by_coor(self, coor, coor_to_x, permanent = True, **kwargs):
obj,nobj = coor_to_x(coor, **kwargs)
if obj is not None: self.hl_select(obj, permanent = permanent)
return obj,nobj
def select_point(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_point, **kwargs)
def select_line(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_line, **kwargs)
def select_circle(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_circle, **kwargs)
def select_cl(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_cl, **kwargs)
def select_pcl(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_pcl, **kwargs)
def select_pl(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_pl, **kwargs)
def select_pc(self, coor, **kwargs):
return self.select_by_coor(coor, self.coor_to_pc, **kwargs)
def _pre_update(self):
self._hl_load()
self._update_find_radius()
self.drag = None
self.drag_start = None
self.confirm = None
self.confirm_next = None
def hl_propose(self, *args, **kwargs):
self.hl_proposals.add(*args, **kwargs)
def hl_select(self, *args, **kwargs):
self.hl_selected.add(*args, **kwargs)
def hl_add_helper(self, *args, **kwargs):
self.hl_helpers.add(*args, **kwargs)
# function executed primarily on mouse movement
def _run_update(self, coor):
self._pre_update()
run_tuple(self.update, coor)
self._hl_update()
# function executted primarily on click (or drag release)
def _run_confirm(self):
if self.confirm is not None:
run_tuple(self.confirm)
if self.confirm_next is not None:
self.update = self.confirm_next
self._hl_save()
else:
self._hl_reset()
self.update = self.update_basic
run_tuple(self.on_reset)
### externally called functions
def button_press(self, coor):
if self.click_coor is not None: self.reset()
self._run_update(coor)
if self.drag is None:
self._run_confirm()
self._run_update(coor)
else:
self.click_coor = coor
self.dragged = False
if self.drag_start is not None:
run_tuple(self.drag_start)
self._hl_save()
def button_release(self, coor):
if self.click_coor is not None:
click_coor = self.click_coor
self.click_coor = None
self.dragged = False
self._run_confirm()
self._run_update(coor)
def motion(self, coor, button_pressed):
if self.click_coor is not None:
if not button_pressed:
self.reset()
elif not self.dragged:
if np.linalg.norm(coor - self.click_coor) >= \
self.distance_to_drag_pix / self.viewport.scale:
assert(self.drag is not None)
self.update = self.drag
self.dragged = True
if self.click_coor is None or self.dragged:
self._run_update(coor)
### helpers for further descendants for interacting with the logic
def lies_on(self, p, cl):
p_li = self.vis.gi_to_li(p)
cl_li = self.vis.gi_to_li(cl)
if self.vis.li_to_type(cl_li) == Line: label = self.tools.lies_on_l
else: label = self.tools.lies_on_c
return self.vis.logic.get_constr(label, (p_li, cl_li)) is not None
def instantiate_obj(self, obj):
if isinstance(obj, tuple):
cmd, *args = obj
if callable(cmd): obj, = cmd(*args, update = False)
elif isinstance(cmd, str):
obj, = self.run_tool(cmd, *args, update = False)
else: raise Exception("Unexpected command: {}".format(cmd))
return obj
def run_tool(self, tool, *args, update = True):
args = tuple(map(self.instantiate_obj, args))
if isinstance(tool, str):
arg_types = tuple(self.vis.gi_to_type(x) for x in args)
tool = self.tools[tool, arg_types]
step = ToolStep(tool, (), args, len(self.env.gi_to_step_i))
return self.env.add_step(step, update = update)
def run_m_tool(self, name, res_obj, *args, update = True):
args = tuple(map(self.instantiate_obj, args))
num_args = tuple(self.vis.gi_to_num(x) for x in args)
arg_types = tuple(type(x) for x in num_args)
out_type = type(res_obj)
tool = self.tools.m[name, arg_types, out_type]
hyper_params = tool.get_hyperpar(res_obj, *num_args)
step = ToolStep(tool, hyper_params, args, len(self.env.gi_to_step_i))
#if name == "intersection":
# print(arg_types, hyper_params)
return self.env.add_step(step, update = update)
### extended methods of ObjSelector
def enter(self, viewport):
ObjSelector.enter(self, viewport)
self.tools = self.env.tools
self.reset()
def leave(self):
self.reset()
ObjSelector.leave(self)
# to be implemented
def on_reset(self):
pass # on right click, unexpected click, or other case of reseting to the default state
def update_basic(self, coor):
pass # investigating in the default (starting) phase
### Simple GTools
# dummy GTool, when no tool is selected
class GToolNone:
def get_icon_name(self): return None
def get_cursor(self): return None
def get_key_shortcut(self): return None
def get_label(self): return None
def reset(self): pass
def cursor_away(self): pass
def button_press(self, coor): pass
def button_release(self, coor): pass
def motion(self, coor, button_pressed): pass
def enter(self, viewport): pass
def leave(self): pass
# applied during holding shift
class AmbiSelect(ObjSelector):
def click(self, coor, rev):
obj,_ = self.coor_to_pcl(coor)
if obj is None: return
direction = 1 if rev else -1
self.vis.swap_priorities(obj, direction)
def enter(self, viewport):
ObjSelector.enter(self, viewport)
self.vis.ambi_select_mode = True
self.vis.visible_export()
def leave(self):
self.vis.ambi_select_mode = False
self.vis.visible_export()
ObjSelector.leave(self)
class GToolMove(GTool):
icon_name = "move"
key_shortcut = 'm'
label = "Move Tool"
def get_cursor(self):
if self.click_coor is None: return "grab"
else: return "grabbing"
def __init__(self):
GTool.__init__(self)
self.distance_to_drag_pix = 0
def update_basic(self, coor):
obj,objn = self.select_pcl(coor, permanent = False)
if obj is None: return
step = self.env.gi_to_step(obj)
num_args = tuple(self.vis.gi_to_num(gi) for gi in step.local_args)
num_res = self.vis.gi_to_num(obj),
grasp = step.tool.get_grasp(coor, *num_args+num_res)
self.drag = self.move_obj, step, grasp, num_args
self.drag_start = self.move_start
def move_start(self):
self.viewport.set_cursor_by_tool()
self.on_reset = self.viewport.set_cursor_by_tool
def move_obj(self, coor, step, grasp, num_args):
tool = step.tool
step.hyper_params = tool.new_hyperpar(grasp, coor, *num_args)
if isinstance(tool, Intersection):
intersections = tool.ordered_candidates(num_args)
i, = step.hyper_params
self.hl_propose(Point(intersections[1-i]))
self.env.refresh_steps(catch_errors = True)
self.env.update_hyperpar_hook(step)
def enter(self, viewport):
ObjSelector.enter(self, viewport)
self.reset()
self.vis.move_mode = True
self.vis.refresh()
def leave(self):
self.vis.move_mode = False
self.vis.refresh()
GTool.leave(self)
class GToolHide(GTool):
icon_name = "hide"
key_shortcut = 'h'
label = "Hide Tool"
def get_cursor(self):
if self.vis.show_all_mode: return "unhide"
else: return "hide"
def update_basic(self, coor):
obj,_ = self.select_pcl(coor)
if obj is None:
self.confirm = self.set_show_all, True
self.confirm_next = self.update_unhide
else:
self.confirm = self.vis.hide, obj
def update_unhide(self, coor):
def is_hidden(gi, num):
return self.vis.gi_to_hidden[gi]
obj,_ = self.select_pcl(coor, filter_f = is_hidden)
if obj is None:
self.confirm = self.set_show_all, False
else:
self.confirm = self.unhide, obj
def unhide(self, obj):
self.vis.gi_to_hidden[obj] = False
self.set_show_all(False)
def leave(self):
self.set_show_all(False)
GTool.leave(self)
def on_reset(self):
self.set_show_all(False)
def set_show_all(self, value):
if self.vis.show_all_mode != value:
self.vis.show_all_mode = value
self.viewport.set_cursor_by_tool()
self.vis.refresh()