Update for API change: scene.cursor_location -> scene.cursor.location
[blender-addons-contrib.git] / space_view3d_library_hide.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 # Script copyright (C) Campbell Barton
22
23 bl_info = {
24     "name": "Library Hide",
25     "description": "Hide objects within library dupligroups",
26     "author": "Campbell Barton",
27     "version": (1, 0),
28     "blender": (2, 63, 0),
29     'location': 'Search: RayCast View Operator',
30     "wiki_url": "",
31     "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
32     "category": "3D View",
33 }
34
35 import bpy
36 from mathutils import Vector
37 from bpy_extras import view3d_utils
38
39 LIB_HIDE_TEXT_ID = "blender_hide_objects.py"
40
41 LIB_HIDE_TEXT_HEADER = """
42 import bpy
43 print("running: %r" % __file__)
44 def hide(name, lib):
45     obj = bpy.data.objects.get((name, lib))
46     if obj is None:
47         print("hide can't find: %r %r" % (name, lib))
48     else:
49         obj.hide = obj.hide_render = True
50
51 """
52
53 def pick_object(context, event, pick_objects, ray_max=10000.0):
54     """Run this function on left mouse, execute the ray cast"""
55     # get the context arguments
56     scene = context.scene
57     region = context.region
58     rv3d = context.region_data
59     coord = event.mouse_region_x, event.mouse_region_y
60
61     # get the ray from the viewport and mouse
62     view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
63     ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
64     ray_target = ray_origin + (view_vector * ray_max)
65
66     scene.cursor.location = ray_target
67
68     def visible_objects_and_duplis():
69         """Loop over (object, matrix) pairs (mesh only)"""
70
71         for obj in context.visible_objects:  # scene.objects:
72             if obj.hide:
73                 continue
74
75             if obj.type == 'MESH':
76                 yield (None, obj, obj.matrix_world.copy())
77
78             if obj.instance_type != 'NONE':
79                 print("DupliInst: %r" % obj)
80                 obj.dupli_list_create(scene)
81                 # matrix = obj.matrix_world.copy()
82                 for dob in obj.dupli_list:
83                     obj_dupli = dob.object
84                     if not obj_dupli.hide:
85                         # print("Dupli: %r" % obj_dupli)
86                         if obj_dupli.type == 'MESH':
87                             yield (obj, obj_dupli, dob.matrix.copy())
88
89                 obj.dupli_list_clear()
90
91     def obj_ray_cast(obj, matrix):
92         """Wrapper for ray casting that moves the ray into object space"""
93
94         # get the ray relative to the object
95         matrix_inv = matrix.inverted()
96         ray_origin_obj = matrix_inv * ray_origin
97         ray_target_obj = matrix_inv * ray_target
98
99         mesh = obj.data
100         if not mesh.polygons:
101             return None, None, None
102
103         hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
104
105         if face_index == -1:
106             hit, normal, face_index = obj.ray_cast(ray_target_obj, ray_origin_obj)
107
108         if face_index != -1:
109             return hit, normal, face_index
110         else:
111             return None, None, None
112
113     # cast rays and find the closest object
114     best_length_squared = ray_max * ray_max
115     best_obj = None
116     best_obj_parent = None
117
118     for obj_parent, obj, matrix in visible_objects_and_duplis():
119         if obj.type == 'MESH':
120             hit, normal, face_index = obj_ray_cast(obj, matrix)
121             if hit is not None:
122                 length_squared = (hit - ray_origin).length_squared
123                 if length_squared < best_length_squared:
124                     best_length_squared = length_squared
125                     best_obj = obj
126                     best_obj_parent = obj_parent
127
128     # now we have the object under the mouse cursor,
129     # we could do lots of stuff but for the example just select.
130     if best_obj is not None:
131         pick_objects.append((best_obj, best_obj.hide, best_obj.hide_render))
132         best_obj.hide = True
133         best_obj.hide_render = True
134
135         #if best_obj_parent:
136         #    best_obj_parent.update_tag(refresh={'OBJECT'})
137         #scene.update()
138         return True
139     else:
140         print("found none")
141         return False
142
143
144 def pick_finalize(context, pick_objects):
145     text = bpy.data.texts.get((LIB_HIDE_TEXT_ID, None))
146     if text is None:
147         text = bpy.data.texts.new(LIB_HIDE_TEXT_ID)
148         text.use_module = True
149         is_new = True
150     else:
151         is_new = False
152
153     if is_new:
154         data = []
155
156         data += LIB_HIDE_TEXT_HEADER.split("\n")
157     else:
158         data = text.as_string().split("\n")
159
160     data.append("# ---")
161
162     for pick_obj_tuple in pick_objects:
163
164         pick_obj = pick_obj_tuple[0]
165
166         pick_obj.hide = True
167         pick_obj.hide_render = True
168
169         line = "hide(%r, %s)" % (pick_obj.name, repr(pick_obj.library.filepath) if pick_obj.library is not None else "None")
170         data.append(line)
171
172     text.from_string("\n".join(data))
173
174
175 def pick_restore(pick_obj):
176     best_obj, hide, hide_render = pick_obj
177     best_obj.hide = hide
178     best_obj.hide_render = hide_render
179
180
181 class ViewOperatorRayCast(bpy.types.Operator):
182     """Modal object selection with a ray cast"""
183     bl_idname = "view3d.modal_operator_raycast"
184     bl_label = "RayCast View Operator"
185
186     _header_text = "Add: LMB, Undo: BackSpace, Finish: Enter"
187
188     def _update_header(self, context):
189         if self.pick_objects:
190             pick_obj = self.pick_objects[-1][0]
191             info_obj = "%s, %s" % (pick_obj.name, pick_obj.library.filepath if pick_obj.library is not None else "None")
192             info = "%s - added: %s" % (ViewOperatorRayCast._header_text, info_obj)
193         else:
194             info = ViewOperatorRayCast._header_text
195
196         context.area.header_text_set(info)
197
198     def modal(self, context, event):
199         if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
200             # allow navigation
201             return {'PASS_THROUGH'}
202         elif event.type == 'LEFTMOUSE':
203             if event.value == 'RELEASE':
204                 if pick_object(context, event, self.pick_objects):
205                     self._update_header(context)
206                 return {'RUNNING_MODAL'}
207         elif event.type == 'BACK_SPACE':
208             if event.value == 'RELEASE':
209                 if self.pick_objects:
210                     pick_obj = self.pick_objects.pop()
211                     pick_restore(pick_obj)
212                     self._update_header(context)
213
214         elif event.type in {'RET', 'NUMPAD_ENTER'}:
215             if event.value == 'RELEASE':
216                 if self.pick_objects:  # avoid enter taking effect on startup
217                     pick_finalize(context, self.pick_objects)
218                     context.area.header_text_set(None)
219                     self.report({'INFO'}, "Finished")
220                     return {'FINISHED'}
221
222         elif event.type in {'RIGHTMOUSE', 'ESC'}:
223             if event.value == 'RELEASE':
224                 for pick_obj in self.pick_objects:
225                     pick_restore(pick_obj)
226                 context.area.header_text_set(None)
227                 self.report({'INFO'}, "Cancelled")
228                 return {'CANCELLED'}
229
230         return {'RUNNING_MODAL'}
231
232     def invoke(self, context, event):
233         if context.space_data.type == 'VIEW_3D':
234
235             self.pick_objects = []
236             self._update_header(context)
237
238             context.window_manager.modal_handler_add(self)
239             return {'RUNNING_MODAL'}
240         else:
241             self.report({'WARNING'}, "Active space must be a View3d")
242             return {'CANCELLED'}
243
244
245 def register():
246     bpy.utils.register_class(ViewOperatorRayCast)
247
248
249 def unregister():
250     bpy.utils.unregister_class(ViewOperatorRayCast)
251
252
253 if __name__ == "__main__":
254     register()