From 8d37aaeca1f9dc74224da378cecd392fd1cf361b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 10 Aug 2015 17:26:37 +0200 Subject: Data previews: add utils to generate/clear previews. Not much to add, you can now clear previews from current .blend file, or a set of non-opened files. Likewise, you can generate previews (for mat/tex, objects, groups, scenes, ...). --- .../bl_previews_utils/bl_previews_render.py | 489 +++++++++++++++++++++ release/scripts/startup/bl_operators/__init__.py | 1 + release/scripts/startup/bl_operators/file.py | 243 ++++++++++ release/scripts/startup/bl_ui/space_info.py | 6 + source/blender/blenkernel/BKE_idcode.h | 3 + source/blender/blenkernel/intern/idcode.c | 84 ++++ .../blender/editors/include/UI_interface_icons.h | 5 + source/blender/editors/interface/interface_icons.c | 66 ++- source/blender/makesdna/DNA_ID.h | 35 ++ source/blender/makesrna/intern/rna_ID.c | 21 +- source/blender/python/intern/bpy_app.c | 15 + source/blender/windowmanager/intern/wm_operators.c | 68 +++ 12 files changed, 1030 insertions(+), 6 deletions(-) create mode 100644 release/scripts/modules/bl_previews_utils/bl_previews_render.py create mode 100644 release/scripts/startup/bl_operators/file.py diff --git a/release/scripts/modules/bl_previews_utils/bl_previews_render.py b/release/scripts/modules/bl_previews_utils/bl_previews_render.py new file mode 100644 index 00000000000..0ef49601c43 --- /dev/null +++ b/release/scripts/modules/bl_previews_utils/bl_previews_render.py @@ -0,0 +1,489 @@ +# ***** 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 ***** + +# + +# Populate a template file (POT format currently) from Blender RNA/py/C data. +# Note: This script is meant to be used from inside Blender! + +import collections +import os +import sys + +import bpy +from mathutils import Vector, Euler + + +INTERN_PREVIEW_TYPES = {'MATERIAL', 'LAMP', 'WORLD', 'TEXTURE', 'IMAGE'} +OBJECT_TYPES_RENDER = {'MESH', 'CURVE', 'SURFACE', 'META', 'FONT'} + + +def rna_backup_gen(data, include_props=None, exclude_props=None, root=()): + # only writable properties... + for p in data.bl_rna.properties: + pid = p.identifier + if pid in {'rna_type',}: + continue + path = root + (pid,) + if include_props is not None and path not in include_props: + continue + if exclude_props is not None and path in exclude_props: + continue + val = getattr(data, pid) + if val is not None and p.type == 'POINTER': + # recurse! + yield from rna_backup_gen(val, include_props, exclude_props, root=path) + elif data.is_property_readonly(pid): + continue + else: + yield path, val + + +def rna_backup_restore(data, backup): + for path, val in backup: + dt = data + for pid in path[:-1]: + dt = getattr(dt, pid) + setattr(dt, path[-1], val) + + +def do_previews(do_objects, do_groups, do_scenes, do_data_intern): + # Helpers. + RenderContext = collections.namedtuple("RenderContext", ( + "scene", "world", "camera", "lamp", "camera_data", "lamp_data", "image", # All those are names! + "backup_scene", "backup_world", "backup_camera", "backup_lamp", "backup_camera_data", "backup_lamp_data", + )) + + RENDER_PREVIEW_SIZE = bpy.app.render_preview_size + + def render_context_create(engine, objects_ignored): + if engine == '__SCENE': + backup_scene, backup_world, backup_camera, backup_lamp, backup_camera_data, backup_lamp_data = [()] * 6 + scene = bpy.context.screen.scene + exclude_props = {('world',), ('camera',), ('tool_settings',), ('preview',)} + backup_scene = tuple(rna_backup_gen(scene, exclude_props=exclude_props)) + world = scene.world + camera = scene.camera + if camera: + camera_data = camera.data + else: + backup_camera, backup_camera_data = [None] * 2 + camera_data = bpy.data.cameras.new("TEMP_preview_render_camera") + camera = bpy.data.objects.new("TEMP_preview_render_camera", camera_data) + camera.rotation_euler = Euler((1.1635528802871704, 0.0, 0.7853981852531433), 'XYZ') # (66.67, 0.0, 45.0) + scene.camera = camera + scene.objects.link(camera) + # TODO: add lamp if none found in scene? + lamp = None + lamp_data = None + else: + backup_scene, backup_world, backup_camera, backup_lamp, backup_camera_data, backup_lamp_data = [None] * 6 + + scene = bpy.data.scenes.new("TEMP_preview_render_scene") + world = bpy.data.worlds.new("TEMP_preview_render_world") + camera_data = bpy.data.cameras.new("TEMP_preview_render_camera") + camera = bpy.data.objects.new("TEMP_preview_render_camera", camera_data) + lamp_data = bpy.data.lamps.new("TEMP_preview_render_lamp", 'SPOT') + lamp = bpy.data.objects.new("TEMP_preview_render_lamp", lamp_data) + + objects_ignored.add((camera.name, lamp.name)) + + scene.world = world + + camera.rotation_euler = Euler((1.1635528802871704, 0.0, 0.7853981852531433), 'XYZ') # (66.67, 0.0, 45.0) + scene.camera = camera + scene.objects.link(camera) + + lamp.rotation_euler = Euler((0.7853981852531433, 0.0, 1.7453292608261108), 'XYZ') # (45.0, 0.0, 100.0) + lamp_data.falloff_type = 'CONSTANT' + lamp_data.spot_size = 1.0471975803375244 # 60 + scene.objects.link(lamp) + + if engine == 'BLENDER_RENDER': + scene.render.engine = 'BLENDER_RENDER' + scene.render.alpha_mode = 'TRANSPARENT' + + world.use_sky_blend = True + world.horizon_color = 0.9, 0.9, 0.9 + world.zenith_color = 0.5, 0.5, 0.5 + world.ambient_color = 0.1, 0.1, 0.1 + world.light_settings.use_environment_light = True + world.light_settings.environment_energy = 1.0 + world.light_settings.environment_color = 'SKY_COLOR' + elif engine == 'CYCLES': + scene.render.engine = 'CYCLES' + scene.cycles.film_transparent = True + # TODO: define Cycles world? + + scene.render.image_settings.file_format = 'PNG' + scene.render.image_settings.color_depth = '8' + scene.render.image_settings.color_mode = 'RGBA' + scene.render.image_settings.compression = 25 + scene.render.resolution_x = RENDER_PREVIEW_SIZE + scene.render.resolution_y = RENDER_PREVIEW_SIZE + scene.render.resolution_percentage = 100 + scene.render.filepath = os.path.join(bpy.app.tempdir, 'TEMP_preview_render.png') + scene.render.use_overwrite = True + scene.render.use_stamp = False + + image = bpy.data.images.new("TEMP_render_image", RENDER_PREVIEW_SIZE, RENDER_PREVIEW_SIZE, alpha=True) + image.source = 'FILE' + image.filepath = scene.render.filepath + + return RenderContext( + scene.name, world.name if world else None, camera.name, lamp.name if lamp else None, + camera_data.name, lamp_data.name if lamp_data else None, image.name, + backup_scene, backup_world, backup_camera, backup_lamp, backup_camera_data, backup_lamp_data, + ) + + def render_context_delete(render_context): + # We use try/except blocks here to avoid crash, too much things can go wrong, and we want to leave the current + # .blend as clean as possible! + success = True + + scene = bpy.data.scenes[render_context.scene] + try: + if render_context.backup_scene is None: + scene.world = None + scene.camera = None + if render_context.camera: + scene.objects.unlink(bpy.data.objects[render_context.camera]) + if render_context.lamp: + scene.objects.unlink(bpy.data.objects[render_context.lamp]) + bpy.data.scenes.remove(scene) + scene = None + else: + rna_backup_restore(scene, render_context.backup_scene) + except Exception as e: + print("ERROR:", e) + success = False + + if render_context.world is not None: + try: + world = bpy.data.worlds[render_context.world] + if render_context.backup_world is None: + if scene is not None: + scene.world = None + world.user_clear() + bpy.data.worlds.remove(world) + else: + rna_backup_restore(world, render_context.backup_world) + except Exception as e: + print("ERROR:", e) + success = False + + if render_context.camera: + try: + camera = bpy.data.objects[render_context.camera] + if render_context.backup_camera is None: + if scene is not None: + scene.camera = None + scene.objects.unlink(camera) + camera.user_clear() + bpy.data.objects.remove(camera) + bpy.data.cameras.remove(bpy.data.cameras[render_context.camera_data]) + else: + rna_backup_restore(camera, render_context.backup_camera) + rna_backup_restore(bpy.data.cameras[render_context.camera_data], render_context.backup_camera_data) + except Exception as e: + print("ERROR:", e) + success = False + + if render_context.lamp: + try: + lamp = bpy.data.objects[render_context.lamp] + if render_context.backup_lamp is None: + if scene is not None: + scene.objects.unlink(lamp) + lamp.user_clear() + bpy.data.objects.remove(lamp) + bpy.data.lamps.remove(bpy.data.lamps[render_context.lamp_data]) + else: + rna_backup_restore(lamp, render_context.backup_lamp) + rna_backup_restore(bpy.data.lamps[render_context.lamp_data], render_context.backup_lamp_data) + except Exception as e: + print("ERROR:", e) + success = False + + try: + image = bpy.data.images[render_context.image] + image.user_clear() + bpy.data.images.remove(image) + except Exception as e: + print("ERROR:", e) + success = False + + return success + + def objects_render_engine_guess(obs): + for obname in obs: + ob = bpy.data.objects[obname] + for matslot in ob.material_slots: + mat = matslot.material + if mat and mat.use_nodes and mat.node_tree: + for nd in mat.node_tree.nodes: + if nd.shading_compatibility == {'NEW_SHADING'}: + return 'CYCLES' + return 'BLENDER_RENDER' + + def object_bbox_merge(bbox, ob, ob_space): + if ob.bound_box: + ob_bbox = ob.bound_box + else: + ob_bbox = ((-ob.scale.x, -ob.scale.y, -ob.scale.z), (ob.scale.x, ob.scale.y, ob.scale.z)) + for v in ob.bound_box: + v = ob_space.matrix_world.inverted() * ob.matrix_world * Vector(v) + if bbox[0].x > v.x: + bbox[0].x = v.x + if bbox[0].y > v.y: + bbox[0].y = v.y + if bbox[0].z > v.z: + bbox[0].z = v.z + if bbox[1].x < v.x: + bbox[1].x = v.x + if bbox[1].y < v.y: + bbox[1].y = v.y + if bbox[1].z < v.z: + bbox[1].z = v.z + + def objects_bbox_calc(camera, objects): + bbox = (Vector((1e9, 1e9, 1e9)), Vector((-1e9, -1e9, -1e9))) + for obname in objects: + ob = bpy.data.objects[obname] + object_bbox_merge(bbox, ob, camera) + # Our bbox has been generated in camera local space, bring it back in world one + bbox[0][:] = camera.matrix_world * bbox[0] + bbox[1][:] = camera.matrix_world * bbox[1] + cos = ( + bbox[0].x, bbox[0].y, bbox[0].z, + bbox[0].x, bbox[0].y, bbox[1].z, + bbox[0].x, bbox[1].y, bbox[0].z, + bbox[0].x, bbox[1].y, bbox[1].z, + bbox[1].x, bbox[0].y, bbox[0].z, + bbox[1].x, bbox[0].y, bbox[1].z, + bbox[1].x, bbox[1].y, bbox[0].z, + bbox[1].x, bbox[1].y, bbox[1].z, + ) + return cos + + def preview_render_do(render_context, item_container, item_name, objects): + scene = bpy.data.scenes[render_context.scene] + if objects is not None: + camera = bpy.data.objects[render_context.camera] + lamp = bpy.data.objects[render_context.lamp] if render_context.lamp is not None else None + cos = objects_bbox_calc(camera, objects) + loc, ortho_scale = camera.camera_fit_coords(scene, cos) + camera.location = loc + if lamp: + loc, ortho_scale = lamp.camera_fit_coords(scene, cos) + lamp.location = loc + scene.update() + + bpy.ops.render.render(write_still=True) + + image = bpy.data.images[render_context.image] + item = getattr(bpy.data, item_container)[item_name] + image.reload() + # Note: we could use struct module here, but not quite sure it'd give any advantage really... + pix = tuple((round(r * 255)) + (round(g * 255) << 8) + (round(b * 255) << 16) + (round(a * 255) << 24) + for r, g, b, a in zip(*[iter(image.pixels)] * 4)) + item.preview.image_size = (RENDER_PREVIEW_SIZE, RENDER_PREVIEW_SIZE) + item.preview.image_pixels = pix + + # And now, main code! + do_save = True + + if do_data_intern: + bpy.ops.wm.previews_clear(id_type=INTERN_PREVIEW_TYPES) + bpy.ops.wm.previews_ensure() + + render_contexts = {} + + objects_ignored = set() + groups_ignored = set() + + prev_scenename = bpy.context.screen.scene.name + + if do_objects: + prev_shown = tuple(ob.hide_render for ob in bpy.data.objects) + for ob in bpy.data.objects: + if ob in objects_ignored: + continue + ob.hide_render = True + for root in bpy.data.objects: + if root.name in objects_ignored: + continue + if root.type not in OBJECT_TYPES_RENDER: + continue + objects = (root.name,) + + render_engine = objects_render_engine_guess(objects) + render_context = render_contexts.get(render_engine, None) + if render_context is None: + render_context = render_context_create(render_engine, objects_ignored) + render_contexts[render_engine] = render_context + + scene = bpy.data.scenes[render_context.scene] + bpy.context.screen.scene = scene + + for obname in objects: + ob = bpy.data.objects[obname] + if obname not in scene.objects: + scene.objects.link(ob) + ob.hide_render = False + scene.update() + + preview_render_do(render_context, 'objects', root.name, objects) + + # XXX Hyper Super Uber Suspicious Hack! + # Without this, on windows build, script excepts with following message: + # Traceback (most recent call last): + # File "", line 1, in + # File "", line 451, in + # File "", line 443, in main + # File "", line 327, in do_previews + # OverflowError: Python int too large to convert to C long + # ... :( + import sys + scene = bpy.data.scenes[render_context.scene] + for obname in objects: + ob = bpy.data.objects[obname] + scene.objects.unlink(ob) + ob.hide_render = True + + for ob, is_rendered in zip(bpy.data.objects, prev_shown): + ob.hide_render = is_rendered + + if do_groups: + for grp in bpy.data.groups: + if grp.name in groups_ignored: + continue + objects = tuple(ob.name for ob in grp.objects) + + render_engine = objects_render_engine_guess(objects) + render_context = render_contexts.get(render_engine, None) + if render_context is None: + render_context = render_context_create(render_engine, objects_ignored) + render_contexts[render_engine] = render_context + + scene = bpy.data.scenes[render_context.scene] + bpy.context.screen.scene = scene + + bpy.ops.object.group_instance_add(group=grp.name) + grp_ob = next((ob for ob in scene.objects if ob.dupli_group and ob.dupli_group.name == grp.name)) + grp_obname = grp_ob.name + scene.update() + + preview_render_do(render_context, 'groups', grp.name, objects) + + scene = bpy.data.scenes[render_context.scene] + scene.objects.unlink(bpy.data.objects[grp_obname]) + + bpy.context.screen.scene = bpy.data.scenes[prev_scenename] + for render_context in render_contexts.values(): + if not render_context_delete(render_context): + do_save = False # Do not save file if something went wrong here, we could 'pollute' it with temp data... + + if do_scenes: + for scene in bpy.data.scenes: + has_camera = scene.camera is not None + bpy.context.screen.scene = scene + render_context = render_context_create('__SCENE', objects_ignored) + scene.update() + + objects = None + if not has_camera: + # We had to add a temp camera, now we need to place it to see interesting objects! + objects = tuple(ob.name for ob in scene.objects + if (not ob.hide_render) and (ob.type in OBJECT_TYPES_RENDER)) + + preview_render_do(render_context, 'scenes', scene.name, objects) + + if not render_context_delete(render_context): + do_save = False + + bpy.context.screen.scene = bpy.data.scenes[prev_scenename] + if do_save: + print("Saving %s..." % bpy.data.filepath) + try: + bpy.ops.wm.save_mainfile() + except Exception as e: + # Might fail in some odd cases, like e.g. in regression files we have glsl/ram_glsl.blend which + # references an inexistent texture... Better not break in this case, just spit error to console. + print("ERROR:", e) + else: + print("*NOT* Saving %s, because some error(s) happened while deleting temp render data..." % bpy.data.filepath) + + +def do_clear_previews(do_objects, do_groups, do_scenes, do_data_intern): + if do_data_intern: + bpy.ops.wm.previews_clear(id_type=INTERN_PREVIEW_TYPES) + + if do_objects: + for ob in bpy.data.objects: + ob.preview.image_size = (0, 0) + + if do_groups: + for grp in bpy.data.groups: + grp.preview.image_size = (0, 0) + + if do_scenes: + for scene in bpy.data.scenes: + scene.preview.image_size = (0, 0) + + print("Saving %s..." % bpy.data.filepath) + bpy.ops.wm.save_mainfile() + + +def main(): + try: + import bpy + except ImportError: + print("This script must run from inside blender") + return + + import sys + import argparse + + # Get rid of Blender args! + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + + parser = argparse.ArgumentParser(description="Use Blender to generate previews for currently open Blender file's items.") + parser.add_argument('--clear', default=False, action="store_true", help="Clear previews instead of generating them.") + parser.add_argument('--no_scenes', default=True, action="store_false", help="Do not generate/clear previews for scene IDs.") + parser.add_argument('--no_groups', default=True, action="store_false", help="Do not generate/clear previews for group IDs.") + parser.add_argument('--no_objects', default=True, action="store_false", help="Do not generate/clear previews for object IDs.") + parser.add_argument('--no_data_intern', default=True, action="store_false", + help="Do not generate/clear previews for mat/tex/image/etc. IDs (those handled by core Blender code).") + args = parser.parse_args(argv) + + if args.clear: + print("clear!") + do_clear_previews(do_objects=args.no_objects, do_groups=args.no_groups, do_scenes=args.no_scenes, + do_data_intern=args.no_data_intern) + else: + print("render!") + do_previews(do_objects=args.no_objects, do_groups=args.no_groups, do_scenes=args.no_scenes, + do_data_intern=args.no_data_intern) + + +if __name__ == "__main__": + print("\n\n *** Running {} *** \n".format(__file__)) + print(" *** Blend file {} *** \n".format(bpy.data.filepath)) + main() + bpy.ops.wm.quit_blender() diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 4047505652f..35c7a55b6da 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -28,6 +28,7 @@ _modules = [ "anim", "clip", "console", + "file", "image", "mask", "mesh", diff --git a/release/scripts/startup/bl_operators/file.py b/release/scripts/startup/bl_operators/file.py new file mode 100644 index 00000000000..a6a6982e746 --- /dev/null +++ b/release/scripts/startup/bl_operators/file.py @@ -0,0 +1,243 @@ +# ##### 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 ##### + +# + +import bpy +from bpy.types import Operator +from bpy.props import ( + StringProperty, + BoolProperty, + CollectionProperty, + ) + +# ########## Datablock previews... ########## + +class WM_OT_previews_batch_generate(Operator): + """Generate selected .blend file's previews""" + bl_idname = "wm.previews_batch_generate" + bl_label = "Batch-Generate Previews" + bl_options = {'REGISTER'} + + # ----------- + # File props. + files = CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + directory = StringProperty( + maxlen=1024, + subtype='FILE_PATH', + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + # Show only images/videos, and directories! + filter_blender = BoolProperty( + default=True, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + filter_folder = BoolProperty( + default=True, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + # ----------- + # Own props. + use_scenes = BoolProperty( + default=True, + name="Scenes", + description="Generate scenes' previews", + ) + use_groups = BoolProperty( + default=True, + name="Groups", + description="Generate groups' previews", + ) + use_objects = BoolProperty( + default=True, + name="Objects", + description="Generate objects' previews", + ) + use_intern_data = BoolProperty( + default=True, + name="Mat/Tex/...", + description="Generate 'internal' previews (materials, textures, images, etc.)", + ) + + use_trusted = BoolProperty( + default=False, + name="Trusted Blend Files", + description="Enable python evaluation for selected files", + ) + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + if "subprocess" in locals(): + import imp + imp.reload(preview_render) + else: + import os + import subprocess + from bl_previews_utils import bl_previews_render as preview_render + + context.window_manager.progress_begin(0, len(self.files)) + context.window_manager.progress_update(0) + for i, fn in enumerate(self.files): + blen_path = os.path.join(self.directory, fn.name) + cmd = [ + bpy.app.binary_path, + "--background", + "--factory-startup", + "-noaudio", + ] + if self.use_trusted: + cmd.append("--enable-autoexec") + cmd.extend([ + blen_path, + "--python", + os.path.join(os.path.dirname(preview_render.__file__), "bl_previews_render.py"), + "--", + ]) + if not self.use_scenes: + cmd.append('--no_scenes') + if not self.use_groups: + cmd.append('--no_groups') + if not self.use_objects: + cmd.append('--no_objects') + if not self.use_intern_data: + cmd.append('--no_data_intern') + if subprocess.call(cmd): + self.report({'ERROR'}, "Previews generation process failed for file '%s'!" % blen_path) + context.window_manager.progress_end() + return {'CANCELLED'} + context.window_manager.progress_update(i + 1) + context.window_manager.progress_end() + + return {'FINISHED'} + + +class WM_OT_previews_batch_clear(Operator): + """Clear selected .blend file's previews""" + bl_idname = "wm.previews_batch_clear" + bl_label = "Batch-Clear Previews" + bl_options = {'REGISTER'} + + # ----------- + # File props. + files = CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + directory = StringProperty( + maxlen=1024, + subtype='FILE_PATH', + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + # Show only images/videos, and directories! + filter_blender = BoolProperty( + default=True, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + filter_folder = BoolProperty( + default=True, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + # ----------- + # Own props. + use_scenes = BoolProperty( + default=True, + name="Scenes", + description="Clear scenes' previews", + ) + use_groups = BoolProperty(default=True, + name="Groups", + description="Clear groups' previews", + ) + use_objects = BoolProperty( + default=True, + name="Objects", + description="Clear objects' previews", + ) + use_intern_data = BoolProperty( + default=True, + name="Mat/Tex/...", + description="Clear 'internal' previews (materials, textures, images, etc.)", + ) + + use_trusted = BoolProperty( + default=False, + name="Trusted Blend Files", + description="Enable python evaluation for selected files", + ) + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + if "subprocess" in locals(): + import imp + imp.reload(preview_render) + else: + import os + import subprocess + from bl_previews_utils import bl_previews_render as preview_render + + context.window_manager.progress_begin(0, len(self.files)) + context.window_manager.progress_update(0) + for i, fn in enumerate(self.files): + blen_path = os.path.join(self.directory, fn.name) + cmd = [ + bpy.app.binary_path, + "--background", + "--factory-startup", + "-noaudio", + ] + if self.use_trusted: + cmd.append("--enable-autoexec") + cmd.extend([ + blen_path, + "--python", + os.path.join(os.path.dirname(preview_render.__file__), "bl_previews_render.py"), + "--", + "--clear", + ]) + if not self.use_scenes: + cmd.append('--no_scenes') + if not self.use_groups: + cmd.append('--no_groups') + if not self.use_objects: + cmd.append('--no_objects') + if not self.use_intern_data: + cmd.append('--no_data_intern') + if subprocess.call(cmd): + self.report({'ERROR'}, "Previews clear process failed for file '%s'!" % blen_path) + context.window_manager.progress_end() + return {'CANCELLED'} + context.window_manager.progress_update(i + 1) + context.window_manager.progress_end() + + return {'FINISHED'} + diff --git a/release/scripts/startup/bl_ui/space_info.py b/release/scripts/startup/bl_ui/space_info.py index 48a1b28289e..d295cc19fb7 100644 --- a/release/scripts/startup/bl_ui/space_info.py +++ b/release/scripts/startup/bl_ui/space_info.py @@ -203,6 +203,12 @@ class INFO_MT_file_previews(Menu): layout = self.layout layout.operator("wm.previews_ensure") + layout.operator("wm.previews_batch_generate") + + layout.separator() + + layout.operator("wm.previews_clear") + layout.operator("wm.previews_batch_clear") class INFO_MT_game(Menu): diff --git a/source/blender/blenkernel/BKE_idcode.h b/source/blender/blenkernel/BKE_idcode.h index 420df77ff1e..ebad0d42232 100644 --- a/source/blender/blenkernel/BKE_idcode.h +++ b/source/blender/blenkernel/BKE_idcode.h @@ -39,6 +39,9 @@ int BKE_idcode_from_name(const char *name); bool BKE_idcode_is_linkable(int code); bool BKE_idcode_is_valid(int code); +int BKE_idcode_to_idfilter(const int idcode); +int BKE_idcode_from_idfilter(const int idfilter); + /** * Return an ID code and steps the index forward 1. * diff --git a/source/blender/blenkernel/intern/idcode.c b/source/blender/blenkernel/intern/idcode.c index 091d8a6ea17..3101d7874a6 100644 --- a/source/blender/blenkernel/intern/idcode.c +++ b/source/blender/blenkernel/intern/idcode.c @@ -165,6 +165,90 @@ int BKE_idcode_from_name(const char *name) return idt ? idt->code : 0; } +/** + * Convert an idcode into an idfilter (e.g. ID_OB -> FILTER_ID_OB). + */ +int BKE_idcode_to_idfilter(const int idcode) +{ +#define CASE_IDFILTER(_id) case ID_##_id: return FILTER_ID_##_id + + switch (idcode) { + CASE_IDFILTER(AC); + CASE_IDFILTER(AR); + CASE_IDFILTER(BR); + CASE_IDFILTER(CA); + CASE_IDFILTER(CU); + CASE_IDFILTER(GD); + CASE_IDFILTER(GR); + CASE_IDFILTER(IM); + CASE_IDFILTER(LA); + CASE_IDFILTER(LS); + CASE_IDFILTER(LT); + CASE_IDFILTER(MA); + CASE_IDFILTER(MB); + CASE_IDFILTER(MC); + CASE_IDFILTER(ME); + CASE_IDFILTER(MSK); + CASE_IDFILTER(NT); + CASE_IDFILTER(OB); + CASE_IDFILTER(PAL); + CASE_IDFILTER(PC); + CASE_IDFILTER(SCE); + CASE_IDFILTER(SPK); + CASE_IDFILTER(SO); + CASE_IDFILTER(TE); + CASE_IDFILTER(TXT); + CASE_IDFILTER(VF); + CASE_IDFILTER(WO); + default: + return 0; + } + +#undef CASE_IDFILTER +} + +/** + * Convert an idfilter into an idcode (e.g. FILTER_ID_OB -> ID_OB). + */ +int BKE_idcode_from_idfilter(const int idfilter) +{ +#define CASE_IDFILTER(_id) case FILTER_ID_##_id: return ID_##_id + + switch (idfilter) { + CASE_IDFILTER(AC); + CASE_IDFILTER(AR); + CASE_IDFILTER(BR); + CASE_IDFILTER(CA); + CASE_IDFILTER(CU); + CASE_IDFILTER(GD); + CASE_IDFILTER(GR); + CASE_IDFILTER(IM); + CASE_IDFILTER(LA); + CASE_IDFILTER(LS); + CASE_IDFILTER(LT); + CASE_IDFILTER(MA); + CASE_IDFILTER(MB); + CASE_IDFILTER(MC); + CASE_IDFILTER(ME); + CASE_IDFILTER(MSK); + CASE_IDFILTER(NT); + CASE_IDFILTER(OB); + CASE_IDFILTER(PAL); + CASE_IDFILTER(PC); + CASE_IDFILTER(SCE); + CASE_IDFILTER(SPK); + CASE_IDFILTER(SO); + CASE_IDFILTER(TE); + CASE_IDFILTER(TXT); + CASE_IDFILTER(VF); + CASE_IDFILTER(WO); + default: + return 0; + } + +#undef CASE_IDFILTER +} + /** * Convert an idcode into a name (plural). * diff --git a/source/blender/editors/include/UI_interface_icons.h b/source/blender/editors/include/UI_interface_icons.h index 634dd3d5bbc..945ac1b6db9 100644 --- a/source/blender/editors/include/UI_interface_icons.h +++ b/source/blender/editors/include/UI_interface_icons.h @@ -34,9 +34,12 @@ struct bContext; struct ID; +struct Scene; struct PreviewImage; struct PointerRNA; +enum eIconSizes; + typedef struct IconFile { struct IconFile *next, *prev; char filename[256]; /* FILE_MAXFILE size */ @@ -60,6 +63,7 @@ int UI_icon_get_height(int icon_id); void UI_id_icon_render( const struct bContext *C, struct Scene *scene, struct ID *id, const bool big, const bool use_job); +int UI_preview_render_size(enum eIconSizes size); void UI_icon_draw(float x, float y, int icon_id); void UI_icon_draw_preview(float x, float y, int icon_id); @@ -78,5 +82,6 @@ int UI_iconfile_get_index(const char *filename); struct PreviewImage *UI_icon_to_preview(int icon_id); int UI_rnaptr_icon_get(struct bContext *C, struct PointerRNA *ptr, int rnaicon, const bool big); +int UI_idcode_icon_get(const int idcode); #endif /* __UI_INTERFACE_ICONS_H__ */ diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index db1eacf57dc..95139c5656e 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -900,7 +900,7 @@ void UI_icons_init(int first_dyn_id) /* Render size for preview images and icons */ -static int preview_render_size(enum eIconSizes size) +int UI_preview_render_size(enum eIconSizes size) { switch (size) { case ICON_SIZE_ICON: @@ -916,7 +916,7 @@ static int preview_render_size(enum eIconSizes size) */ static void icon_create_rect(struct PreviewImage *prv_img, enum eIconSizes size) { - unsigned int render_size = preview_render_size(size); + unsigned int render_size = UI_preview_render_size(size); if (!prv_img) { if (G.debug & G_DEBUG) @@ -1399,6 +1399,68 @@ int UI_rnaptr_icon_get(bContext *C, PointerRNA *ptr, int rnaicon, const bool big return rnaicon; } +int UI_idcode_icon_get(const int idcode) +{ + switch (idcode) { + case ID_AC: + return ICON_ANIM_DATA; + case ID_AR: + return ICON_ARMATURE_DATA; + case ID_BR: + return ICON_BRUSH_DATA; + case ID_CA: + return ICON_CAMERA_DATA; + case ID_CU: + return ICON_CURVE_DATA; + case ID_GD: + return ICON_GREASEPENCIL; + case ID_GR: + return ICON_GROUP; + case ID_IM: + return ICON_IMAGE_DATA; + case ID_LA: + return ICON_LAMP_DATA; + case ID_LS: + return ICON_LINE_DATA; + case ID_LT: + return ICON_LATTICE_DATA; + case ID_MA: + return ICON_MATERIAL_DATA; + case ID_MB: + return ICON_META_DATA; + case ID_MC: + return ICON_CLIP; + case ID_ME: + return ICON_MESH_DATA; + case ID_MSK: + return ICON_MOD_MASK; /* TODO! this would need its own icon! */ + case ID_NT: + return ICON_NODETREE; + case ID_OB: + return ICON_OBJECT_DATA; + case ID_PAL: + return ICON_COLOR; /* TODO! this would need its own icon! */ + case ID_PC: + return ICON_CURVE_BEZCURVE; /* TODO! this would need its own icon! */ + case ID_SCE: + return ICON_SCENE_DATA; + case ID_SPK: + return ICON_SPEAKER; + case ID_SO: + return ICON_SOUND; + case ID_TE: + return ICON_TEXTURE_DATA; + case ID_TXT: + return ICON_TEXT; + case ID_VF: + return ICON_FONT_DATA; + case ID_WO: + return ICON_WORLD_DATA; + default: + return ICON_NONE; + } +} + static void icon_draw_at_size( float x, float y, int icon_id, float aspect, float alpha, enum eIconSizes size, const bool nocreate) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 6b9de26f0a4..88e728c0e37 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -290,6 +290,41 @@ enum { LIB_ID_RECALC_ALL = (LIB_ID_RECALC | LIB_ID_RECALC_DATA), }; +/* To filter ID types (filter_id) */ +/* XXX We cannot put all needed IDs inside an enum... + * We'll have to see whether we can fit all needed ones inside 32 values, + * or if we need to fallback to longlong defines :/ + */ +enum { + FILTER_ID_AC = (1 << 0), + FILTER_ID_AR = (1 << 1), + FILTER_ID_BR = (1 << 2), + FILTER_ID_CA = (1 << 3), + FILTER_ID_CU = (1 << 4), + FILTER_ID_GD = (1 << 5), + FILTER_ID_GR = (1 << 6), + FILTER_ID_IM = (1 << 7), + FILTER_ID_LA = (1 << 8), + FILTER_ID_LS = (1 << 9), + FILTER_ID_LT = (1 << 10), + FILTER_ID_MA = (1 << 11), + FILTER_ID_MB = (1 << 12), + FILTER_ID_MC = (1 << 13), + FILTER_ID_ME = (1 << 14), + FILTER_ID_MSK = (1 << 15), + FILTER_ID_NT = (1 << 16), + FILTER_ID_OB = (1 << 17), + FILTER_ID_PAL = (1 << 18), + FILTER_ID_PC = (1 << 19), + FILTER_ID_SCE = (1 << 20), + FILTER_ID_SPK = (1 << 21), + FILTER_ID_SO = (1 << 22), + FILTER_ID_TE = (1 << 23), + FILTER_ID_TXT = (1 << 24), + FILTER_ID_VF = (1 << 25), + FILTER_ID_WO = (1 << 26), +}; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 65db92ab9e6..19b5a1b8219 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -472,14 +472,14 @@ static void rna_ImagePreview_size_set(PointerRNA *ptr, const int *values, enum e BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); } - BKE_previewimg_ensure(prv_img, size); + BKE_previewimg_clear_single(prv_img, size); if (values[0] && values[1]) { prv_img->rect[size] = MEM_callocN(values[0] * values[1] * sizeof(unsigned int), "prv_rect"); - } - prv_img->w[size] = values[0]; - prv_img->h[size] = values[1]; + prv_img->w[size] = values[0]; + prv_img->h[size] = values[1]; + } prv_img->flag[size] |= (PRV_CHANGED | PRV_USER_EDITED); } @@ -600,6 +600,14 @@ static void rna_ImagePreview_icon_reload(PreviewImage *prv) } } +static PointerRNA rna_IDPreview_get(PointerRNA *ptr) +{ + ID *id = (ID *)ptr->data; + PreviewImage *prv_img = BKE_previewimg_id_ensure(id); + + return rna_pointer_inherit_refine(ptr, &RNA_ImagePreview, prv_img); +} + #else static void rna_def_ID_properties(BlenderRNA *brna) @@ -840,6 +848,11 @@ static void rna_def_ID(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Library", "Library file the datablock is linked from"); + prop = RNA_def_pointer(srna, "preview", "ImagePreview", "Preview", + "Preview image and icon of this datablock (None if not supported for this type of data)"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs(prop, "rna_IDPreview_get", NULL, NULL, NULL); + /* functions */ func = RNA_def_function(srna, "copy", "rna_ID_copy"); RNA_def_function_ui_description(func, "Create a copy of this datablock (not supported for all datablocks)"); diff --git a/source/blender/python/intern/bpy_app.c b/source/blender/python/intern/bpy_app.c index 1cf0c44fd87..90d97cad880 100644 --- a/source/blender/python/intern/bpy_app.c +++ b/source/blender/python/intern/bpy_app.c @@ -50,6 +50,10 @@ #include "BKE_blender.h" #include "BKE_global.h" +#include "DNA_ID.h" + +#include "UI_interface_icons.h" + #include "../generic/py_capi_utils.h" #include "../generic/python_utildefines.h" @@ -298,6 +302,14 @@ static PyObject *bpy_app_driver_dict_get(PyObject *UNUSED(self), void *UNUSED(cl return Py_INCREF_RET(bpy_pydriver_Dict); } +PyDoc_STRVAR(bpy_app_preview_render_size_doc, +"Reference size for icon/preview renders (read-only)" +); +static PyObject *bpy_app_preview_render_size_get(PyObject *UNUSED(self), void *closure) +{ + return PyLong_FromLong((long)UI_preview_render_size(GET_INT_FROM_POINTER(closure))); +} + static PyObject *bpy_app_autoexec_fail_message_get(PyObject *UNUSED(self), void *UNUSED(closure)) { return PyC_UnicodeFromByte(G.autoexec_fail); @@ -322,6 +334,9 @@ static PyGetSetDef bpy_app_getsets[] = { {(char *)"tempdir", bpy_app_tempdir_get, NULL, (char *)bpy_app_tempdir_doc, NULL}, {(char *)"driver_namespace", bpy_app_driver_dict_get, NULL, (char *)bpy_app_driver_dict_doc, NULL}, + {(char *)"render_icon_size", bpy_app_preview_render_size_get, NULL, (char *)bpy_app_preview_render_size_doc, (void *)ICON_SIZE_ICON}, + {(char *)"render_preview_size", bpy_app_preview_render_size_get, NULL, (char *)bpy_app_preview_render_size_doc, (void *)ICON_SIZE_PREVIEW}, + /* security */ {(char *)"autoexec_fail", bpy_app_global_flag_get, NULL, NULL, (void *)G_SCRIPT_AUTOEXEC_FAIL}, {(char *)"autoexec_fail_quiet", bpy_app_global_flag_get, NULL, NULL, (void *)G_SCRIPT_AUTOEXEC_FAIL_QUIET}, diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 37d434faa6e..1455b7000e9 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -74,6 +74,7 @@ #include "BKE_brush.h" #include "BKE_context.h" #include "BKE_depsgraph.h" +#include "BKE_icons.h" #include "BKE_idprop.h" #include "BKE_image.h" #include "BKE_library.h" @@ -4897,6 +4898,72 @@ static void WM_OT_previews_ensure(wmOperatorType *ot) ot->exec = previews_ensure_exec; } +/* *************************** Datablocks previews clear ************* */ + +/* Only types supporting previews currently. */ +static EnumPropertyItem preview_id_type_items[] = { + {FILTER_ID_SCE, "SCENE", 0, "Scenes", ""}, + {FILTER_ID_GR, "GROUP", 0, "Groups", ""}, + {FILTER_ID_OB, "OBJECT", 0, "Objects", ""}, + {FILTER_ID_MA, "MATERIAL", 0, "Materials", ""}, + {FILTER_ID_LA, "LAMP", 0, "Lamps", ""}, + {FILTER_ID_WO, "WORLD", 0, "Worlds", ""}, + {FILTER_ID_TE, "TEXTURE", 0, "Textures", ""}, + {FILTER_ID_IM, "IMAGE", 0, "Images", ""}, +#if 0 /* XXX TODO */ + {FILTER_ID_BR, "BRUSH", 0, "Brushes", ""}, +#endif + {0, NULL, 0, NULL, NULL} +}; + +static int previews_clear_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + ListBase *lb[] = {&bmain->object, &bmain->group, + &bmain->mat, &bmain->world, &bmain->lamp, &bmain->tex, &bmain->image, NULL}; + int i; + + const int id_filters = RNA_enum_get(op->ptr, "id_type"); + + for (i = 0; lb[i]; i++) { + ID *id = lb[i]->first; + + if (!id) continue; + +// printf("%s: %d, %d, %d -> %d\n", id->name, GS(id->name), BKE_idcode_to_idfilter(GS(id->name)), +// id_filters, BKE_idcode_to_idfilter(GS(id->name)) & id_filters); + + if (!id || !(BKE_idcode_to_idfilter(GS(id->name)) & id_filters)) { + continue; + } + + for (; id; id = id->next) { + PreviewImage *prv_img = BKE_previewimg_id_ensure(id); + + BKE_previewimg_clear(prv_img); + } + } + + return OPERATOR_FINISHED; +} + +static void WM_OT_previews_clear(wmOperatorType *ot) +{ + ot->name = "Clear DataBlock Previews"; + ot->idname = "WM_OT_previews_clear"; + ot->description = "Clear datablock previews (only for some types like objects, materials, textures, etc.)"; + + ot->exec = previews_clear_exec; + ot->invoke = WM_menu_invoke; + + ot->prop = RNA_def_enum_flag(ot->srna, "id_type", preview_id_type_items, + FILTER_ID_SCE | FILTER_ID_OB | FILTER_ID_GR | + FILTER_ID_MA | FILTER_ID_LA | FILTER_ID_WO | FILTER_ID_TE | FILTER_ID_IM, + "DataBlock Type", "Which datablock previews to clear"); +} + +/* *************************** Doc from UI ************* */ + static int doc_view_manual_ui_context_exec(bContext *C, wmOperator *UNUSED(op)) { PointerRNA ptr_props; @@ -5025,6 +5092,7 @@ void wm_operatortype_init(void) WM_operatortype_append(WM_OT_console_toggle); #endif WM_operatortype_append(WM_OT_previews_ensure); + WM_operatortype_append(WM_OT_previews_clear); WM_operatortype_append(WM_OT_doc_view_manual_ui_context); } -- cgit v1.2.3