Extensions: change the constant for the complete status
[blender-addons-contrib.git] / np_station / np_point_move.py
blob5c16a72faac40f73c68daed380b47f0b226a306d
2 # ##### BEGIN GPL LICENSE BLOCK #####
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
21 '''
22 DESCRIPTION
24 Translates objects using anchor and target points.
26 Emulates the functionality of the standard 'move' command in CAD applications, with start and end points. This way, it does pretty much what the basic 'grab' function does, only it locks the snap ability to one designated point in selected group, giving more control and precision to the user.
28 INSTALATION
30 Two ways:
32 A. Paste the the .py file to text editor and run (ALT+P)
33 B. Unzip and place .py file to addons_contrib. In User Preferences / Addons tab search under Testing / NP Anchor Translate and check the box.
35 Now you have the operator in your system. If you press Save User Preferences, you will have it at your disposal every time you run Bl.
37 SHORTCUTS
39 After succesful instalation of the addon, or it's activation from the text editor, the NP Point Move operator should be registered in your system. Enter User Preferences / Input, and under that, 3DView / Object Mode. Search for definition assigned to simple M key (provided that you don't use it for placing objects into layers, instead of now almost-standard 'Layer manager' addon) and instead object.move_to_layer, type object.np_xxx_point_move (xxx being the number of the version). I suggest asigning hotkey only for the Object Mode because the addon doesn't work in other modes. Also, this way the basic G command should still be available and at your disposal.
41 USAGE
43 Select one or more objects.
44 Run operator (spacebar search - NP Anchor Translate, or keystroke if you assigned it)
45 Select a point anywhere in the scene (holding CTRL enables snapping). This will be your anchor point.
46 Place objects anywhere in the scene, in relation to the anchor point (again CTRL - snap).
47 Middle mouse button (MMB) enables axis constraint, numpad keys enable numerical input of distance, and RMB and ESC key interrupt the operation.
49 IMPORTANT PERFORMANCE NOTES
51 Should be key-mapped only for Object Mode. Other modes are not supported and key definitions should not be replaced.
53 WISH LIST
55 Bgl overlay for snapping modes and eventualy the translate path
56 Blf instructions on screen, preferably interactive
57 Smarter code and faster performance
59 WARNINGS
61 None so far
62 '''
64 bl_info = {
65 'name': 'NP 020 Point Move',
66 'author': 'Okavango & the Blenderartists community. Special thanks to CoDEmanX, lukas_t, matali',
67 'version': (0, 2, 0),
68 'blender': (2, 75, 0),
69 'location': 'View3D',
70 'warning': '',
71 'description': 'Moves selected objects using "take" and "place" snap points',
72 'doc_url': '',
73 'category': '3D View'}
75 import bpy
76 import copy
77 import bgl
78 import blf
79 import mathutils
80 from bpy_extras import view3d_utils
81 from bpy.app.handlers import persistent
83 from .utils_geometry import *
84 from .utils_graphics import *
85 from .utils_function import *
88 # Defining the main class - the macro:
90 class NP020PointMove(bpy.types.Macro):
91 bl_idname = 'object.np_020_point_move'
92 bl_label = 'NP 020 Point Move'
93 bl_options = {'UNDO'}
96 # Defining the storage class that will serve as a variable bank for exchange among the classes. Later, this bank will receive more variables with their values for safe keeping, as the program goes on:
98 class NP020PM:
100 flag = 'TAKE'
103 # Defining the scene update algorithm that will track the state of the objects during modal transforms, which is otherwise impossible:
105 @persistent
106 def NPPM_scene_update(context):
108 if bpy.data.objects.is_updated:
109 a = 1
112 # Defining the first of the classes from the macro, that will gather the current system settings set by the user. Some of the system settings will be changed during the process, and will be restored when macro has completed.
114 class NPPMGetContext(bpy.types.Operator):
115 bl_idname = 'object.np_pm_get_context'
116 bl_label = 'NP PM Get Context'
117 bl_options = {'INTERNAL'}
119 def execute(self, context):
120 if bpy.context.selected_objects == []:
121 self.report({'WARNING'}, "Please select objects first")
122 return {'CANCELLED'}
123 NP020PM.use_snap = copy.deepcopy(bpy.context.tool_settings.use_snap)
124 NP020PM.snap_element = copy.deepcopy(bpy.context.tool_settings.snap_element)
125 NP020PM.snap_target = copy.deepcopy(bpy.context.tool_settings.snap_target)
126 NP020PM.pivot_point = copy.deepcopy(bpy.context.space_data.pivot_point)
127 NP020PM.trans_orient = copy.deepcopy(bpy.context.space_data.transform_orientation)
128 NP020PM.curloc = copy.deepcopy(bpy.context.scene.cursor.location)
129 NP020PM.acob = bpy.context.active_object
130 if bpy.context.mode == 'OBJECT':
131 NP020PM.edit_mode = 'OBJECT'
132 elif bpy.context.mode in ('EDIT_MESH', 'EDIT_CURVE', 'EDIT_SURFACE', 'EDIT_TEXT', 'EDIT_ARMATURE', 'EDIT_METABALL', 'EDIT_LATTICE'):
133 NP020PM.edit_mode = 'EDIT'
134 elif bpy.context.mode == 'POSE':
135 NP020PM.edit_mode = 'POSE'
136 elif bpy.context.mode == 'SCULPT':
137 NP020PM.edit_mode = 'SCULPT'
138 elif bpy.context.mode == 'PAINT_WEIGHT':
139 NP020PM.edit_mode = 'WEIGHT_PAINT'
140 elif bpy.context.mode == 'PAINT_TEXTURE':
141 NP020PM.edit_mode = 'TEXTURE_PAINT'
142 elif bpy.context.mode == 'PAINT_VERTEX':
143 NP020PM.edit_mode = 'VERTEX_PAINT'
144 elif bpy.context.mode == 'PARTICLE':
145 NP020PM.edit_mode = 'PARTICLE_EDIT'
146 return {'FINISHED'}
149 # Defining the operator for aquiring the list of selected objects and storing them for later re-calls:
151 class NPPMGetSelection(bpy.types.Operator):
152 bl_idname = 'object.np_pm_get_selection'
153 bl_label = 'NP PM Get Selection'
154 bl_options = {'INTERNAL'}
156 def execute(self, context):
157 # Reading and storing the selection:
158 NP020PM.selob = bpy.context.selected_objects
159 return {'FINISHED'}
162 # Defining the operator that will read the mouse position in 3D when the command is activated and store it as a location for placing the helper object under the mouse:
164 class NPPMGetMouseloc(bpy.types.Operator):
165 bl_idname = 'object.np_pm_get_mouseloc'
166 bl_label = 'NP PM Get Mouseloc'
167 bl_options = {'INTERNAL'}
169 def modal(self, context, event):
170 region = context.region
171 rv3d = context.region_data
172 co2d = ((event.mouse_region_x, event.mouse_region_y))
173 view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, co2d)
174 enterloc = view3d_utils.region_2d_to_origin_3d(region, rv3d, co2d) + view_vector/5
175 NP020PM.enterloc = copy.deepcopy(enterloc)
176 # np_print('02_RadMouseloc_FINISHED', ';', 'flag = ', Storage.flag)
177 return{'FINISHED'}
179 def invoke(self,context,event):
180 args = (self, context)
181 context.window_manager.modal_handler_add(self)
182 # np_print('02_ReadMouseloc_INVOKED_FINISHED', ';', 'flag = ', NP020PM.flag)
183 return {'RUNNING_MODAL'}
186 # Defining the operator that will generate the helper object at the spot marked by mouse, preparing for translation:
188 class NPPMAddHelper(bpy.types.Operator):
189 bl_idname = 'object.np_pm_add_helper'
190 bl_label = 'NP PM Add Helper'
191 bl_options = {'INTERNAL'}
193 def execute(self, context):
194 # np_print('03_AddHelpers_START', ';', 'flag = ', NP020PM.flag)
195 enterloc = NP020PM.enterloc
196 bpy.ops.object.add(type = 'MESH',location = enterloc)
197 helper = bpy.context.active_object
198 helper.name = 'NP_PM_helper'
199 NP020PM.helper = helper
200 # np_print('03_AddHelpers_FINISHED', ';', 'flag = ', NP020PM.flag)
201 return{'FINISHED'}
204 # Defining the operator that will change some of the system settings and prepare objects for the operation:
206 class NPPMPrepareContext(bpy.types.Operator):
207 bl_idname = 'object.np_pm_prepare_context'
208 bl_label = 'NP PM Prepare Context'
209 bl_options = {'INTERNAL'}
211 def execute(self, context):
212 selob = NP020PM.selob
213 helper = NP020PM.helper
214 for ob in selob:
215 ob.select_set(False)
216 helper.select_set(True)
217 bpy.context.view_layer.objects.active = helper
218 bpy.context.tool_settings.use_snap = False
219 bpy.context.tool_settings.snap_element = 'VERTEX'
220 bpy.context.tool_settings.snap_target = 'ACTIVE'
221 bpy.context.space_data.pivot_point = 'ACTIVE_ELEMENT'
222 bpy.context.space_data.transform_orientation = 'GLOBAL'
223 return{'FINISHED'}
226 # Defining the operator that will let the user translate the helper to the desired point. It also uses some listening operators that clean up the leftovers should the user interrupt the command. Many thanks to CoDEmanX and lukas_t:
228 class NPPMRunTranslate(bpy.types.Operator):
229 bl_idname = 'object.np_pm_run_translate'
230 bl_label = 'NP PM Run Translate'
231 bl_options = {'INTERNAL'}
233 count = 0
235 def modal(self, context, event):
236 context.area.tag_redraw()
237 flag = NP020PM.flag
238 selob = NP020PM.selob
239 helper = NP020PM.helper
242 if event.type in ('LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE') and event.value == 'RELEASE':
243 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
244 if flag == 'TAKE':
245 NP020PM.takeloc = copy.deepcopy(helper.location)
246 NP020PM.flag = 'PLACE'
247 elif flag == 'PLACE':
248 NP020PM.flag = 'EXIT'
249 return{'FINISHED'}
251 elif event.type in ('ESC', 'RIGHTMOUSE'):
252 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
253 NP020PM.flag = 'EXIT'
254 return{'FINISHED'}
256 return{'PASS_THROUGH'}
258 def invoke(self, context, event):
259 # np_print('04_RunTrans_INVOKE_START')
260 helper = NP020PM.helper
261 flag = NP020PM.flag
262 selob = NP020PM.selob
263 # np_print('flag =', flag)
264 if context.area.type == 'VIEW_3D':
265 if flag == 'EXIT':
266 # np_print('04_RunTrans_INVOKE_DECLINED_FINISHED',';','flag = ', flag)
267 return {'FINISHED'}
268 elif flag == 'PLACE':
269 for ob in selob:
270 ob.select_set(True)
271 bpy.context.view_layer.objects.active = helper
272 args = (self, context)
273 self._handle = bpy.types.SpaceView3D.draw_handler_add(DRAW_RunTranslate, args, 'WINDOW', 'POST_PIXEL')
274 context.window_manager.modal_handler_add(self)
275 bpy.ops.transform.translate('INVOKE_DEFAULT')
276 # np_print('04_RunTrans_INVOKED_RUNNING_MODAL',';','flag = ', Storage.flag)
277 return {'RUNNING_MODAL'}
279 else:
280 self.report({'WARNING'}, "View3D not found, cannot run operator")
281 # np_print('04_RunTrans_INVOKE_DECLINED_FINISHED',';','flag = ', Storage.flag)
282 return {'FINISHED'}
285 # Defining the set of instructions that will draw the OpenGL elements on the screen during the execution of RunTranslate operator:
287 def DRAW_RunTranslate(self, context):
289 # np_print('04_DRAW_RunTranslate_START',';','flag = ', Storage.flag)
290 flag = NP020PM.flag
291 sel = bpy.context.selected_objects
292 lensel = len(sel)
293 helper = NP020PM.helper
299 if flag == 'TAKE':
300 takeloc = helper.location
301 placeloc = helper.location
302 main = 'select "take" point'
303 keys_aff = 'LMB - select, CTRL - snap'
304 keys_nav = ''
305 keys_neg = 'ESC, RMB - quit'
307 badge_mode = 'RUN'
308 message_main = 'CTRL+SNAP'
309 message_aux = None
310 aux_num = None
311 aux_str = None
313 elif flag == 'PLACE':
314 takeloc = NP020PM.takeloc
315 placeloc = helper.location
316 main = 'select "place" point'
317 keys_aff = 'LMB - select, CTRL - snap, MMB - lock axis, NUMPAD - value'
318 keys_nav = ''
319 keys_neg = 'ESC, RMB - quit'
321 badge_mode = 'RUN'
322 message_main = 'CTRL+SNAP'
323 message_aux = None
324 aux_num = None
325 aux_str = None
328 # ON-SCREEN INSTRUCTIONS:
330 region = bpy.context.region
331 rv3d = bpy.context.region_data
332 instruct = main
334 display_instructions(region, rv3d, instruct, keys_aff, keys_nav, keys_neg)
336 co2d = view3d_utils.location_3d_to_region_2d(region, rv3d, helper.location)
338 symbol = [[19, 34], [18, 35], [19, 36], [18, 35],
339 [26, 35], [25, 34], [26, 35], [25, 37],
340 [26, 35], [22, 35], [22, 39], [21, 38],
341 [22, 39], [23, 38], [22, 39], [22, 31],
342 [21, 32], [22, 31], [23, 32]]
344 display_cursor_badge(co2d, symbol, badge_mode, message_main, message_aux, aux_num, aux_str)
346 display_line_between_two_points(region, rv3d, takeloc, placeloc)
348 display_distance_between_two_points(region, rv3d, takeloc, placeloc)
350 # Restoring the object selection and system settings from before the operator activation:
352 class NPPMRestoreContext(bpy.types.Operator):
353 bl_idname = "object.np_pm_restore_context"
354 bl_label = "NP PM Restore Context"
355 bl_options = {'INTERNAL'}
357 def execute(self, context):
358 selob = NP020PM.selob
359 helper = NP020PM.helper
360 bpy.ops.object.select_all(action = 'DESELECT')
361 helper.select_set(True)
362 bpy.ops.object.delete('EXEC_DEFAULT')
363 for ob in selob:
364 ob.select_set(True)
365 bpy.context.view_layer.objects.active = ob
366 bpy.context.tool_settings.use_snap = NP020PM.use_snap
367 bpy.context.tool_settings.snap_element = NP020PM.snap_element
368 bpy.context.tool_settings.snap_target = NP020PM.snap_target
369 bpy.context.space_data.pivot_point = NP020PM.pivot_point
370 bpy.context.space_data.transform_orientation = NP020PM.trans_orient
371 if NP020PM.acob is not None:
372 bpy.context.view_layer.objects.active = NP020PM.acob
373 bpy.ops.object.mode_set(mode = NP020PM.edit_mode)
374 NP020PM.flag = 'TAKE'
375 return {'FINISHED'}
378 # This is the actual addon process, the algorithm that defines the order of operator activation inside the main macro:
380 def register():
382 NP020PointMove.define('OBJECT_OT_np_pm_get_context')
383 NP020PointMove.define('OBJECT_OT_np_pm_get_selection')
384 NP020PointMove.define('OBJECT_OT_np_pm_get_mouseloc')
385 NP020PointMove.define('OBJECT_OT_np_pm_add_helper')
386 NP020PointMove.define('OBJECT_OT_np_pm_prepare_context')
387 for i in range(1, 3):
388 NP020PointMove.define('OBJECT_OT_np_pm_run_translate')
389 NP020PointMove.define('OBJECT_OT_np_pm_restore_context')
391 def unregister():
392 pass