Skip to content
Snippets Groups Projects
space_view3d_library_hide.py 8.19 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    # Script copyright (C) Campbell Barton
    
    
    Campbell Barton's avatar
    Campbell Barton committed
    bl_info = {
        "name": "Library Hide",
        "description": "Hide objects within library dupligroups",
        "author": "Campbell Barton",
        "version": (1, 0),
    
        "blender": (2, 63, 0),
    
        'location': 'Search: RayCast View Operator',
    
    Campbell Barton's avatar
    Campbell Barton committed
        "wiki_url": "",
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    
    Campbell Barton's avatar
    Campbell Barton committed
        "category": "3D View",
    }
    
    
    import bpy
    from mathutils import Vector
    from bpy_extras import view3d_utils
    
    LIB_HIDE_TEXT_ID = "blender_hide_objects.py"
    
    LIB_HIDE_TEXT_HEADER = """
    import bpy
    print("running: %r" % __file__)
    def hide(name, lib):
        obj = bpy.data.objects.get((name, lib))
        if obj is None:
            print("hide can't find: %r %r" % (name, lib))
        else:
            obj.hide = obj.hide_render = True
    
    """
    
    def pick_object(context, event, pick_objects, ray_max=10000.0):
        """Run this function on left mouse, execute the ray cast"""
        # get the context arguments
        scene = context.scene
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y
    
        # get the ray from the viewport and mouse
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
        ray_target = ray_origin + (view_vector * ray_max)
    
        scene.cursor_location = ray_target
    
        def visible_objects_and_duplis():
            """Loop over (object, matrix) pairs (mesh only)"""
    
            for obj in context.visible_objects:  # scene.objects:
                if obj.hide:
                    continue
    
                if obj.type == 'MESH':
                    yield (None, obj, obj.matrix_world.copy())
    
    
                if obj.instance_type != 'NONE':
    
                    print("DupliInst: %r" % obj)
                    obj.dupli_list_create(scene)
                    # matrix = obj.matrix_world.copy()
                    for dob in obj.dupli_list:
                        obj_dupli = dob.object
                        if not obj_dupli.hide:
                            # print("Dupli: %r" % obj_dupli)
                            if obj_dupli.type == 'MESH':
                                yield (obj, obj_dupli, dob.matrix.copy())
    
                    obj.dupli_list_clear()
    
        def obj_ray_cast(obj, matrix):
            """Wrapper for ray casting that moves the ray into object space"""
    
            # get the ray relative to the object
            matrix_inv = matrix.inverted()
            ray_origin_obj = matrix_inv * ray_origin
            ray_target_obj = matrix_inv * ray_target
    
            mesh = obj.data
            if not mesh.polygons:
                return None, None, None
    
            hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
    
            if face_index == -1:
                hit, normal, face_index = obj.ray_cast(ray_target_obj, ray_origin_obj)
    
            if face_index != -1:
                return hit, normal, face_index
            else:
                return None, None, None
    
        # cast rays and find the closest object
        best_length_squared = ray_max * ray_max
        best_obj = None
        best_obj_parent = None
    
        for obj_parent, obj, matrix in visible_objects_and_duplis():
            if obj.type == 'MESH':
                hit, normal, face_index = obj_ray_cast(obj, matrix)
                if hit is not None:
                    length_squared = (hit - ray_origin).length_squared
                    if length_squared < best_length_squared:
                        best_length_squared = length_squared
                        best_obj = obj
                        best_obj_parent = obj_parent
    
        # now we have the object under the mouse cursor,
        # we could do lots of stuff but for the example just select.
        if best_obj is not None:
            pick_objects.append((best_obj, best_obj.hide, best_obj.hide_render))
            best_obj.hide = True
            best_obj.hide_render = True
    
            #if best_obj_parent:
            #    best_obj_parent.update_tag(refresh={'OBJECT'})
            #scene.update()
            return True
        else:
            print("found none")
            return False
    
    
    def pick_finalize(context, pick_objects):
        text = bpy.data.texts.get((LIB_HIDE_TEXT_ID, None))
        if text is None:
            text = bpy.data.texts.new(LIB_HIDE_TEXT_ID)
            text.use_module = True
            is_new = True
        else:
            is_new = False
    
        if is_new:
            data = []
    
            data += LIB_HIDE_TEXT_HEADER.split("\n")
        else:
            data = text.as_string().split("\n")
    
        data.append("# ---")
    
        for pick_obj_tuple in pick_objects:
    
            pick_obj = pick_obj_tuple[0]
    
            pick_obj.hide = True
            pick_obj.hide_render = True
    
            line = "hide(%r, %s)" % (pick_obj.name, repr(pick_obj.library.filepath) if pick_obj.library is not None else "None")
            data.append(line)
    
        text.from_string("\n".join(data))
    
    
    def pick_restore(pick_obj):
        best_obj, hide, hide_render = pick_obj
        best_obj.hide = hide
        best_obj.hide_render = hide_render
    
    
    class ViewOperatorRayCast(bpy.types.Operator):
        """Modal object selection with a ray cast"""
        bl_idname = "view3d.modal_operator_raycast"
        bl_label = "RayCast View Operator"
    
        _header_text = "Add: LMB, Undo: BackSpace, Finish: Enter"
    
        def _update_header(self, context):
            if self.pick_objects:
                pick_obj = self.pick_objects[-1][0]
                info_obj = "%s, %s" % (pick_obj.name, pick_obj.library.filepath if pick_obj.library is not None else "None")
                info = "%s - added: %s" % (ViewOperatorRayCast._header_text, info_obj)
            else:
                info = ViewOperatorRayCast._header_text
    
            context.area.header_text_set(info)
    
        def modal(self, context, event):
            if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
                # allow navigation
                return {'PASS_THROUGH'}
            elif event.type == 'LEFTMOUSE':
                if event.value == 'RELEASE':
                    if pick_object(context, event, self.pick_objects):
                        self._update_header(context)
                    return {'RUNNING_MODAL'}
            elif event.type == 'BACK_SPACE':
                if event.value == 'RELEASE':
                    if self.pick_objects:
                        pick_obj = self.pick_objects.pop()
                        pick_restore(pick_obj)
                        self._update_header(context)
    
            elif event.type in {'RET', 'NUMPAD_ENTER'}:
                if event.value == 'RELEASE':
                    if self.pick_objects:  # avoid enter taking effect on startup
                        pick_finalize(context, self.pick_objects)
    
                        context.area.header_text_set(None)
    
                        self.report({'INFO'}, "Finished")
                        return {'FINISHED'}
    
            elif event.type in {'RIGHTMOUSE', 'ESC'}:
                if event.value == 'RELEASE':
                    for pick_obj in self.pick_objects:
                        pick_restore(pick_obj)
    
                    context.area.header_text_set(None)
    
                    self.report({'INFO'}, "Cancelled")
                    return {'CANCELLED'}
    
            return {'RUNNING_MODAL'}
    
        def invoke(self, context, event):
            if context.space_data.type == 'VIEW_3D':
    
                self.pick_objects = []
                self._update_header(context)
    
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
            else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'}
    
    
    def register():
        bpy.utils.register_class(ViewOperatorRayCast)
    
    
    def unregister():
        bpy.utils.unregister_class(ViewOperatorRayCast)
    
    
    if __name__ == "__main__":
        register()