BProjection: numpad shortcut was still bugy, the plan position was not update. last...
[blender-addons-contrib.git] / space_view3d_paint_bprojection.py
1 bl_info = {
2     "name": "BProjection",
3     "description": "Help Clone tool",
4     "author": "kgeogeo",
5     "version": (1, 0),
6     "blender": (2, 6, 3),
7     "category": "Paint"}
8
9 import bpy
10 from bpy.types import Panel, Operator
11 from bpy.props import IntProperty, FloatProperty, BoolProperty, IntVectorProperty, StringProperty
12 from bpy_extras import view3d_utils
13 from mathutils import *
14 from math import * 
15 import mathutils
16 import math
17
18 def align_to_view(context):
19     ob = context.object        
20     rotation = ob.custom_rotation
21     scale = ob.custom_scale
22     z = ob.custom_z
23     posx = ob.custom_location[0]
24     posy = ob.custom_location[1]
25
26     reg = bpy.context.area.regions[4]        
27     width = reg.width
28     height = reg.height 
29         
30     r3d =  context.space_data.region_3d        
31     r3d.update()
32     vl = r3d.view_location
33     vr = r3d.view_rotation
34     quat = mathutils.Quaternion((0.0, 0.0, 1.0), math.radians(float(rotation)))
35         
36     v = Vector((1,0,z))
37     v.rotate(vr)
38
39     pos = (posx,posy) 
40
41     em = bpy.data.objects['Empty for clone']
42     img = bpy.data.textures['Texture for clone'].image
43     if img and img.size[1] != 0:
44         prop = img.size[0]/img.size[1]
45         em.scale[0] = prop
46     else: prop = 1    
47         
48     em.scale =  Vector((prop*scale, scale, scale))
49     em.location = view3d_utils.region_2d_to_location_3d(context.area.regions[4], r3d, pos, v)        
50     em.rotation_euler = Quaternion.to_euler(vr*quat)
51
52 class HelpClone(Panel):
53     bl_space_type = 'VIEW_3D'
54     bl_region_type = 'UI'
55     bl_label = "Help Clone"
56
57     @classmethod
58     def poll(cls, context):
59         return (context.image_paint_object)
60
61     def draw(self, context):        
62         layout = self.layout
63                 
64         try: 
65             bpy.data.objects['Empty for clone']
66
67             col = layout.column(align =True)
68             col.operator("object.removecloneplane", text="Remove clone plane")
69         
70             tex = bpy.data.textures['Texture for clone']
71
72             layout.template_ID_preview(tex, "image", open="image.open", rows=3, cols=3)
73         
74             col = layout.column(align =True)
75             col.operator('object.applyimage', text = "Apply image")
76             col = layout.column(align =True)
77             ob = context.object
78             col.prop(ob, "custom_c3d",text="Capture Cursor3d")
79             col.prop(ob, "custom_rot",text="Rotate around selection")
80             col = layout.column(align =True)
81             col.prop(ob,'custom_rotation', slider = True)
82             col.prop(ob,'custom_scale', slider = True) 
83             col.prop(ob,'custom_z', slider = True) 
84             col.prop(ob,'custom_location')
85             col = layout.column(align =True)
86             col.prop(ob,'custom_scaleuv', slider = True)
87             col = layout.column(align =True)
88             col.prop(ob.material_slots['Material for clone'].material,'alpha', slider = True)
89
90         except:
91             col = layout.column(align =True)
92             col.operator("object.addcloneplane", text="Add clone plan")             
93                 
94 class ApplyImage(Operator):
95     bl_idname = "object.applyimage"
96     bl_label = "Apply image"
97
98     def execute(self, context):        
99         img = bpy.data.textures['Texture for clone'].image
100         em = bpy.data.objects['Empty for clone']
101         uvdata = bpy.context.object.data.uv_textures.active.data        
102         uvdata[len(uvdata)-1].image = img
103         if img and img.size[1] != 0:        
104             prop = img.size[0]/img.size[1]                
105             em.scale[0] = prop
106             
107         context.object.data.update()
108         align_to_view(context)
109         
110         return {'FINISHED'}
111
112 class DrawLines(Operator):
113     bl_idname = "object.drawlines"
114     bl_label = "Draw lines"
115
116     def invoke(self, context, event):
117         
118         x = event.mouse_region_x
119         y = event.mouse_region_y                
120         if len(bpy.context.object.grease_pencil.layers.active.frames)==0: 
121             bpy.ops.gpencil.draw(mode='DRAW', stroke=[{"name":"", "pen_flip":False,
122                                                                 "is_start":True, "location":(0, 0, 0),
123                                                                 "mouse":(x,y), "pressure":1, "time":0}])
124         else:
125             if len(bpy.context.object.grease_pencil.layers.active.frames[0].strokes) < 4:
126                 bpy.ops.gpencil.draw(mode='DRAW', stroke=[{"name":"", "pen_flip":False,
127                                                                     "is_start":True, "location":(0, 0, 0),
128                                                                     "mouse":(x,y), "pressure":1, "time":0}])
129             if len(bpy.context.object.grease_pencil.layers.active.frames[0].strokes) == 4:
130                 s = bpy.context.object.grease_pencil.layers.active.frames[0]
131                 v1 = s.strokes[1].points[0].co - s.strokes[0].points[0].co
132                 v2 = s.strokes[3].points[0].co - s.strokes[2].points[0].co
133                 prop = v1.x/v2.x
134                 bpy.context.object.custom_scale *= abs(prop)
135                 bpy.ops.gpencil.active_frame_delete()
136         
137         return {'FINISHED'}
138
139 class AddClonePlane(Operator):
140     bl_idname = "object.addcloneplane"
141     bl_label = "Configure"
142     
143     def creatematerial(self, context):        
144         try:
145             matclone = bpy.data.materials['Material for clone']
146         except:            
147             bpy.data.textures.new(name='Texture for clone',type='IMAGE')
148     
149             bpy.data.materials.new(name='Material for clone')
150             
151             matclone = bpy.data.materials['Material for clone']
152             matclone.texture_slots.add()
153             matclone.use_shadeless = True
154             matclone.use_transparency = True
155             matclone.active_texture = bpy.data.textures['Texture for clone']
156         
157             index = matclone.active_texture_index
158             matclone.texture_slots[index].texture_coords = 'UV'
159      
160         old_index = context.object.active_material_index
161         bpy.ops.object.material_slot_add()
162         index = context.object.active_material_index
163         bpy.context.object.material_slots[index].material = bpy.data.materials['Material for clone']
164         bpy.ops.object.material_slot_assign()
165         context.object.active_material_index = old_index
166             
167     def execute(self, context):    
168         try:
169             bpy.data.objects['Empty for clone']
170
171         except:            
172             bpy.ops.paint.texture_paint_toggle()
173             
174             bpy.context.space_data.show_relationship_lines = False
175             
176             ob = bpy.context.object
177         
178             bpy.ops.object.add()
179             em = bpy.context.object
180             em.name = "Empty for clone"
181                         
182             bpy.data.scenes['Scene'].objects.active = ob
183             ob.select = True
184     
185             bpy.ops.object.editmode_toggle()
186     
187             bpy.ops.mesh.primitive_plane_add()
188             bpy.ops.object.vertex_group_assign(new = True)
189             ob.vertex_groups.active.name = 'texture plane'   
190             bpy.ops.uv.unwrap()
191             
192             bpy.ops.object.editmode_toggle()
193             for i in range(4):
194                 ob.data.edges[len(ob.data.edges)-1-i].crease = 1
195             bpy.ops.object.editmode_toggle()
196     
197             em.select = True
198             bpy.ops.object.hook_add_selob()
199             
200             self.creatematerial(context)
201             
202             bpy.ops.gpencil.data_add()
203             bpy.context.object.grease_pencil.draw_mode = 'VIEW'
204             bpy.ops.gpencil.layer_add()
205             bpy.context.object.grease_pencil.layers.active.color = [1.0,0,0]
206
207             bpy.ops.mesh.hide()
208             
209             em.select = False
210             em.hide = True
211             
212             bpy.ops.object.editmode_toggle()
213                     
214             bpy.ops.object.applyimage()
215             km = bpy.data.window_managers['WinMan'].keyconfigs['Blender'].keymaps['3D View']
216             km.keymap_items[3-1].idname = 'view3d.rotate_view3d'
217             km.keymap_items[19-1].idname = 'view3d.zoom_view3d'
218             km.keymap_items[19-1].properties.delta = 1.0
219             km.keymap_items[20-1].idname = 'view3d.zoom_view3d'
220             km.keymap_items[20-1].properties.delta = -1.0
221             km.keymap_items[4-1].idname = 'view3d.pan_view3d'
222             km.keymap_items[26-1].idname = 'view3d.preset_view3d'
223             km.keymap_items[26-1].properties.view = 'FRONT'
224             km.keymap_items[28-1].idname = 'view3d.preset_view3d'
225             km.keymap_items[28-1].properties.view = 'RIGHT'            
226             km.keymap_items[32-1].idname = 'view3d.preset_view3d'
227             km.keymap_items[32-1].properties.view = 'TOP'
228             km.keymap_items[34-1].idname = 'view3d.preset_view3d'
229             km.keymap_items[34-1].properties.view = 'BACK'
230             km.keymap_items[35-1].idname = 'view3d.preset_view3d'
231             km.keymap_items[35-1].properties.view = 'LEFT'            
232             km.keymap_items[36-1].idname = 'view3d.preset_view3d'
233             km.keymap_items[36-1].properties.view = 'BOTTOM'                                   
234             km = bpy.context.window_manager.keyconfigs.default.keymaps['Image Paint']
235             kmi = km.keymap_items.new("object.drawlines", 'LEFTMOUSE', 'PRESS', shift=True)
236                         
237             align_to_view(context)
238             
239             bpy.ops.paint.texture_paint_toggle()
240             
241         return {'FINISHED'}
242     
243 class RemoveClonePlane(Operator):
244     bl_idname = "object.removecloneplane"
245     bl_label = "Configure"
246
247     def removematerial(self, context):
248         i = 0
249         for ms in context.object.material_slots:
250             if ms.name == 'Material for clone':
251                 index = i
252             i+=1
253                 
254         context.object.active_material_index = index
255         bpy.ops.object.material_slot_remove()
256     
257     def execute(self, context):
258         try:               
259             bpy.ops.paint.texture_paint_toggle()
260             
261             context.space_data.show_relationship_lines = True
262             
263             bpy.ops.object.modifier_remove(modifier="Hook-Empty for clone")
264             
265             self.removematerial(context)
266
267             ob = bpy.context.object
268     
269             bpy.ops.object.editmode_toggle()
270     
271             bpy.ops.mesh.reveal()
272                                    
273             bpy.ops.mesh.select_all()
274             bpy.ops.object.editmode_toggle() 
275             if ob.data.vertices[0].select:
276                 bpy.ops.object.editmode_toggle()
277                 bpy.ops.mesh.select_all()
278                 bpy.ops.object.editmode_toggle()
279             bpy.ops.object.editmode_toggle()                    
280             
281             ob.vertex_groups.active.name = 'texture plane'
282             bpy.ops.object.vertex_group_select()
283             bpy.ops.mesh.delete()
284             bpy.ops.object.vertex_group_remove()
285     
286             bpy.ops.object.editmode_toggle()
287    
288             ob.select = False
289                 
290             em = bpy.data.objects['Empty for clone']
291             bpy.data.scenes['Scene'].objects.active = em
292             em.hide = False
293             em.select = True
294             bpy.ops.object.delete()
295     
296             bpy.data.scenes['Scene'].objects.active = ob
297             ob.select = True
298             
299             km = bpy.data.window_managers['WinMan'].keyconfigs['Blender'].keymaps['3D View']
300             km.keymap_items[3-1].idname = 'view3d.rotate'
301             km.keymap_items[19-1].idname = 'view3d.zoom'
302             km.keymap_items[19-1].properties.delta = 1.0
303             km.keymap_items[20-1].idname = 'view3d.zoom'
304             km.keymap_items[20-1].properties.delta = -1.0
305             km.keymap_items[4-1].idname = 'view3d.move'
306             km.keymap_items[26-1].idname = 'view3d.viewnumpad'
307             km.keymap_items[26-1].properties.type = 'FRONT'
308             km.keymap_items[28-1].idname = 'view3d.viewnumpad'
309             km.keymap_items[28-1].properties.type = 'RIGHT'            
310             km.keymap_items[32-1].idname = 'view3d.viewnumpad'
311             km.keymap_items[32-1].properties.type = 'TOP'
312             km.keymap_items[34-1].idname = 'view3d.viewnumpad'
313             km.keymap_items[34-1].properties.type = 'BACK'
314             km.keymap_items[35-1].idname = 'view3d.viewnumpad'
315             km.keymap_items[35-1].properties.type = 'LEFT'            
316             km.keymap_items[36-1].idname = 'view3d.viewnumpad'
317             km.keymap_items[36-1].properties.type = 'BOTTOM'            
318             
319             km = bpy.context.window_manager.keyconfigs.default.keymaps['Image Paint']
320             for kmi in km.keymap_items:
321                 if kmi.idname in ["object.drawlines"]:
322                     km.keymap_items.remove(kmi)
323             
324             bpy.ops.paint.texture_paint_toggle()
325                     
326         except:
327             nothing
328         
329         return {'FINISHED'}
330
331 class RotateView3D(Operator):
332     bl_idname = "view3d.rotate_view3d"
333     bl_label = "Rotate the View3D"
334     
335     first_mouse_x = 0
336     first_mouse_y = 0 
337
338     panx = 0
339     pany = 0
340
341     rkey = False
342     skey = False
343     gkey = False
344     zkey = False
345     ukey = False
346     first_time = True
347     
348     def vect_sphere(self,mx,my):
349         width = bpy.context.area.regions[4].width
350         height = bpy.context.area.regions[4].height
351            
352         if width >= height:
353             ratio = height/width
354         
355             x = 2*mx/width
356             y = 2*ratio*my/height
357             
358             x = x - 1
359             y = y - ratio
360         else:
361             ratio = width/height
362         
363             x = 2*ratio*mx/width
364             y = 2*my/height
365  
366             x = x - ratio
367             y = y - 1
368         
369         z2 = 1 - x * x - y * y
370         if z2 > 0:
371             z= sqrt(z2)
372         else : z=0
373             
374         p = Vector((x, y, z))
375         p.normalize()
376         return p
377     
378     def tracball(self,mx,my,origine):
379         pos_init_cursor = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, bpy.context.space_data.cursor_location)        
380         if bpy.context.object.custom_rot:
381             pos_init = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, origine)
382             bpy.context.space_data.region_3d.view_location = origine
383         
384         v1 = self.vect_sphere(self.first_mouse_x,self.first_mouse_y)
385         v2 = self.vect_sphere(mx,my)
386                         
387         axis = Vector.cross(v1, v2);
388         angle = Vector.angle(v1, v2);
389             
390         q =  Quaternion(axis,-2*angle)
391                         
392         bpy.context.space_data.region_3d.view_rotation *=q
393         bpy.context.space_data.region_3d.update()
394         
395         if bpy.context.object.custom_rot:
396             pos_end = view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, pos_init, Vector((0,0,0)))                
397             bpy.context.space_data.region_3d.view_location =  -1*pos_end
398
399         if bpy.context.object.custom_c3d:
400             bpy.context.space_data.region_3d.update()       
401             bpy.context.space_data.cursor_location = view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, pos_init_cursor, Vector((0,0,0)))
402         
403         self.first_mouse_x = mx
404         self.first_mouse_y = my
405                 
406     def modal(self, context, event):                                
407         if event.value == 'PRESS':
408             self.panx = event.mouse_region_x
409             self.pany = event.mouse_region_y
410                     
411             if event.type == 'S':
412                 self.skey = True
413             
414             if event.type == 'R':
415                 self.rkey = True    
416
417             if event.type == 'G':
418                 self.gkey = True
419
420             if event.type == 'Z':
421                 self.zkey = True            
422
423             if event.type == 'U':
424                 self.ukey = True 
425                             
426         if event.value == 'RELEASE':
427             if event.type == 'S':
428                 self.skey = False
429             
430             if event.type == 'R':
431                 self.rkey = False
432             
433             if event.type == 'G':
434                 self.gkey = False            
435
436             if event.type == 'Z':
437                 self.zkey = False 
438
439             if event.type == 'U':
440                 self.ukey = False 
441             
442         if event.type == 'MOUSEMOVE':                        
443             
444             if self.rkey == False and self.skey == False and self.gkey == False and self.zkey == False and self.ukey == False:
445                 self.tracball(event.mouse_region_x,event.mouse_region_y,bpy.context.object.location)
446                 align_to_view(context)
447                 if self.first_time:
448                     rot_ang = bpy.context.user_preferences.view.rotation_angle            
449                     bpy.context.user_preferences.view.rotation_angle = 0
450                     bpy.ops.view3d.view_orbit(type='ORBITLEFT')
451                     bpy.context.user_preferences.view.rotation_angle = rot_ang   
452                     bpy.ops.view3d.view_persportho()         
453                     bpy.ops.view3d.view_persportho()
454                     self.first_time = False
455           
456             deltax = event.mouse_region_x - self.panx
457             deltay = event.mouse_region_y - self.pany           
458
459             if self.rkey == False and self.skey == False and self.gkey == True and self.zkey == False and self.ukey == False:       
460                 bpy.context.object.custom_location[0]+=deltax
461                 bpy.context.object.custom_location[1]+=deltay                
462                                    
463             if self.rkey == False and self.skey == True and self.gkey == False and self.zkey == False and self.ukey == False:                
464                 bpy.context.object.custom_scale+=deltax/10
465                                           
466             if self.rkey == False and self.skey == False and self.gkey == False and self.zkey == True and self.ukey == False:                
467                 bpy.context.object.custom_z+=deltax/10
468                       
469             if self.rkey == True and self.skey == False and self.gkey == False and self.zkey == False and self.ukey == False:
470                 bpy.context.object.custom_rotation+=deltax
471
472             if self.rkey == False and self.skey == False and self.gkey == False and self.zkey == False and self.ukey == True:
473                 bpy.context.object.custom_scaleuv+=deltax/10                
474
475             self.panx = event.mouse_region_x
476             self.pany = event.mouse_region_y
477             self.first_mouse_x = event.mouse_region_x
478             self.first_mouse_y = event.mouse_region_y
479             
480         elif event.type == 'MIDDLEMOUSE'and event.value == 'RELEASE':
481
482             return {'FINISHED'}
483         
484         return {'RUNNING_MODAL'}
485     
486     def execute(self, context):        
487         align_to_view(context)  
488         
489         return{'FINISHED'}
490
491     def invoke(self, context, event):
492         context.window_manager.modal_handler_add(self)
493         self.first_mouse_x = event.mouse_region_x
494         self.first_mouse_y = event.mouse_region_y
495         self.first_time = True
496         
497         return {'RUNNING_MODAL'}
498
499 class ZoomView3D(Operator):
500     bl_idname = "view3d.zoom_view3d"
501     bl_label = "Zoom View3D"
502
503     delta = FloatProperty(
504         name="delta",
505         description="Delta",
506         min=-1.0, max=1,
507         default=1.0,
508         )
509
510     def invoke(self, context, event):                   
511         bpy.ops.view3d.zoom(delta = self.delta)
512         
513         align_to_view(context)
514         
515         return {'FINISHED'}
516
517     def execute(self, context):        
518         align_to_view(context)
519         
520         return{'FINISHED'}
521
522 class PresetView3D(Operator):
523     bl_idname = "view3d.preset_view3d"
524     bl_label = "Preset View3D"
525
526     view = StringProperty(name="View", description="Select the view", default='TOP',)
527
528     def invoke(self, context, event):                   
529         origine = bpy.context.object.location
530         if bpy.context.object.custom_rot:
531             pos_init = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, origine)
532             bpy.context.space_data.region_3d.view_location = origine
533
534         tmp = bpy.context.user_preferences.view.smooth_view
535         bpy.context.user_preferences.view.smooth_view = 0
536         bpy.ops.view3d.viewnumpad(type=self.view)        
537         align_to_view(context)
538         bpy.context.user_preferences.view.smooth_view = tmp
539
540         if bpy.context.object.custom_rot:
541             pos_end = view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, pos_init, Vector((0,0,0)))                
542             bpy.context.space_data.region_3d.view_location =  -1*pos_end
543             align_to_view(context)
544         
545                     
546         return {'FINISHED'}
547
548
549 class PanView3D(bpy.types.Operator):
550     bl_idname = "view3d.pan_view3d"
551     bl_label = "Pan View3D"
552
553     
554     first_mouse_x = 0
555     first_mouse_y = 0 
556
557     panx = 0
558     pany = 0
559
560     def modal(self, context, event):
561         width = bpy.context.area.regions[4].width
562         height = bpy.context.area.regions[4].height
563
564         deltax = event.mouse_region_x - self.first_mouse_x
565         deltay = event.mouse_region_y - self.first_mouse_y                
566                
567         l =  bpy.context.space_data.region_3d
568         vr = l.view_rotation
569         
570         v = Vector((deltax/max(width,height),deltay/max(width,height),0))
571         v.rotate(vr)
572         
573         pos = [0,0]
574         v1 = view3d_utils.region_2d_to_location_3d(bpy.context.region, l, pos, v)
575         pos = [width,height]
576         v2 = view3d_utils.region_2d_to_location_3d(bpy.context.region, l, pos, v)
577         
578         v3 = (v2 - v1)
579         
580         pos_init_cursor = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d, bpy.context.space_data.cursor_location)
581         
582         bpy.context.space_data.region_3d.view_location -= v*v3.length
583         
584         bpy.context.space_data.region_3d.update()       
585         bpy.context.space_data.cursor_location = view3d_utils.region_2d_to_location_3d(bpy.context.region, bpy.context.space_data.region_3d, pos_init_cursor, Vector((0,0,0)))
586             
587         align_to_view(context)
588
589         self.first_mouse_x = event.mouse_region_x
590         self.first_mouse_y = event.mouse_region_y
591
592         if event.type == 'MIDDLEMOUSE'and event.value == 'RELEASE':
593             return {'FINISHED'}
594         
595         return {'RUNNING_MODAL'}
596                 
597     def invoke(self, context, event):
598         context.window_manager.modal_handler_add(self)
599         self.first_mouse_x = event.mouse_region_x
600         self.first_mouse_y = event.mouse_region_y   
601         
602         return {'RUNNING_MODAL'}
603
604     def execute(self, context):        
605         align_to_view(context)  
606         
607         return{'FINISHED'}
608
609 def update_func(self, context):          
610     v = Vector((0.5,0.5))
611     for i in range(4):
612         vres =  v - bpy.context.object.data.uv_loop_layers.active.data[len(bpy.context.object.data.uv_loop_layers.active.data)-1-i].uv 
613         vres /= vres.length
614         for j in range(2):
615             if abs(vres[j-1])>0:
616                 vres[j-1] /= abs(vres[j-1])
617         bpy.context.object.data.uv_loop_layers.active.data[len(bpy.context.object.data.uv_loop_layers.active.data)-1-i].uv = v - vres*bpy.context.object.custom_scaleuv/2
618     
619     align_to_view(context)
620
621 def createcustomprops():
622     Ob = bpy.types.Object    
623     Ob.custom_rotation = IntProperty(name="Rotation", description="Rotate the plane", min=-180, max=180, default=0,update = update_func)
624     Ob.custom_scale = FloatProperty(name="Scale", description="Scale the plane", min=0, max=10, default=1.0,update = update_func)
625     Ob.custom_z = FloatProperty(name="Z", description="Z axis for the plane", min=-10, max=10, default=-1.0,update = update_func)
626     Ob.custom_scaleuv = FloatProperty(name="ScaleUV", description="Scale the texture's UV", min=1.0, max=10, default=1.0,update = update_func)
627     Ob.custom_location = IntVectorProperty(name="Location", description="Location of the plan", default=(0, 0), subtype = 'XYZ', size=2, update = update_func)
628     Ob.custom_c3d = BoolProperty(name="c3d", default=True)
629     Ob.custom_rot = BoolProperty(name="rot", default=True)
630
631 def register():
632     bpy.utils.register_module(__name__)
633     createcustomprops()
634
635 def unregister():
636     bpy.utils.unregister_module(__name__)
637
638 if __name__ == "__main__":
639     register()
640    
641