OSDN Git Service

Changed coding style with pep8
[arescue/arescue.git] / utils / modeleditor / ctrmodel.py
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 """CtrModel edit TK widget.
4
5 CtrModel is widget for model edit.
6
7 """
8 import copy
9 from Tkinter import *
10
11 import Image
12 import ImageDraw
13 import ImageTk
14
15 import settings
16
17 from common.color import Color
18 from common.history import History
19 import common.model as model
20 from common.polygon import Polygon
21 from common.vertex import Vertex
22
23 from mark import Mark
24
25 # edit panel
26 COL_BACK = (0, 0, 0)
27 COL_GRID_LINE1 = (32, 32, 32)
28 COL_GRID_LINE2 = (128, 128, 128)
29 GRID_STEP = 64
30 # depth buffer flag
31 FLAG_NONE = 0
32 FLAG_POLYGON = 1
33 FLAG_VERTEXMARK = 2
34 # polygon
35 MAX_POLYGONS = 256
36
37 __all__ = ["CtrModel"]
38
39
40 class CtrModel(Frame):
41
42     def _getdepth(self, x, y):
43         """get drawed object at position.
44
45         This method read depth buffer and return return tuple.
46         (flag, p_idx, v_idx)
47         flag : flag(0:None, 1:Polygon, 2:Vertexmark)
48         p_idx: polygon index
49         v_idx: vertex index
50         """
51         return self.imdp.getpixel((x, y))
52
53     def _drawgrid(self, draw):
54         """draw grid
55         """
56         cx = self.width / 2
57         cy = self.height / 2
58         # center grid
59         col = COL_GRID_LINE1
60         step = self.scale_self * GRID_STEP
61         begin = 0
62         end = (((max(self.width, self.height) // 2) // step) + 1) * step
63         for d in range(begin, end, step):
64             draw.line((cx + d, 0, cx + d, self.height), fill=col)
65             draw.line((cx - d, 0, cx - d, self.height), fill=col)
66             draw.line((0, cy + d, self.width, cy + d), fill=col)
67             draw.line((0, cy - d, self.width, cy - d), fill=col)
68         # cell grid
69         col = COL_GRID_LINE2
70         step = self.scale_self * GRID_STEP
71         begin = step // 2
72         end = (((max(self.width, self.height) // 2) // step) + 1) * step
73         for d in range(begin, end, step):
74             draw.line((cx + d, 0, cx + d, self.height), fill=col)
75             draw.line((cx - d, 0, cx - d, self.height), fill=col)
76             draw.line((0, cy + d, self.width, cy + d), fill=col)
77             draw.line((0, cy - d, self.width, cy - d), fill=col)
78
79     def _drawmodel(self, md):
80         """draw model
81         """
82         draw = ImageDraw.Draw(self.im)
83         drawdp = ImageDraw.Draw(self.imdp)  # depth buffer
84         w, h = self.imdp.size
85         box = (0, 0, w - 1, h - 1)
86         draw.rectangle(box, fill=COL_BACK)
87         drawdp.rectangle(box, fill=(FLAG_NONE, 0, 0))
88         self._drawgrid(draw)
89         self.marks = []
90         for p_idx, polygon in enumerate(md.polygons):
91             vl = [(v.x, v.y) for v in polygon.vertexs]
92             fill = tuple(polygon.fill)
93             outline = tuple(polygon.outline)
94             draw.polygon(vl, fill=fill, outline=outline)
95             drawdp.polygon(vl, fill=(FLAG_POLYGON, p_idx, 0))
96             for v_idx, vertex in enumerate(polygon.vertexs):
97                 x = int(vertex.x)
98                 y = int(vertex.y)
99                 r = Mark.getr()
100                 mark = Mark(x, y, p_idx, v_idx)
101                 box = (x - r, y - r, x + r, y + r)
102                 drawdp.chord(box, 0, 360,
103                              fill=(FLAG_VERTEXMARK, p_idx, v_idx))
104                 if self.mark_display:
105                     if self.is_markselected(p_idx, v_idx):
106                         fill = Mark.getoutline()
107                         outline = Mark.getfill()
108                     else:
109                         fill = Mark.getfill()
110                         outline = Mark.getoutline()
111                     draw.chord(box, 0, 360, fill=fill, outline=outline)
112                 self.marks.append(mark)
113
114     def _drawcalcmodel(self):
115         """scale, move and draw model
116         """
117         move = (- self.width / 2, - self.height / 2, 0)
118         mdc = self.md.calc(move, None, None, None, self.scale_self, None)
119         self._drawmodel(mdc)
120
121     def __init__(self, master=None, fname='', width=480, height=480, scale=4):
122         Frame.__init__(self, master)
123         # basic setting
124         self.width = width
125         self.height = height
126         self.scale_self = scale
127
128         if fname:
129             self.filename = "%s/%s" % (settings.DATAPATH, fname)
130             self.md = model.load(self.filename)
131         else:
132             self.filename = "%s" % settings.DATAPATH
133             self.md = model.new()
134         self.marks = []
135         self.marks_selected = []
136         self.click_pos = None
137         self.drag_pos = None
138         self.mark_display = True
139
140         # history
141         self.history = History()
142         self.push()
143
144         # depth buffer
145         self.imdp = Image.new('RGB', (self.width, self.height), (0, 0, 0))
146         self.im = Image.new('RGB', (self.width, self.height), (0, 0, 0))
147         self._drawcalcmodel()
148         self.imagetk = ImageTk.PhotoImage(self.im)
149
150         # main panel
151         self.la = Label(self,
152                         image=self.imagetk,
153                         bg="#000000",
154                         width=self.width,
155                         height=self.height)
156         self.la.bind("<Button-1>", self.selectmark)
157         self.la.bind("<Control-Button-1>", self.addmark)
158         self.la.bind("<Button-2>", self.shortcut)
159         self.la.bind("<Button-3>", self.popupmenu)
160         self.la.bind("<B1-Motion>", self.movevertex)
161         self.la.bind("<B2-Motion>", self.movevertex)
162         self.la.bind("<ButtonRelease-1>", self.release)
163         self.la.bind("<ButtonRelease-2>", self.release)
164         self.la.pack(side=LEFT)
165
166         # polygon menu
167         self.mn_polygon = Menu(self, tearoff=0)
168         self.mn_polygon.add_command(label="Bring to front",
169                                     command=self.front)
170         self.mn_polygon.add_command(label="Bring forward",
171                                     command=self.forward)
172         self.mn_polygon.add_command(label="Send backward",
173                                     command=self.backward)
174         self.mn_polygon.add_command(label="Send to back",
175                                     command=self.back)
176         self.mn_polygon.add("separator")
177         self.mn_polygon.add_command(label="clone", command=self.clonepolygon)
178
179         # vertex menu
180         self.mn_vertex = Menu(self, tearoff=0)
181         self.mn_vertex.add_command(label="copy this vertex",
182                                    command=self.addvertex)
183         self.mn_vertex.add_command(label="del this vertex",
184                                    command=self.delvertex)
185
186     def getundodata(self):
187         """get undo data object
188         """
189         return {
190             'width': self.width,
191             'height': self.height,
192             'scale_self': self.scale_self,
193             'filename': self.filename,
194             'md': self.md,
195             'marks': self.marks,
196             'marks_selected': self.marks_selected,
197             'click_pos': self.click_pos,
198             'mark_display': self.mark_display,
199             }
200
201     def setundodata(self, data):
202         """set undo data object
203         """
204         self.width = data['width']
205         self.height = data['height']
206         self.scale_self = data['scale_self']
207         self.filename = data['filename']
208         self.md = data['md']
209         self.marks = data['marks']
210         self.marks_selected = data['marks_selected']
211         self.click_pos = data['click_pos']
212         self.mark_display = data['mark_display']
213
214     # push
215     def push(self):
216         self.history.push(self.getundodata())
217
218     # undo
219     def undo(self):
220         self.setundodata(self.history.undo())
221         self.redraw()
222
223     # redo
224     def redo(self):
225         self.setundodata(self.history.redo())
226         self.redraw()
227
228     def is_markselected(self, p_idx, v_idx):
229         for mark in self.marks_selected:
230             if mark.issame(p_idx, v_idx):
231                 return True
232         return False
233
234     def search_mark(self, p_idx, v_idx):
235         for mark in self.marks:
236             if not mark.issame(p_idx, v_idx):
237                 continue
238             return mark
239         return None
240
241     def redraw(self):
242         self._drawcalcmodel()
243         self.imagetk = ImageTk.PhotoImage(self.im)
244         self.la.config(image=self.imagetk)
245
246     # access
247     def getfilename(self):
248         return self.filename
249
250     def load(self, filename):
251         self.md = model.load(filename)
252         self.filename = filename
253         self.redraw()
254         self.push()
255
256     def save(self):
257         if self.filename:
258             model.save(self.filename, self.md)
259
260     def saveas(self, filename):
261         model.save(filename, self.md)
262         self.filename = filename
263
264     def getfill(self):
265         if len(self.marks_selected):
266             p_idx = self.marks_selected[-1].p_idx
267             fill = self.md.getfill(p_idx)
268             return (fill.r, fill.g, fill.b)
269         else:
270             return None
271
272     def setfill(self, r, g, b):
273         if len(self.marks_selected):
274             p_idx = self.marks_selected[-1].p_idx
275             self.md.setfill(p_idx, Color(r, g, b))
276             self.redraw()
277             self.push()
278
279     def getoutline(self):
280         if len(self.marks_selected):
281             p_idx = self.marks_selected[-1].p_idx
282             outline = self.md.getoutline(p_idx)
283             return (outline.r, outline.g, outline.b)
284         else:
285             return None
286
287     def setoutline(self, r, g, b):
288         if len(self.marks_selected):
289             p_idx = self.marks_selected[-1].p_idx
290             self.md.setoutline(p_idx, Color(r, g, b))
291             self.redraw()
292             self.push()
293
294     def markon(self):
295         self.mark_display = True
296         self.redraw()
297
298     def markoff(self):
299         self.mark_display = False
300         self.redraw()
301
302     def getname(self):
303         return self.md.name
304
305     def setname(self, name):
306         self.md.name = name
307
308     # polygon
309     def addpolygon(self):
310         if len(self.md.polygons) < MAX_POLYGONS:
311             if len(self.marks_selected):
312                 p_idx = self.marks_selected[-1].p_idx
313             else:
314                 p_idx = -1
315             polygon = Polygon([
316                 Vertex(-10, -10, 0),
317                 Vertex(10, -10, 0),
318                 Vertex(10, 10, 0),
319                 Vertex(-10, 10, 0),
320                 ])
321             self.md.addPolygon(p_idx, polygon)
322             self.redraw()
323             self.push()
324
325     def clonepolygon(self):
326         if len(self.md.polygons) < MAX_POLYGONS:
327             if len(self.marks_selected):
328                 p_idx = self.marks_selected[-1].p_idx
329             else:
330                 p_idx = -1
331             # copy Poligon
332             polygon = copy.deepcopy(self.md.polygons[p_idx])
333             self.md.addPolygon(p_idx, polygon)
334             self.redraw()
335             self.push()
336
337     def delpolygon(self):
338         if len(self.md.polygons) > 1:
339             if len(self.marks_selected):
340                 p_idx = self.marks_selected[-1].p_idx
341                 self.md.delPolygon(p_idx)
342                 self.marks_selected = []
343             self.redraw()
344             self.push()
345
346     # vertex
347     def addvertex(self):
348         if not self.click_pos:
349             return
350         mx = self.click_pos['x']
351         my = self.click_pos['y']
352         (flag, p_idx, v_idx) = self._getdepth(mx, my)
353         if flag == FLAG_POLYGON:
354             pass
355         elif flag == FLAG_VERTEXMARK:
356             if self._lenvertexs(p_idx) < 256:
357                 mark = self.search_mark(p_idx, v_idx)
358                 self.marks_selected = [mark]
359                 v = self.md.getvertex(p_idx, v_idx)
360                 self.md.addvertex(p_idx, v_idx, Vertex(v.x, v.y, v.z))
361                 mark.v_idx = v_idx + 1
362                 self.redraw()
363                 #self.push()
364
365     def delvertex(self):
366         if not self.click_pos:
367             return
368         mx = self.click_pos['x']
369         my = self.click_pos['y']
370         self.click_pos = None
371         (flag, p_idx, v_idx) = self._getdepth(mx, my)
372         if flag == FLAG_POLYGON:
373             pass
374         elif flag == FLAG_VERTEXMARK:
375             if self._lenvertexs(p_idx) > 3:
376                 self.md.delvertex(p_idx, v_idx)
377                 self.marks_selected = []
378                 self.click_pos = None
379                 self.redraw()
380                 self.push()
381
382     # utils
383     def _lenpolygons(self):
384         return len(self.md.polygons)
385
386     def _lenvertexs(self, p_idx):
387         return len(self.md.polygons[p_idx].vertexs)
388
389     def _addmarkpolygon(self, p_idx):
390         """add mark to maks_selected
391         """
392         for mark in self.marks:
393             if mark.p_idx == p_idx:
394                 self.marks_selected.append(mark)
395
396     def _delmarkpolygon(self, p_idx):
397         """del mark to maks_selected
398         """
399         marks = []
400         for mark in self.marks_selected:
401             if mark.p_idx != p_idx:
402                 marks.append(mark)
403         self.marks_selected = marks
404
405     def _ismarkpolygon(self, p_idx):
406         """is polygon selected
407         """
408         vidxs = []
409         for mark in self.marks_selected:
410             if mark.p_idx == p_idx:
411                 vidxs.append(mark.v_idx)
412
413         for mark in self.marks:
414             if mark.p_idx == p_idx:
415                 if mark.v_idx in vidxs:
416                     continue
417                 else:
418                     return False
419         return True
420
421     def _syncmark(self):
422         """sync mark position
423         """
424         for mark in self.marks_selected:
425             org = self.search_mark(mark.p_idx, mark.v_idx)
426             mark.x = org.x
427             mark.y = org.y
428
429     def _pidxlist(self):
430         """p_idx list
431         """
432         pidxs = []
433         for mark in self.marks_selected:
434             if mark.p_idx in pidxs:
435                 continue
436             pidxs.append(mark.p_idx)
437         pidxs.sort()
438         return pidxs
439
440     def _screentomodel(self, sx, sy):
441         x = (sx - self.width / 2) / self.scale_self
442         y = (sy - self.height / 2) / self.scale_self
443         return x, y
444
445     # scale
446     def scalemin(self):
447         return 1
448
449     def scalemax(self):
450         return 4
451
452     def isscalemax(self):
453         if self.scale_self == self.scalemax():
454             return True
455         return False
456
457     def isscalemin(self):
458         if self.scale_self == self.scalemin():
459             return True
460         return False
461
462     def setscale(self, scale):
463         self.scale_self = scale
464         self.marks_selected = []
465         self.redraw()
466         self.push()
467
468     # order
469     def update_markidx(self, p_idx, new_idx):
470         for mark in self.marks_selected:
471             if mark.p_idx == p_idx:
472                 mark.p_idx = new_idx
473
474     def backward(self):
475         pidxs = self._pidxlist()
476         for p_idx in pidxs:
477             ismove = self.md.backward(p_idx)
478             if ismove:
479                 self.update_markidx(p_idx, p_idx - 1)
480         self.redraw()
481         self.push()
482
483     def forward(self):
484         pidxs = self._pidxlist()
485         pidxs.reverse()
486         for p_idx in pidxs:
487             ismove = self.md.forward(p_idx)
488             if ismove:
489                 self.update_markidx(p_idx, p_idx + 1)
490         self.redraw()
491         self.push()
492
493     def back(self):
494         pidxs = self._pidxlist()
495         pidxs.reverse()
496         for idx, p_idx in enumerate(pidxs):
497             self.md.back(p_idx + idx)
498         pidxs.reverse()
499         for idx, p_idx in enumerate(pidxs):
500             self.update_markidx(p_idx, idx + self._lenpolygons())
501         for idx, p_idx in enumerate(pidxs):
502             self.update_markidx(idx + self._lenpolygons(), idx)
503         self.redraw()
504         self.push()
505
506     def front(self):
507         pidxs = self._pidxlist()
508         for idx, p_idx in enumerate(pidxs):
509             self.md.front(p_idx - idx)
510         pidxs.reverse()
511         for idx, p_idx in enumerate(pidxs):
512             self.update_markidx(p_idx, idx + self._lenpolygons())
513         for idx, p_idx in enumerate(pidxs):
514             self.update_markidx(idx + self._lenpolygons(),
515                                 self._lenpolygons() - len(pidxs) + idx)
516         self.redraw()
517         self.push()
518
519     # event
520     def selectmark(self, event):
521         self.marks_selected = []
522         self.addmark(event)
523
524     def addmark(self, event):
525         mx = event.x
526         my = event.y
527         self.click_pos = None
528         (flag, p_idx, v_idx) = self._getdepth(mx, my)
529         if flag == FLAG_NONE:
530             return
531         elif flag == FLAG_POLYGON:
532             if self._ismarkpolygon(p_idx):
533                 self._delmarkpolygon(p_idx)
534             else:
535                 self._addmarkpolygon(p_idx)
536             # sync mark
537             self._syncmark()
538         elif flag == FLAG_VERTEXMARK:
539             mark_new = self.search_mark(p_idx, v_idx)
540             for mark in self.marks_selected:
541                 if mark.issame(p_idx, v_idx):
542                     self.marks_selected.remove(mark)
543                     break
544             else:
545                 self.marks_selected.append(mark_new)
546             # sync mark
547             self._syncmark()
548
549         # polygon or vertex
550         self.click_pos = {
551             'x': mx,
552             'y': my,
553             }
554         self.redraw()
555         #self.push()
556
557     def movevertex(self, event):
558         if len(self.marks_selected) and self.click_pos:
559             mx = event.x
560             my = event.y
561             ox = self.click_pos['x']
562             oy = self.click_pos['y']
563             rx = (mx - ox)
564             ry = (my - oy)
565             for mark in self.marks_selected:
566                 p_idx = mark.p_idx
567                 v_idx = mark.v_idx
568                 v = self.md.getvertex(p_idx, v_idx)
569                 v.x, v.y = self._screentomodel(mark.x + rx, mark.y + ry)
570             self.drag_pos = {
571                 'x': mx,
572                 'y': my,
573                 }
574             self.redraw()
575             #self.push()
576
577     def release(self, event):
578         if self.click_pos and self.drag_pos:
579             self.push()
580         self.click_pos = None
581         self.drag_pos = None
582
583     def shortcut(self, event):
584         self.marks_selected = []
585         mx = event.x
586         my = event.y
587         self.click_pos = None
588         (flag, p_idx, v_idx) = self._getdepth(mx, my)
589         if flag == FLAG_POLYGON:
590             pass
591         elif flag == FLAG_VERTEXMARK:
592             self.click_pos = {
593                 'x': mx,
594                 'y': my,
595                 }
596             self.addvertex()
597
598     def popupmenu(self, event):
599         mx = event.x
600         my = event.y
601         self.click_pos = None
602         (flag, p_idx, v_idx) = self._getdepth(mx, my)
603         if flag == FLAG_POLYGON:
604             self.mn_polygon.post(event.x_root, event.y_root)
605         elif flag == FLAG_VERTEXMARK:
606             self.click_pos = {
607                 'x': mx,
608                 'y': my,
609                 }
610             self.mn_vertex.post(event.x_root, event.y_root)
611
612     def center_h(self):
613         self.center(move_x=True)
614
615     def center_v(self):
616         self.center(move_y=True)
617
618     def center_hv(self):
619         self.center(move_x=True, move_y=True)
620
621     def center(self, move_x=False, move_y=False, move_z=False):
622         """centering
623
624         This method move selected polygons to center.
625         """
626         if not len(self.marks_selected):
627             return
628         pidxs = self._pidxlist()
629
630         x_min = 256
631         x_max = -256
632         y_min = 256
633         y_max = -256
634         z_min = 256
635         z_max = -256
636         for pidx in pidxs:
637             for v in self.md.polygons[pidx].vertexs:
638                 x_max = max(v.x, x_max)
639                 x_min = min(v.x, x_min)
640                 y_max = max(v.y, y_max)
641                 y_min = min(v.y, y_min)
642                 z_max = max(v.z, z_max)
643                 z_min = min(v.z, z_min)
644
645         x_ave = (x_min + x_max) // 2
646         y_ave = (y_min + y_max) // 2
647         z_ave = (z_min + z_max) // 2
648
649         ox = 0
650         oy = 0
651         oz = 0
652         if move_x:
653             ox = -x_ave
654         if move_y:
655             oy = -y_ave
656         if move_z:
657             oz = -z_ave
658
659         for pidx in pidxs:
660             for v in self.md.polygons[pidx].vertexs:
661                 v.x += ox
662                 v.y += oy
663                 v.z += oz
664
665         # remarking
666         self.redraw()
667         self.push()
668
669     def flipcopy_lr(self):
670         def check(v):
671             if v.x <= 0:
672                 # vertex in left side
673                 return True
674             return False
675         self.flipcopy(-1, 1, 1, check)
676
677     def flipcopy_rl(self):
678         def check(v):
679             if v.x >= 0:
680                 # vertex in right side
681                 return True
682             return False
683         self.flipcopy(-1, 1, 1, check)
684
685     def flipcopy_ud(self):
686         def check(v):
687             if v.y <= 0:
688                 # vertex in up side
689                 return True
690             return False
691         self.flipcopy(1, -1, 1, check)
692
693     def flipcopy_du(self):
694         def check(v):
695             if v.y >= 0:
696                 # vertex in down side
697                 return True
698             return False
699         self.flipcopy(1, -1, 1, check)
700
701     def flipcopy(self, flip_x, flip_y, flip_z, func_check):
702         """flip copy left to right
703
704         Delete vertexs in one side, copy and flip vertexs in other side
705         and join these vertexs.
706         """
707         if len(self.marks_selected):
708             edited_pidxs = []  # edited polygon idx
709             pidxs = self._pidxlist()
710             pidxs.reverse()
711             for p_idx in pidxs:
712
713                 # copy
714                 vertexs_copy = []
715                 other_idx = -1
716                 for v_idx, v in enumerate(self.md.polygons[p_idx].vertexs):
717                     if not func_check(v):
718                         # other side
719                         if other_idx < 0:
720                             other_idx = v_idx
721                         continue
722                     vc = Vertex(v.x, v.y, v.z)
723                     vertexs_copy.append(vc)
724
725                 # sort
726                 if other_idx >= 0:
727                     vertexs_copy = (vertexs_copy[other_idx:] +
728                                     vertexs_copy[:other_idx])
729
730                 # flip
731                 vertexs_flip = []
732                 for v in vertexs_copy:
733                     vf = Vertex(v.x * flip_x, v.y * flip_y, v.z * flip_z)
734                     vertexs_flip.insert(0, vf)
735
736                 if other_idx >= 0:
737                     # flip join replace
738                     vertexs_copy.extend(vertexs_flip)
739                     polygon_flip = Polygon(vertexs_copy,
740                                            self.md.getfill(p_idx),
741                                            self.md.getoutline(p_idx),
742                                            self.md.polygons[p_idx].name)
743                     self.md.addPolygon(p_idx, polygon_flip)
744                     self.md.delPolygon(p_idx)
745                     edited_pidxs.append(p_idx)
746                 else:
747                     # flip add
748                     polygon_flip = Polygon(vertexs_flip,
749                                            self.md.getfill(p_idx),
750                                            self.md.getoutline(p_idx),
751                                            self.md.polygons[p_idx].name)
752                     self.md.addPolygon(p_idx, polygon_flip)
753                     edited_pidxs = [idx + 1 for idx in edited_pidxs]
754                     edited_pidxs.append(p_idx)
755                     edited_pidxs.append(p_idx + 1)
756
757             # remarking
758             self.redraw()
759             self.marks_selected = []
760             for p_idx in edited_pidxs:
761                 self._addmarkpolygon(p_idx)
762
763             self.redraw()
764             self.push()
765
766     def smoothpolygon(self, div=5):
767         """make smooth polygon
768
769         add vertexs in polygon, and make smooth line.
770         """
771         pidxs = self._pidxlist()
772         for p_idx in pidxs:
773             if self._lenvertexs(p_idx) >= 256 // div:
774                 continue
775             self.md.smoothPolygon(p_idx, div)
776
777         # remarking
778         self.redraw()
779         self.marks_selected = []
780         for p_idx in pidxs:
781             self._addmarkpolygon(p_idx)
782
783         self.redraw()
784         self.push()
785
786
787 if __name__ == "__main__":
788     app = CtrModel()
789     app.pack()
790     app.mainloop()