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:
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
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue