UI/Assets: Support generating object preview images

Object previews are really helpful for visual data-block selection, like asset
browsing. Having them be generative should also be quite handy and should work
well enough in many, if not most cases.

What this does is simple:
* Place the object (actually a deep copy of it, for thread safety) in a virtual
  .blend into an empty scene/view-layer.
* Add a camera, point it towards the front of the object, assuming that means
  pointing towards its +Y axis.
* Use "Camera Fit Frame to Selected" logic to put the object into frame.
* Create a threaded off-screen render.

Of course, such an automatic preview will not work in all situations. E.g. it
currently does a bad job capturing a single plane. We could add options for
more advanced automatic previews, but probably custom previews is more
important, which I committed already (812ea91842).

Part of the first Asset Browser milestone. Check the #asset_browser_milestone_1
project milestone on developer.blender.org.

Reviewed as part of https://developer.blender.org/D9719.
Reviewed by: Bastien Montagne, Brecht Van Lommel
This commit is contained in:
Julian Eisel 2020-12-14 12:48:16 +01:00
parent 732d0b458b
commit 4b0396695c
Notes: blender-bot 2023-02-14 09:03:55 +01:00
Referenced by issue #83776, Crashes with add-on's icon preview in menus
5 changed files with 254 additions and 76 deletions

View File

@ -2208,7 +2208,9 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f
BLI_listbase_clear(&psysn->childcachebufs);
if (flag & LIB_ID_CREATE_NO_MAIN) {
BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0);
/* XXX Disabled, fails when evaluating depsgraph after copying ID with no main for preview
* creation. */
// BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0);
psysn->flag |= PSYS_SHARED_CACHES;
BLI_assert(psysn->pointcache != NULL);
}

View File

@ -141,6 +141,11 @@ void ED_view3d_to_object(const struct Depsgraph *depsgraph,
const float quat[4],
const float dist);
bool ED_view3d_camera_to_view_selected(struct Main *bmain,
struct Depsgraph *depsgraph,
const struct Scene *scene,
struct Object *camera_ob);
void ED_view3d_lastview_store(struct RegionView3D *rv3d);
/* Depth buffer */

View File

