BProjection: add the possibility to scale the plan according to the 3D Cursor
[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     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/3D_interaction/bprojection",
8     "tracker_url":"http://projects.blender.org/tracker/index.php?func=detail&aid=30521&group_id=153&atid=468",
9     "category": "Paint"}
10
11 import bpy
12 from bpy.types import Panel, Operator
13 from bpy.props import IntProperty, FloatProperty, BoolProperty, IntVectorProperty, StringProperty, FloatVectorProperty
14 from bpy_extras import view3d_utils
15 import math
16 from math import *
17 import mathutils
18 from mathutils import * 
19
20 # Main function for align the plan to view
21 def align_to_view(context):
22     ob = context.object        
23     rotation = ob.custom_rotation
24     scale = ob.custom_scale
25     z = ob.custom_z
26     pos = [ob.custom_location[0], ob.custom_location[1]]
27
28     reg = context.area.regions[4]        
29     width = reg.width
30     height = reg.height 
31         
32     r3d = context.space_data.region_3d        
33     r3d.update()
34     vl = r3d.view_location
35     vr = r3d.view_rotation
36     quat = mathutils.Quaternion((0.0, 0.0, 1.0), math.radians(float(rotation)))
37         
38     v = Vector((1,0,z))
39     v.rotate(vr)
40
41     em = bpy.data.objects['Empty for BProjection']
42     img = bpy.data.textures['Texture for BProjection'].image
43     if img and img.size[1] != 0:
44         prop = img.size[0]/img.size[1]
45     else: prop = 1    
46     
47     if ob.custom_linkscale:    
48         em.scale = Vector((prop*scale[0], scale[0], 1))
49     else:
50         em.scale = Vector((prop*scale[0], scale[1], 1))
51             
52     em.location = view3d_utils.region_2d_to_location_3d(context.area.regions[4], r3d, pos, v)        
53     em.rotation_euler = Quaternion.to_euler(vr*quat)
54
55 # Function to update the properties
56 def update_props(self, context):          
57     align_to_view(context)
58
59 # Function to update the scaleUV
60 def update_UVScale(self, context):
61     v = Vector((context.object.custom_offsetuv[0]/10 + 0.5, context.object.custom_offsetuv[1]/10 + 0.5))
62     l = Vector((0.0,0.0))
63     ob = context.object
64     s = ob.custom_scaleuv
65     os = ob.custom_old_scaleuv 
66     scale = s - os
67     uvdata = ob.data.uv_loop_layers.active.data
68     for i in range(trunc(pow(ob.custom_sub+1, 2)*4)):
69         vres =  v - uvdata[len(uvdata)-1-i].uv  
70         if ob.custom_linkscaleuv:
71             uvdata[len(uvdata)-1-i].uv = [v.x - vres.x/os[0]*s[0], v.y - vres.y/os[0]*s[0]]
72         else:
73             uvdata[len(uvdata)-1-i].uv = [v.x - vres.x/os[0]*s[0], v.y - vres.y/os[1]*s[1]]    
74     ob.custom_old_scaleuv = s
75         
76     align_to_view(context)
77
78 # Function to update the offsetUV
79 def update_UVOffset(self, context):
80     ob = context.object
81     o = ob.custom_offsetuv
82     oo = ob.custom_old_offsetuv 
83     uvdata = ob.data.uv_loop_layers.active.data
84     for i in range(trunc(pow(ob.custom_sub+1, 2)*4)):
85         uvdata[len(uvdata)-1-i].uv = [uvdata[len(uvdata)-1-i].uv[0] - oo[0]/10 + o[0]/10, uvdata[len(uvdata)-1-i].uv[1] - oo[1]/10 + o[1]/10]   
86     ob.custom_old_offsetuv = o
87     
88     align_to_view(context)
89
90 # Function to update the flip horizontal
91 def update_FlipUVX(self, context):          
92     uvdata = context.object.data.uv_loop_layers.active.data
93     for i in range(trunc(pow(context.object.custom_sub+1, 2)*4)):
94         x = uvdata[len(uvdata)-1-i].uv[0]
95         uvdata[len(uvdata)-1-i].uv[0] = 1 - x
96     
97     align_to_view(context)
98
99 # Function to update the flip vertical
100 def update_FlipUVY(self, context):          
101     uvdata = context.object.data.uv_loop_layers.active.data
102     for i in range(trunc(pow(context.object.custom_sub+1, 2)*4)):
103         y = uvdata[len(uvdata)-1-i].uv[1]
104         uvdata[len(uvdata)-1-i].uv[1] = 1 - y
105     
106     align_to_view(context)
107
108 # Function to update
109 def update_Rotation(self, context):              
110     if context.object.custom_rotc3d:
111         angle = context.object.custom_rotation - context.object.custom_old_rotation
112         sd = context.space_data
113         c3d = sd.cursor_location
114         e = bpy.data.objects['Empty for BProjection'].location
115         c = c3d
116         v1 = e-c
117         vo = Vector((0.0, 0.0, 1.0))
118         vo.rotate(sd.region_3d.view_rotation)
119         quat = mathutils.Quaternion(vo, math.radians(float(angle)))        
120         quat = quat
121         v2 = v1.copy()
122         v2.rotate(quat)
123         v = v1 - v2
124         res = e - v
125         sd.region_3d.update()
126         floc = view3d_utils.location_3d_to_region_2d(context.area.regions[4], sd.region_3d, res)
127         iloc=[round(floc[0]), round(floc[1])]
128         
129         context.object.custom_location = iloc       
130     else:
131         align_to_view(context)
132     
133     context.object.custom_old_rotation = context.object.custom_rotation
134
135 # Function to update scale
136 def update_Scale(self, context):              
137     ob = context.object
138     if context.object.custom_scac3d:
139         sd = context.space_data
140         c3d = sd.cursor_location
141         sd.region_3d.update()
142         e = bpy.data.objects['Empty for BProjection'].location
143         c = c3d
144         ce = e - c
145         v = view3d_utils.location_3d_to_region_2d(context.area.regions[4], sd.region_3d,c + ((ce)/ob.custom_old_scale.x)*ob.custom_scale.x)   
146         print(ce,c,v)
147         res = [round(v.x),round(v.y)]        
148         ob.custom_location = res    
149     else:
150         align_to_view(context)
151     
152     ob.custom_old_scale = ob.custom_scale
153
154 # Function to create custom properties
155 def createcustomprops(context):
156     Ob = bpy.types.Object    
157     
158     # plane properties 
159     Ob.custom_location = IntVectorProperty(name="Location", description="Location of the plan",
160                                            default=(trunc(context.area.regions[4].width*3/4),trunc( context.area.regions[4].height*3/4)),
161                                            subtype = 'XYZ', size=2, update = update_props)
162                                            
163     Ob.custom_rotation = IntProperty(name="Rotation", description="Rotate the plane",
164                                      min=-180, max=180, default=0,update = update_Rotation)
165                                      
166     Ob.custom_old_rotation = IntProperty(name="old_Rotation", description="Old Rotate the plane",
167                                          min=-180, max=180, default=0)
168                                          
169     Ob.custom_scale = FloatVectorProperty(name="Scales", description="Scale the planes",
170                                           subtype = 'XYZ', default=(1.0, 1.0),min = 0.1, size=2,update = update_Scale)
171     Ob.custom_old_scale = FloatVectorProperty(name="old_Scales", description="Old Scale the planes",
172                                           subtype = 'XYZ', default=(1.0, 1.0),min = 0.1, size=2)
173                                           
174     Ob.custom_linkscale = BoolProperty(name="linkscale", default=True, update = update_props)
175     
176     Ob.custom_z = FloatProperty(name="Z", description="Z axis for the plane",
177                                 min=-10, max=10, default=-1.0,update = update_props)
178                                 
179     Ob.custom_sub = IntProperty(name="Subdivide", description="Number of subdivision of the plan",
180                                      min=1, max=20, default=10)                                
181     
182     # UV properties
183     Ob.custom_scaleuv = FloatVectorProperty(name="ScaleUV", description="Scale the texture's UV",
184                                             default=(1.0,1.0),min = 0.01, subtype = 'XYZ', size=2,update = update_UVScale)    
185     Ob.custom_old_scaleuv = FloatVectorProperty(name="old_ScaleUV", description="Scale the texture's UV",
186                                                 default=(1.0,1.0),min = 0.01, subtype = 'XYZ', size=2)
187     Ob.custom_offsetuv = FloatVectorProperty(name="OffsetUV", description="Decal the texture's UV",
188                                             default=(0.0,0.0), subtype = 'XYZ', size=2,update = update_UVOffset)    
189     Ob.custom_old_offsetuv = FloatVectorProperty(name="old_OffsetUV", description="Decal the texture's UV",
190                                                  default=(0.0,0.0), subtype = 'XYZ', size=2)    
191     Ob.custom_linkscaleuv = BoolProperty(name="linkscaleUV", default=True, update = update_UVScale)
192     Ob.custom_flipuvx = BoolProperty(name="flipuvx", default=False, update = update_FlipUVX)
193     Ob.custom_flipuvy = BoolProperty(name="flipuvy", default=False, update = update_FlipUVY)
194     
195     # other properties    
196     Ob.custom_c3d = BoolProperty(name="c3d", default=True)
197     Ob.custom_rot = BoolProperty(name="rot", default=True)
198     Ob.custom_rotc3d = BoolProperty(name="rotc3d", default=False)
199     Ob.custom_scac3d = BoolProperty(name="scac3d", default=False)  
200
201 # Function to remove custom properties
202 def removecustomprops():    
203     list_prop = ['custom_location', 'custom_rotation', 'custom_old_rotation', 'custom_scale', 'custom_old_scale', 'custom_z', 'custom_c3d',
204                  'custom_rot', 'custom_rotc3d', 'custom_scaleuv', 'custom_flipuvx', 'custom_flipuvy', 'custom_linkscale',
205                  'custom_linkscaleuv', 'custom_old_scaleuv', 'custom_offsetuv', 'custom_old_offsetuv', 'custom_scac3d', 'custom_sub']
206     for prop in list_prop:
207         try:
208             del bpy.context.object[prop]
209         except:
210             do = 'nothing'
211
212 # Draw Class to show the panel
213 class BProjection(Panel):
214     bl_space_type = 'VIEW_3D'
215     bl_region_type = 'UI'
216     bl_label = "BProjection"
217
218     @classmethod
219     def poll(cls, context):
220         return (context.image_paint_object or context.sculpt_object)
221
222     def draw(self, context):        
223         layout = self.layout
224                 
225         try: 
226             bpy.data.objects['Empty for BProjection']
227
228             col = layout.column(align =True)
229             col.operator("object.removebprojectionplane", text="Remove BProjection plane")
230         
231             tex = bpy.data.textures['Texture for BProjection']
232
233             layout.template_ID_preview(tex, "image", open="image.open", rows=3, cols=3)
234         
235             col = layout.column(align =True)
236             col.operator('object.applyimage', text="Apply image", icon = 'FILE_TICK')
237             col = layout.column(align =True)
238             ob = context.object
239             col.prop(ob, "custom_c3d",text="Capture Cursor3d", icon='CURSOR')
240             col.prop(ob, "custom_rot",text="Rotate around selection", icon='ROTATE')
241             col = layout.column(align =True)
242             col.prop(ob,'custom_location', text='Plane Properties')
243             col.prop(ob,'custom_z') 
244             col.prop(ob,'custom_rotation')
245             col.prop(ob,'custom_rotc3d',text="Rotate around 3D Cursor",icon='MANIPUL')            
246             row = layout.row()
247             col = row.column(align =True)
248             col.prop(ob,'custom_scale')
249             if ob.custom_linkscale :
250                 col.prop(ob, "custom_linkscale",text="Linked",icon='LINKED')
251             else: 
252                 col.prop(ob, "custom_linkscale",text="Unlinked",icon='UNLINKED')
253             col.prop(ob,'custom_scac3d',text="Scale according to 3D Cursor",icon='MANIPUL')
254                             
255             col = layout.column(align =True)
256             col.prop(ob,'custom_offsetuv')
257             col.prop(ob,'custom_scaleuv')
258             if ob.custom_linkscaleuv:
259                 col.prop(ob, "custom_linkscaleuv",text="Linked",icon='LINKED')
260             else: 
261                 col.prop(ob, "custom_linkscaleuv",text="Uninked",icon='UNLINKED') 
262             row = layout.row()
263             col = row.column(align =True)
264             col.prop(ob, "custom_flipuvx",text="Flip X",icon='ARROW_LEFTRIGHT')   
265             col = row.column(align =True)
266             col.prop(ob, "custom_flipuvy",text="Flip Y",icon='FULLSCREEN_ENTER')            
267             col = layout.column(align =True)
268             col.prop(ob.material_slots['Material for BProjection'].material,'alpha', slider = True)
269
270         except:
271             col = layout.column(align = True)
272             col.operator("object.addbprojectionplane", text="Add BProjection plan")             
273
274 # Oprerator Class to apply the image to the plane             
275 class ApplyImage(Operator):
276     bl_idname = "object.applyimage"
277     bl_label = "Apply image"
278
279     def execute(self, context):        
280         img = bpy.data.textures['Texture for BProjection'].image
281         em = bpy.data.objects['Empty for BProjection']
282         ob = context.object
283                
284         bpy.ops.object.editmode_toggle()
285         f = ob.data.polygons
286         nbface = len(ob.data.polygons)
287         uvdata = ob.data.uv_textures.active.data 
288         wasnul = False
289         if len(uvdata) == 0:
290             bpy.ops.object.editmode_toggle()
291             uvdata = ob.data.uv_textures.active.data
292             wasnul = True                        
293         
294         
295         vglen = trunc(pow(ob.custom_sub+1, 2))
296         
297         for i in range(vglen):  
298             uvdata[f[nbface-i-1].index].image = img
299         
300         if wasnul == False:
301             bpy.ops.object.editmode_toggle()
302         else:
303             bpy.ops.paint.texture_paint_toggle()
304                                    
305         ob.custom_scale = [1,1]                       
306         ob.data.update()
307
308         align_to_view(context)
309         
310         return {'FINISHED'}
311
312 # Oprerator Class to make the 4 or 6 point and scale the plan
313 class IntuitiveScale(Operator):
314     bl_idname = "object.intuitivescale"
315     bl_label = "Draw lines"
316
317     def invoke(self, context, event):
318         ob = context.object 
319         x = event.mouse_region_x
320         y = event.mouse_region_y                
321         if len(ob.grease_pencil.layers.active.frames) == 0: 
322             bpy.ops.gpencil.draw(mode='DRAW', stroke=[{"name":"", "pen_flip":False,
323                                                        "is_start":True, "location":(0, 0, 0),
324                                                        "mouse":(x,y), "pressure":1, "time":0}])
325         else:
326             if ob.custom_linkscale:
327                 nb_point = 4
328             else:
329                 nb_point = 6
330                    
331             if len(ob.grease_pencil.layers.active.frames[0].strokes) < nb_point:
332                 bpy.ops.gpencil.draw(mode='DRAW', stroke=[{"name":"", "pen_flip":False,
333                                                            "is_start":True, "location":(0, 0, 0),
334                                                            "mouse":(x,y), "pressure":1, "time":0}])
335                                                            
336             if len(ob.grease_pencil.layers.active.frames[0].strokes) == nb_point:
337                 s = ob.grease_pencil.layers.active.frames[0]
338                 v1 = s.strokes[1].points[0].co - s.strokes[0].points[0].co
339                 if not ob.custom_linkscale:
340                     v2 = s.strokes[4].points[0].co - s.strokes[3].points[0].co
341                 else:
342                     v2 = s.strokes[3].points[0].co - s.strokes[2].points[0].co
343                 propx = v1.x/v2.x                
344                 ob.custom_scale[0] *= abs(propx)
345                 
346                 if not ob.custom_linkscale:
347                     v1 = s.strokes[2].points[0].co - s.strokes[0].points[0].co
348                     v2 = s.strokes[5].points[0].co - s.strokes[3].points[0].co
349                     propy = v1.y/v2.y
350                     ob.custom_scale[1] *= abs(propy)
351                 bpy.ops.gpencil.active_frame_delete()
352         
353         return {'FINISHED'}
354
355 # Oprerator Class to configure all wath is needed
356 class AddBProjectionPlane(Operator):
357     bl_idname = "object.addbprojectionplane"
358     bl_label = "Configure"
359     
360     def creatematerial(self, context):        
361         try:
362             matBProjection = bpy.data.materials['Material for BProjection']
363         except:            
364             bpy.data.textures.new(name='Texture for BProjection',type='IMAGE')
365     
366             bpy.data.materials.new(name='Material for BProjection')
367             
368             matBProjection = bpy.data.materials['Material for BProjection']
369             matBProjection.texture_slots.add()
370             matBProjection.use_shadeless = True
371             matBProjection.use_transparency = True
372             matBProjection.active_texture = bpy.data.textures['Texture for BProjection']
373         
374             index = matBProjection.active_texture_index
375             matBProjection.texture_slots[index].texture_coords = 'UV'
376         
377         ob = context.object 
378         old_index = ob.active_material_index
379         bpy.ops.object.material_slot_add()
380         index = ob.active_material_index
381         ob.material_slots[index].material = bpy.data.materials['Material for BProjection']
382         bpy.ops.object.material_slot_assign()
383         ob.active_material_index = old_index
384         ob.data.update()
385             
386     def execute(self, context):    
387         try:
388             bpy.data.objects['Empty for BProjection']
389
390         except:            
391             createcustomprops(context)
392             bpy.ops.paint.texture_paint_toggle()
393             
394             context.space_data.show_relationship_lines = False
395             
396             ob = context.object
397         
398             bpy.ops.object.add()
399             em = context.object
400             em.name = "Empty for BProjection"
401                         
402             bpy.data.scenes['Scene'].objects.active = ob
403             ob.select = True
404     
405             bpy.ops.object.editmode_toggle()
406     
407             bpy.ops.mesh.primitive_plane_add()
408             bpy.ops.object.vertex_group_assign(new = True)
409             ob.vertex_groups.active.name = 'texture plane'   
410             bpy.ops.uv.unwrap()
411             
412             bpy.ops.object.editmode_toggle()
413             for i in range(4):
414                 ob.data.edges[len(ob.data.edges)-1-i].crease = 1
415             bpy.ops.object.editmode_toggle()
416
417             bpy.ops.mesh.subdivide(number_cuts = ob.custom_sub)
418     
419             em.select = True
420             bpy.ops.object.hook_add_selob()
421             em.select = False
422             em.hide = True   
423                      
424             self.creatematerial(context)
425
426             bpy.ops.object.applyimage()  
427           
428             bpy.ops.gpencil.data_add()
429             ob.grease_pencil.draw_mode = 'VIEW'
430             bpy.ops.gpencil.layer_add()
431             ob.grease_pencil.layers.active.color = [1.0,0,0]
432             
433             bpy.ops.object.editmode_toggle()
434                     
435             km = bpy.data.window_managers['WinMan'].keyconfigs['Blender'].keymaps['3D View']
436             km.keymap_items[3-1].idname = 'view3d.rotate_view3d'
437             km.keymap_items[19-1].idname = 'view3d.zoom_view3d'
438             km.keymap_items[19-1].properties.delta = 1.0
439             km.keymap_items[20-1].idname = 'view3d.zoom_view3d'
440             km.keymap_items[20-1].properties.delta = -1.0
441             km.keymap_items[4-1].idname = 'view3d.pan_view3d'
442             km.keymap_items[26-1].idname = 'view3d.preset_view3d'
443             km.keymap_items[26-1].properties.view = 'FRONT'
444             km.keymap_items[28-1].idname = 'view3d.preset_view3d'
445             km.keymap_items[28-1].properties.view = 'RIGHT'            
446             km.keymap_items[32-1].idname = 'view3d.preset_view3d'
447             km.keymap_items[32-1].properties.view = 'TOP'
448             km.keymap_items[34-1].idname = 'view3d.preset_view3d'
449             km.keymap_items[34-1].properties.view = 'BACK'
450             km.keymap_items[35-1].idname = 'view3d.preset_view3d'
451             km.keymap_items[35-1].properties.view = 'LEFT'            
452             km.keymap_items[36-1].idname = 'view3d.preset_view3d'
453             km.keymap_items[36-1].properties.view = 'BOTTOM'                                   
454             km = context.window_manager.keyconfigs.default.keymaps['Image Paint']
455             kmi = km.keymap_items.new("object.intuitivescale", 'LEFTMOUSE', 'PRESS', shift=True)
456                         
457             align_to_view(context)
458             
459             bpy.ops.paint.texture_paint_toggle()
460             
461         return {'FINISHED'}
462
463 # Oprerator Class to remove what is no more needed    
464 class RemoveBProjectionPlane(Operator):
465     bl_idname = "object.removebprojectionplane"
466     bl_label = "Configure"
467
468     def removematerial(self, context):
469         ob = context.object 
470         i = 0
471         for ms in ob.material_slots:
472             if ms.name == 'Material for BProjection':
473                 index = i
474             i+=1
475                 
476         ob.active_material_index = index
477         bpy.ops.object.material_slot_remove()
478     
479     def execute(self, context):
480         try:               
481             bpy.ops.paint.texture_paint_toggle()
482             
483             context.space_data.show_relationship_lines = True
484             
485             bpy.ops.object.modifier_remove(modifier="Hook-Empty for BProjection")
486             
487             self.removematerial(context)
488
489             ob = context.object
490     
491             bpy.ops.object.editmode_toggle()
492     
493             bpy.ops.mesh.reveal()
494                                    
495             bpy.ops.mesh.select_all()
496             bpy.ops.object.editmode_toggle() 
497             if ob.data.vertices[0].select:
498                 bpy.ops.object.editmode_toggle()
499                 bpy.ops.mesh.select_all()
500                 bpy.ops.object.editmode_toggle()
501             bpy.ops.object.editmode_toggle()                    
502             
503             ob.vertex_groups.active.name = 'texture plane'
504             bpy.ops.object.vertex_group_select()
505             bpy.ops.mesh.delete()
506             bpy.ops.object.vertex_group_remove()
507     
508             bpy.ops.object.editmode_toggle()
509    
510             ob.select = False
511                 
512             em = bpy.data.objects['Empty for BProjection']
513             bpy.data.scenes['Scene'].objects.active = em
514             em.hide = False
515             em.select = True
516             bpy.ops.object.delete()
517     
518             bpy.data.scenes['Scene'].objects.active = ob
519             ob.select = True
520             
521             km = bpy.data.window_managers['WinMan'].keyconfigs['Blender'].keymaps['3D View']
522             km.keymap_items[3-1].idname = 'view3d.rotate'
523             km.keymap_items[19-1].idname = 'view3d.zoom'
524             km.keymap_items[19-1].properties.delta = 1.0
525             km.keymap_items[20-1].idname = 'view3d.zoom'
526             km.keymap_items[20-1].properties.delta = -1.0
527             km.keymap_items[4-1].idname = 'view3d.move'
528             km.keymap_items[26-1].idname = 'view3d.viewnumpad'
529             km.keymap_items[26-1].properties.type = 'FRONT'
530             km.keymap_items[28-1].idname = 'view3d.viewnumpad'
531             km.keymap_items[28-1].properties.type = 'RIGHT'            
532             km.keymap_items[32-1].idname = 'view3d.viewnumpad'
533             km.keymap_items[32-1].properties.type = 'TOP'
534             km.keymap_items[34-1].idname = 'view3d.viewnumpad'
535             km.keymap_items[34-1].properties.type = 'BACK'
536             km.keymap_items[35-1].idname = 'view3d.viewnumpad'
537             km.keymap_items[35-1].properties.type = 'LEFT'            
538             km.keymap_items[36-1].idname = 'view3d.viewnumpad'
539             km.keymap_items[36-1].properties.type = 'BOTTOM'            
540             
541             km = context.window_manager.keyconfigs.default.keymaps['Image Paint']
542             for kmi in km.keymap_items:
543                 if kmi.idname in ["object.intuitivescale"]:
544                     km.keymap_items.remove(kmi)
545             
546             bpy.ops.paint.texture_paint_toggle()
547             removecustomprops()
548                     
549         except:
550             nothing = 0
551         
552         return {'FINISHED'}
553
554 # Oprerator Class to rotate the view3D
555 class RotateView3D(Operator):
556     bl_idname = "view3d.rotate_view3d"
557     bl_label = "Rotate the View3D"
558     
559     first_mouse = Vector((0,0))
560
561     pan = Vector((0,0))
562
563     key = ['']
564  
565     first_time = True
566     
567     def vect_sphere(self, context, mx, my):
568         width = context.area.regions[4].width
569         height = context.area.regions[4].height
570            
571         if width >= height:
572             ratio = height/width
573         
574             x = 2*mx/width
575             y = 2*ratio*my/height
576             
577             x = x - 1
578             y = y - ratio
579         else:
580             ratio = width/height
581         
582             x = 2*ratio*mx/width
583             y = 2*my/height
584  
585             x = x - ratio
586             y = y - 1
587         
588         z2 = 1 - x * x - y * y
589         if z2 > 0:
590             z= sqrt(z2)
591         else : z=0
592             
593         p = Vector((x, y, z))
594         p.normalize()
595         return p
596     
597     def tracball(self, context, mx, my, origine):
598         sd = context.space_data
599         ob = context.object 
600         pos_init_cursor = view3d_utils.location_3d_to_region_2d(context.region, sd.region_3d, sd.cursor_location)        
601         if ob.custom_rot:
602             pos_init = view3d_utils.location_3d_to_region_2d(context.region, sd.region_3d, origine)
603             sd.region_3d.view_location = origine
604         
605         v1 = self.vect_sphere(context, self.first_mouse.x, self.first_mouse.y)
606         v2 = self.vect_sphere(context, mx, my)
607                         
608         axis = Vector.cross(v1,v2);
609         angle = Vector.angle(v1,v2);
610             
611         q =  Quaternion(axis,-2*angle)
612                         
613         sd.region_3d.view_rotation *=q
614         sd.region_3d.update()
615         
616         if ob.custom_rot:
617             pos_end = view3d_utils.region_2d_to_location_3d(context.region, sd.region_3d, pos_init, Vector((0,0,0)))                
618             sd.region_3d.view_location =  -1*pos_end
619
620         if ob.custom_c3d:
621             sd.region_3d.update()       
622             sd.cursor_location = view3d_utils.region_2d_to_location_3d(context.region, sd.region_3d, pos_init_cursor, Vector((0,0,0)))
623         
624         self.first_mouse = Vector((mx, my))
625                 
626     def modal(self, context, event):                                
627         ob = context.object 
628         if event.value == 'PRESS':
629             self.pan = Vector((event.mouse_region_x, event.mouse_region_y))
630                     
631             self.key = [event.type]
632
633         if event.value == 'RELEASE':
634             self.key = [''] 
635
636         if event.type == 'MOUSEMOVE':                        
637             
638             if '' in self.key:
639                 self.tracball(context, event.mouse_region_x, event.mouse_region_y,ob.location)
640                 align_to_view(context)
641                 if self.first_time:
642                     rot_ang = context.user_preferences.view.rotation_angle            
643                     context.user_preferences.view.rotation_angle = 0
644                     bpy.ops.view3d.view_orbit(type='ORBITLEFT')
645                     context.user_preferences.view.rotation_angle = rot_ang   
646                     bpy.ops.view3d.view_persportho()         
647                     bpy.ops.view3d.view_persportho()
648                     self.first_time = False
649           
650             deltax = event.mouse_region_x - round(self.pan.x)
651             deltay = event.mouse_region_y - round(self.pan.y)          
652
653             if 'G' in self.key:       
654                 cl = ob.custom_location
655                 ob.custom_location = [cl[0] + deltax,cl[1] + deltay]               
656                                    
657             if 'S' in self.key:                
658                 s = ob.custom_scale
659                 if ob.custom_linkscale:
660                     ob.custom_scale = [s[0] + deltax/20, s[1]]
661                 else:
662                     ob.custom_scale = [s[0] + deltax/20, s[1] + deltay/20]
663                                           
664             if 'Z' in self.key:                
665                 ob.custom_z+=deltax/10
666                       
667             if 'R' in self.key:
668                 ob.custom_rotation+=deltax
669                     
670             if 'U' in self.key:
671                 suv = ob.custom_scaleuv
672                 if ob.custom_linkscaleuv:    
673                     ob.custom_scaleuv= [suv[0] + deltax/50 , suv[0] + deltax/50]
674                 else:
675                     ob.custom_scaleuv= [suv[0] + deltax/50 , suv[1] + deltay/50]               
676
677             if 'Y' in self.key:       
678                 ouv = ob.custom_offsetuv
679                 ob.custom_offsetuv = [ouv[0] - deltax/50,ouv[1] - deltay/50] 
680
681             self.pan = Vector((event.mouse_region_x, event.mouse_region_y))
682             self.first_mouse = Vector((event.mouse_region_x, self.first_mouse.y))
683                         
684         elif event.type == 'MIDDLEMOUSE'and event.value == 'RELEASE':       
685             
686             return {'FINISHED'}
687         
688         if 'C' in self.key:
689             ob.custom_scale = [1,1]
690             ob.custom_rotation = 0
691             ob.custom_scaleuv =[1.0,1.0]
692             ob.custom_offsetuv =[0.0,0.0]
693             return {'RUNNING_MODAL'}
694                     
695         return {'RUNNING_MODAL'}
696     
697     def execute(self, context):        
698         align_to_view(context)  
699         
700         return{'FINISHED'}
701
702     def invoke(self, context, event):
703         context.window_manager.modal_handler_add(self)
704         self.first_mouse = Vector((event.mouse_region_x,event.mouse_region_y))
705         self.first_time = True
706         
707         return {'RUNNING_MODAL'}
708
709 # Oprerator Class to pan the view3D
710 class PanView3D(bpy.types.Operator):
711     bl_idname = "view3d.pan_view3d"
712     bl_label = "Pan View3D"
713     
714     first_mouse = Vector((0,0))
715
716     def modal(self, context, event):
717         width = context.area.regions[4].width
718         height = context.area.regions[4].height
719
720         deltax = event.mouse_region_x - self.first_mouse.x
721         deltay = event.mouse_region_y - self.first_mouse.y                
722         
723         sd = context.space_data
724                
725         l =  sd.region_3d
726         vr = l.view_rotation
727         
728         v = Vector((deltax/max(width,height),deltay/max(width,height),0))
729         v.rotate(vr)
730         
731         pos = [0,0]
732         v1 = view3d_utils.region_2d_to_location_3d(context.region, l, pos, v)
733         pos = [width,height]
734         v2 = view3d_utils.region_2d_to_location_3d(context.region, l, pos, v)
735         
736         v3 = (v2 - v1)
737         
738         pos_init_cursor = view3d_utils.location_3d_to_region_2d(context.region, sd.region_3d, sd.cursor_location)
739         
740         sd.region_3d.view_location -= v*v3.length
741         
742         sd.region_3d.update()       
743         sd.cursor_location = view3d_utils.region_2d_to_location_3d(context.region, sd.region_3d, pos_init_cursor, Vector((0,0,0)))
744             
745         align_to_view(context)
746
747         self.first_mouse.x = event.mouse_region_x
748         self.first_mouse.y = event.mouse_region_y
749
750         if event.type == 'MIDDLEMOUSE'and event.value == 'RELEASE':
751             return {'FINISHED'}
752         
753         return {'RUNNING_MODAL'}
754                 
755     def invoke(self, context, event):
756         context.window_manager.modal_handler_add(self)
757         self.first_mouse.x = event.mouse_region_x
758         self.first_mouse.y = event.mouse_region_y   
759         
760         return {'RUNNING_MODAL'}
761
762     def execute(self, context):        
763         align_to_view(context)  
764         
765         return{'FINISHED'}
766
767 # Oprerator Class to zoom the view3D
768 class ZoomView3D(Operator):
769     bl_idname = "view3d.zoom_view3d"
770     bl_label = "Zoom View3D"
771
772     delta = FloatProperty(
773         name="delta",
774         description="Delta",
775         min=-1.0, max=1,
776         default=1.0)
777
778     def invoke(self, context, event):                   
779         bpy.ops.view3d.zoom(delta = self.delta)
780         
781         align_to_view(context)
782         
783         return {'FINISHED'}
784
785     def execute(self, context):        
786         align_to_view(context)
787         
788         return{'FINISHED'}
789
790 # Oprerator Class to use numpad shortcut
791 class PresetView3D(Operator):
792     bl_idname = "view3d.preset_view3d"
793     bl_label = "Preset View3D"
794
795     view = StringProperty(name="View", description="Select the view", default='TOP',)
796
797     def invoke(self, context, event):                   
798         ob = context.object 
799         origine = ob.location
800         sd = context.space_data
801         
802         pos_init_cursor = view3d_utils.location_3d_to_region_2d(context.region, sd.region_3d, sd.cursor_location)
803
804         if ob.custom_rot:
805             pos_init = view3d_utils.location_3d_to_region_2d(context.region, sd.region_3d, origine)
806             sd.region_3d.view_location = origine
807
808         tmp = context.user_preferences.view.smooth_view
809         context.user_preferences.view.smooth_view = 0
810         bpy.ops.view3d.viewnumpad(type=self.view)        
811         align_to_view(context)
812         context.user_preferences.view.smooth_view = tmp
813
814         if ob.custom_rot:
815             pos_end = view3d_utils.region_2d_to_location_3d(context.region, sd.region_3d, pos_init, Vector((0,0,0)))                
816             sd.region_3d.view_location =  -1*pos_end
817             align_to_view(context)
818
819         if ob.custom_c3d:
820             sd.region_3d.update()       
821             sd.cursor_location = view3d_utils.region_2d_to_location_3d(context.region, sd.region_3d, pos_init_cursor, Vector((0,0,0)))        
822                     
823         return {'FINISHED'}
824
825 def register():
826     bpy.utils.register_module(__name__)
827
828 def unregister():
829     bpy.utils.unregister_module(__name__)
830
831 if __name__ == "__main__":
832     register()