@ -68,6 +68,7 @@
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_scene.h"
#include "BKE_texture.h"
#include "BKE_world.h"
@ -94,12 +95,16 @@
#include "ED_datafiles.h"
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "ED_view3d_offscreen.h"
#ifndef NDEBUG
/* Used for database init assert(). */
# include "BLI_threads.h"
#endif
static void icon_copy_rect(ImBuf *ibuf, uint w, uint h, uint *rect);
ImBuf *get_brush_icon(Brush *brush)
{
static const int flags = IB_rect | IB_multilayer | IB_metadata;
@ -336,7 +341,7 @@ static World *preview_get_localized_world(ShaderPreview *sp, World *world)
return sp->worldcopy;
}
static ID *duplicate_ids(ID *id)
static ID *duplicate_ids(ID *id, const bool allow_failure)
{
if (id == NULL) {
/* Non-ID preview render. */
@ -344,6 +349,7 @@ static ID *duplicate_ids(ID *id)
}
switch (GS(id->name)) {
case ID_OB:
case ID_MA:
case ID_TE:
case ID_LA:
@ -357,7 +363,9 @@ static ID *duplicate_ids(ID *id)
case ID_SCR:
return NULL;
default:
BLI_assert(!"ID type preview not supported.");
if (!allow_failure) {
BLI_assert(!"ID type preview not supported.");
}
return NULL;
}
}
@ -698,6 +706,132 @@ void ED_preview_draw(const bContext *C, void *idp, void *parentp, void *slotp, r
}
}
/* **************************** Object preview ****************** */
struct ObjectPreviewData {
/* The main for the preview, not of the current file. */
Main *pr_main;
/* Copy of the object to create the preview for. The copy is for thread safety (and to insert it
* into an own main). */
Object *object;
int sizex;
int sizey;
};
static Object *object_preview_camera_create(
Main *preview_main, ViewLayer *view_layer, Object *preview_object, int sizex, int sizey)
{
Object *camera = BKE_object_add(preview_main, view_layer, OB_CAMERA, "Preview Camera");
float rotmat[3][3];
float dummyscale[3];
mat4_to_loc_rot_size(camera->loc, rotmat, dummyscale, preview_object->obmat);
/* Camera is Y up, so needs additional 90deg rotation around X to match object's Z up. */
float drotmat[3][3];
axis_angle_to_mat3_single(drotmat, 'X', M_PI_2);
mul_m3_m3_post(rotmat, drotmat);
camera->rotmode = ROT_MODE_QUAT;
mat3_to_quat(camera->quat, rotmat);
/* shader_preview_render() does this too. */
if (sizex > sizey) {
((Camera *)camera->data)->lens *= (float)sizey / (float)sizex;
}
return camera;
}
static Scene *object_preview_scene_create(const struct ObjectPreviewData *preview_data,
Depsgraph **r_depsgraph)
{
Scene *scene = BKE_scene_add(preview_data->pr_main, "Object preview scene");
ViewLayer *view_layer = scene->view_layers.first;
Depsgraph *depsgraph = DEG_graph_new(
preview_data->pr_main, scene, view_layer, DAG_EVAL_VIEWPORT);
BLI_assert(preview_data->object != NULL);
BLI_addtail(&preview_data->pr_main->objects, preview_data->object);
BKE_collection_object_add(preview_data->pr_main, scene->master_collection, preview_data->object);
Object *camera_object = object_preview_camera_create(preview_data->pr_main,
view_layer,
preview_data->object,
preview_data->sizex,
preview_data->sizey);
scene->camera = camera_object;
scene->r.xsch = preview_data->sizex;
scene->r.ysch = preview_data->sizey;
scene->r.size = 100;
Base *preview_base = BKE_view_layer_base_find(view_layer, preview_data->object);
/* For 'view selected' below. */
preview_base->flag |= BASE_SELECTED;
DEG_graph_build_from_view_layer(depsgraph);
DEG_evaluate_on_refresh(depsgraph);
ED_view3d_camera_to_view_selected(preview_data->pr_main, depsgraph, scene, camera_object);
BKE_scene_graph_update_tagged(depsgraph, preview_data->pr_main);
*r_depsgraph = depsgraph;
return scene;
}
static void object_preview_render(IconPreview *preview, IconPreviewSize *preview_sized)
{
Main *preview_main = BKE_main_new();
const float pixelsize_old = U.pixelsize;
char err_out[256] = "unknown";
BLI_assert(preview->id_copy && (preview->id_copy != preview->id));
struct ObjectPreviewData preview_data = {
.pr_main = preview_main,
/* Act on a copy. */
.object = (Object *)preview->id_copy,
.sizex = preview_sized->sizex,
.sizey = preview_sized->sizey,
};
Depsgraph *depsgraph;
Scene *scene = object_preview_scene_create(&preview_data, &depsgraph);
/* Ownership is now ours. */
preview->id_copy = NULL;
U.pixelsize = 2.0f;
ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple(
depsgraph,
DEG_get_evaluated_scene(depsgraph),
NULL,
OB_SOLID,
DEG_get_evaluated_object(depsgraph, scene->camera),
preview_sized->sizex,
preview_sized->sizey,
IB_rect,
V3D_OFSDRAW_NONE,
R_ALPHAPREMUL,
NULL,
NULL,
err_out);
/* TODO color-management? */
U.pixelsize = pixelsize_old;
if (ibuf) {
icon_copy_rect(ibuf, preview_sized->sizex, preview_sized->sizey, preview_sized->rect);
IMB_freeImBuf(ibuf);
}
DEG_graph_free(depsgraph);
BKE_main_free(preview_main);
}
/* **************************** new shader preview system ****************** */
/* inside thread, called by renderer, sets job update value */
@ -1188,27 +1322,54 @@ static void common_preview_startjob(void *customdata,
}
}
/* exported functions */
static void icon_preview_add_size(IconPreview *ip, uint *rect, int sizex, int sizey)
/**
* Some ID types already have their own, more focused rendering (only objects right now). This is
* for the other ones, which all share #ShaderPreview and some functions.
*/
static void other_id_types_preview_render(IconPreview *ip,
IconPreviewSize *cur_size,
const bool is_deferred,
short *stop,
short *do_update,
float *progress)
{
IconPreviewSize *cur_size = ip->sizes.first, *new_size;
ShaderPreview *sp = MEM_callocN(sizeof(ShaderPreview), "Icon ShaderPreview");
const bool is_render = !is_deferred;
while (cur_size) {
if (cur_size->sizex == sizex && cur_size->sizey == sizey) {
/* requested size is already in list, no need to add it again */
return;
/* These types don't use the ShaderPreview mess, they have their own types and functions. */
BLI_assert(!ELEM(GS(ip->id->name), ID_OB));
/* construct shader preview from image size and previewcustomdata */
sp->scene = ip->scene;
sp->owner = ip->owner;
sp->sizex = cur_size->sizex;
sp->sizey = cur_size->sizey;
sp->pr_method = is_render ? PR_ICON_RENDER : PR_ICON_DEFERRED;
sp->pr_rect = cur_size->rect;
sp->id = ip->id;
sp->id_copy = ip->id_copy;
sp->bmain = ip->bmain;
sp->own_id_copy = false;
Material *ma = NULL;
if (is_render) {
BLI_assert(ip->id);
/* grease pencil use its own preview file */
if (GS(ip->id->name) == ID_MA) {
ma = (Material *)ip->id;
}
cur_size = cur_size->next;
if ((ma == NULL) || (ma->gp_style == NULL)) {
sp->pr_main = G_pr_main;
}
else {
sp->pr_main = G_pr_main_grease_pencil;
}
}
new_size = MEM_callocN(sizeof(IconPreviewSize), "IconPreviewSize");
new_size->sizex = sizex;
new_size->sizey = sizey;
new_size->rect = rect;
BLI_addtail(&ip->sizes, new_size);
common_preview_startjob(sp, stop, do_update, progress);
shader_preview_free(sp);
}
static void icon_preview_startjob_all_sizes(void *customdata,
@ -1235,41 +1396,36 @@ static void icon_preview_startjob_all_sizes(void *customdata,
continue;
}
ShaderPreview *sp = MEM_callocN(sizeof(ShaderPreview), "Icon ShaderPreview");
const bool is_render = !(prv->tag & PRV_TAG_DEFFERED);
if (ELEM(GS(ip->id->name), ID_OB)) {
/* Much simpler than the ShaderPreview mess used for other ID types. */
object_preview_render(ip, cur_size);
}
else {
other_id_types_preview_render(
ip, cur_size, (prv->tag & PRV_TAG_DEFFERED), stop, do_update, progress);
}
}
}
/* construct shader preview from image size and previewcustomdata */
sp->scene = ip->scene;
sp->owner = ip->owner;
sp->sizex = cur_size->sizex;
sp->sizey = cur_size->sizey;
sp->pr_method = is_render ? PR_ICON_RENDER : PR_ICON_DEFERRED;
sp->pr_rect = cur_size->rect;
sp->id = ip->id;
sp->id_copy = ip->id_copy;
sp->bmain = ip->bmain;
sp->own_id_copy = false;
Material *ma = NULL;
static void icon_preview_add_size(IconPreview *ip, uint *rect, int sizex, int sizey)
{
IconPreviewSize *cur_size = ip->sizes.first, *new_size;
if (is_render) {
BLI_assert(ip->id);
/* grease pencil use its own preview file */
if (GS(ip->id->name) == ID_MA) {
ma = (Material *)ip->id;
}
if ((ma == NULL) || (ma->gp_style == NULL)) {
sp->pr_main = G_pr_main;
}
else {
sp->pr_main = G_pr_main_grease_pencil;
}
while (cur_size) {
if (cur_size->sizex == sizex && cur_size->sizey == sizey) {
/* requested size is already in list, no need to add it again */
return;
}
common_preview_startjob(sp, stop, do_update, progress);
shader_preview_free(sp);
cur_size = cur_size->next;
}
new_size = MEM_callocN(sizeof(IconPreviewSize), "IconPreviewSize");
new_size->sizex = sizex;
new_size->sizey = sizey;
new_size->rect = rect;
BLI_addtail(&ip->sizes, new_size);
}
static void icon_preview_endjob(void *customdata)
@ -1333,7 +1489,9 @@ void ED_preview_icon_render(Main *bmain, Scene *scene, ID *id, uint *rect, int s
ip.scene = scene;
ip.owner = BKE_previewimg_id_ensure(id);
ip.id = id;
ip.id_copy = duplicate_ids(id);
/* Control isn't given back to the caller until the preview is done. So we don't need to copy
* the ID to avoid thread races. */
ip.id_copy = duplicate_ids(id, true);
icon_preview_add_size(&ip, rect, sizex, sizey);
@ -1376,7 +1534,7 @@ void ED_preview_icon_job(
ip->scene = CTX_data_scene(C);
ip->owner = owner;
ip->id = id;
ip->id_copy = duplicate_ids(id);
ip->id_copy = duplicate_ids(id, false);
icon_preview_add_size(ip, rect, sizex, sizey);
@ -1445,7 +1603,7 @@ void ED_preview_shader_job(const bContext *C,
sp->sizey = sizey;
sp->pr_method = method;
sp->id = id;
sp->id_copy = duplicate_ids(id);
sp->id_copy = duplicate_ids(id, false);
sp->own_id_copy = true;
sp->parent = parent;
sp->slot = slot;

View File

@ -1620,6 +1620,41 @@ void ED_view3d_to_object(const Depsgraph *depsgraph,
BKE_object_apply_mat4_ex(ob, mat, ob_eval->parent, ob_eval->parentinv, true);
}
bool ED_view3d_camera_to_view_selected(struct Main *bmain,
Depsgraph *depsgraph,
const Scene *scene,
Object *camera_ob)
{
Object *camera_ob_eval = DEG_get_evaluated_object(depsgraph, camera_ob);
float co[3]; /* the new location to apply */
float scale; /* only for ortho cameras */
if (BKE_camera_view_frame_fit_to_scene(depsgraph, scene, camera_ob_eval, co, &scale)) {
ObjectTfmProtectedChannels obtfm;
float obmat_new[4][4];
if ((camera_ob_eval->type == OB_CAMERA) &&
(((Camera *)camera_ob_eval->data)->type == CAM_ORTHO)) {
((Camera *)camera_ob->data)->ortho_scale = scale;
}
copy_m4_m4(obmat_new, camera_ob_eval->obmat);
copy_v3_v3(obmat_new[3], co);
/* only touch location */
BKE_object_tfm_protected_backup(camera_ob, &obtfm);
BKE_object_apply_mat4(camera_ob, obmat_new, true, true);
BKE_object_tfm_protected_restore(camera_ob, &obtfm, OB_LOCK_SCALE | OB_LOCK_ROT4D);
/* notifiers */
DEG_id_tag_update_ex(bmain, &camera_ob->id, ID_RECALC_TRANSFORM);
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -535,40 +535,18 @@ void VIEW3D_OT_camera_to_view(wmOperatorType *ot)
* meant to take into account vertex/bone selection for eg. */
static int view3d_camera_to_view_selected_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C); /* can be NULL */
Object *camera_ob = v3d ? v3d->camera : scene->camera;
Object *camera_ob_eval = DEG_get_evaluated_object(depsgraph, camera_ob);
float r_co[3]; /* the new location to apply */
float r_scale; /* only for ortho cameras */
if (camera_ob_eval == NULL) {
if (camera_ob == NULL) {
BKE_report(op->reports, RPT_ERROR, "No active camera");
return OPERATOR_CANCELLED;
}
/* this function does all the important stuff */
if (BKE_camera_view_frame_fit_to_scene(depsgraph, scene, camera_ob_eval, r_co, &r_scale)) {
ObjectTfmProtectedChannels obtfm;
float obmat_new[4][4];
if ((camera_ob_eval->type == OB_CAMERA) &&
(((Camera *)camera_ob_eval->data)->type == CAM_ORTHO)) {
((Camera *)camera_ob->data)->ortho_scale = r_scale;
}
copy_m4_m4(obmat_new, camera_ob_eval->obmat);
copy_v3_v3(obmat_new[3], r_co);
/* only touch location */
BKE_object_tfm_protected_backup(camera_ob, &obtfm);
BKE_object_apply_mat4(camera_ob, obmat_new, true, true);
BKE_object_tfm_protected_restore(camera_ob, &obtfm, OB_LOCK_SCALE | OB_LOCK_ROT4D);
/* notifiers */
DEG_id_tag_update(&camera_ob->id, ID_RECALC_TRANSFORM);
if (ED_view3d_camera_to_view_selected(bmain, depsgraph, scene, camera_ob)) {
WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, camera_ob);
return OPERATOR_FINISHED;
}