Cleanup: Use utility function to find groups in node tree #104465

Merged
Hans Goudey merged 23 commits from mod_moder/blender:is_contains into main 2023-02-10 17:30:57 +01:00
36 changed files with 963 additions and 196 deletions
Showing only changes of commit 5651587f11 - Show all commits

View File

@ -1,13 +1,15 @@
name: Bug Report
about: File a bug report
labels:
- bug
- "type::Report"
- "status::Needs Triage"
- "priority::Normal"
body:
- type: markdown
attributes:
value: |
### Instructions
First time reporting? See [tips](https://wiki.blender.org/wiki/Process/Bug_Reports) and [walkthrough video](https://www.youtube.com/watch?v=JTD0OJq_rF4).
First time reporting? See [tips](https://wiki.blender.org/wiki/Process/Bug_Reports).
* Use **Help > Report a Bug** in Blender to fill system information and exact Blender version.
* Test [daily builds](https://builder.blender.org/) to verify if the issue is already fixed.
@ -19,6 +21,7 @@ body:
id: body
attributes:
label: "Description"
hide_label: true
value: |
**System Information**
Operating system:

View File

@ -1,9 +1,10 @@
name: Design
about: Create a design task (for developers only)
labels:
- design
- "type::Design"
body:
- type: textarea
id: body
attributes:
label: "Description"
hide_label: true

View File

@ -1,9 +1,10 @@
name: To Do
about: Create a to do task (for developers only)
labels:
- todo
- "type::To Do"
body:
- type: textarea
id: body
attributes:
label: "Description"
hide_label: true

View File

@ -14,6 +14,7 @@ body:
id: body
attributes:
label: "Description"
hide_label: true
value: |
Description of the problem that is addressed in the patch.

@ -1 +1 @@
Subproject commit 39cadb50aa31005bb64461f98b36a1a0f88ebc78
Subproject commit 9958ddb879934718cc2b379b556f0bc3b861bee5

View File

@ -38,7 +38,7 @@ def draw_node_group_add_menu(context, layout):
groups = [
group for group in context.blend_data.node_groups
if (group.bl_idname == node_tree.bl_idname and
not node_tree.contains_in(group) and
not group.contains_group(node_tree) and
not group.name.startswith('.'))
]
if groups:

View File

@ -80,7 +80,7 @@ def node_group_items(context):
if group.bl_idname != ntree.bl_idname:
continue
# filter out recursive groups
if ntree.contains_in(group):
if group.contains_group(ntree):
continue
# filter out hidden nodetrees
if group.name.startswith('.'):

View File

@ -2250,6 +2250,19 @@ static void make_bevel_list_3D_minimum_twist(BevList *bl)
BevPoint *bevp2, *bevp1, *bevp0; /* Standard for all make_bevel_list_3D_* functions. */
int nr;
float q[4];
const bool is_cyclic = bl->poly != -1;
/* NOTE(@campbellbarton): For non-cyclic curves only initialize the first direction
* (via `vec_to_quat`), necessary for symmetry, see T71137.
* Otherwise initialize the first and second points before propagating rotation forward.
* This is historical as changing this can cause significantly different output.
* Specifically: `deform_modifiers` test: (`CurveMeshDeform`).
*
* While it would seem correct to only use the first point for non-cyclic curves as well
* the end-points direction is initialized from the input handles (instead of the directions
* between points), there is often a bigger difference in the first and second directions
* than you'd otherwise expect. So using only the first direction breaks compatibility
* enough it's best to leave it as-is. */
const int nr_init = bl->nr - (is_cyclic ? 1 : 2);
bevel_list_calc_bisect(bl);
@ -2260,7 +2273,8 @@ static void make_bevel_list_3D_minimum_twist(BevList *bl)
nr = bl->nr;
while (nr--) {
if (nr + 3 > bl->nr) { /* first time and second time, otherwise first point adjusts last */
if (nr >= nr_init) {
/* Initialize the rotation, otherwise propagate the previous rotation forward. */
vec_to_quat(bevp1->quat, bevp1->dir, 5, 1);
}
else {

View File

@ -3803,8 +3803,6 @@ void BKE_node_instance_hash_remove_untagged(bNodeInstanceHash *hash,
void ntreeUpdateAllNew(Main *main)
{
Vector<bNodeTree *> new_ntrees;
/* Update all new node trees on file read or append, to add/remove sockets
* in groups nodes if the group changed, and handle any update flags that
* might have been set in file reading or versioning. */

View File

@ -103,8 +103,8 @@ void BlendfileLoadingBaseTest::TearDownTestCase()
void BlendfileLoadingBaseTest::TearDown()
{
BKE_mball_cubeTable_free();
depsgraph_free();
blendfile_free();
depsgraph_free();
testing::Test::TearDown();
}

View File

@ -516,6 +516,7 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD
&texture_buffer,
transform_mode,
IMB_FILTER_NEAREST,
1,
uv_to_texel.ptr(),
crop_rect_ptr);
}

View File

@ -12,7 +12,7 @@ void workbench_material_data_get(int handle,
vec4 data = materials_data[uint(handle) & 0xFFFu];
color = data.rgb;
if (materialIndex == 0) {
color_interp = vertex_color;
color = vertex_color;
}
#else

View File

@ -10,6 +10,7 @@
#include "BLI_compiler_attrs.h"
#include "DNA_object_enums.h"
#include "DNA_userdef_enums.h"
#include "DNA_windowmanager_types.h"
#ifdef __cplusplus
extern "C" {
@ -558,15 +559,19 @@ bool ED_object_modifier_remove(struct ReportList *reports,
struct ModifierData *md);
void ED_object_modifier_clear(struct Main *bmain, struct Scene *scene, struct Object *ob);
bool ED_object_modifier_move_down(struct ReportList *reports,
eReportType error_type,
struct Object *ob,
struct ModifierData *md);
bool ED_object_modifier_move_up(struct ReportList *reports,
eReportType error_type,
struct Object *ob,
struct ModifierData *md);
bool ED_object_modifier_move_to_index(struct ReportList *reports,
eReportType error_type,
struct Object *ob,
struct ModifierData *md,
int index);
int index,
bool allow_partial);
bool ED_object_modifier_convert_psys_to_mesh(struct ReportList *reports,
struct Main *bmain,

View File

@ -63,10 +63,8 @@ BMBackup EDBM_redo_state_store(BMEditMesh *em)
void EDBM_redo_state_restore(BMBackup *backup, BMEditMesh *em, bool recalc_looptri)
{
BMesh *tmpbm;
BM_mesh_data_free(em->bm);
tmpbm = BM_mesh_copy(backup->bmcopy);
BMesh *tmpbm = BM_mesh_copy(backup->bmcopy);
*em->bm = *tmpbm;
MEM_freeN(tmpbm);
tmpbm = NULL;
@ -208,11 +206,9 @@ bool EDBM_op_call_and_selectf(BMEditMesh *em,
const char *fmt,
...)
{
BMOpSlot *slot_select_out;
BMesh *bm = em->bm;
BMOperator bmop;
va_list list;
char hflag;
va_start(list, fmt);
@ -224,8 +220,8 @@ bool EDBM_op_call_and_selectf(BMEditMesh *em,
BMO_op_exec(bm, &bmop);
slot_select_out = BMO_slot_get(bmop.slots_out, select_slot_out);
hflag = slot_select_out->slot_subtype.elem & BM_ALL_NOLOOP;
BMOpSlot *slot_select_out = BMO_slot_get(bmop.slots_out, select_slot_out);
char hflag = slot_select_out->slot_subtype.elem & BM_ALL_NOLOOP;
BLI_assert(hflag != 0);
if (select_extend == false) {
@ -269,14 +265,12 @@ bool EDBM_op_call_silentf(BMEditMesh *em, const char *fmt, ...)
void EDBM_mesh_make(Object *ob, const int select_mode, const bool add_key_index)
{
Mesh *me = ob->data;
BMesh *bm;
bm = BKE_mesh_to_bmesh(me,
ob,
add_key_index,
&((struct BMeshCreateParams){
.use_toolflags = true,
}));
BMesh *bm = BKE_mesh_to_bmesh(me,
ob,
add_key_index,
&((struct BMeshCreateParams){
.use_toolflags = true,
}));
if (me->edit_mesh) {
/* this happens when switching shape keys */
@ -456,21 +450,15 @@ UvVertMap *BM_uv_vert_map_create(BMesh *bm, const bool use_select, const bool us
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
/* vars from original func */
UvVertMap *vmap;
UvMapVert *buf;
const float(*luv)[2];
uint a;
int totverts, i, totuv, totfaces;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
bool *winding = NULL;
BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, BM_DEFAULT_NGON_STACK_SIZE);
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
totfaces = bm->totface;
totverts = bm->totvert;
totuv = 0;
const int totfaces = bm->totface;
const int totverts = bm->totvert;
int totuv = 0;
/* generate UvMapVert array */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
@ -482,13 +470,15 @@ UvVertMap *BM_uv_vert_map_create(BMesh *bm, const bool use_select, const bool us
if (totuv == 0) {
return NULL;
}
vmap = (UvVertMap *)MEM_callocN(sizeof(*vmap), "UvVertMap");
UvVertMap *vmap = (UvVertMap *)MEM_callocN(sizeof(*vmap), "UvVertMap");
if (!vmap) {
return NULL;
}
vmap->vert = (UvMapVert **)MEM_callocN(sizeof(*vmap->vert) * totverts, "UvMapVert_pt");
buf = vmap->buf = (UvMapVert *)MEM_callocN(sizeof(*vmap->buf) * totuv, "UvMapVert");
UvMapVert *buf = vmap->buf = (UvMapVert *)MEM_callocN(sizeof(*vmap->buf) * totuv, "UvMapVert");
bool *winding = NULL;
if (use_winding) {
winding = MEM_callocN(sizeof(*winding) * totfaces, "winding");
}
@ -506,6 +496,7 @@ UvVertMap *BM_uv_vert_map_create(BMesh *bm, const bool use_select, const bool us
tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len);
}
int i;
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
buf->loop_of_poly_index = i;
buf->poly_index = a;
@ -516,7 +507,7 @@ UvVertMap *BM_uv_vert_map_create(BMesh *bm, const bool use_select, const bool us
buf++;
if (use_winding) {
luv = BM_ELEM_CD_GET_FLOAT2_P(l, cd_loop_uv_offset);
const float(*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(l, cd_loop_uv_offset);
copy_v2_v2(tf_uv[i], *luv);
}
}
@ -1263,14 +1254,10 @@ UvElement *BM_uv_element_get_head(UvElementMap *element_map, UvElement *child)
BMFace *EDBM_uv_active_face_get(BMEditMesh *em, const bool sloppy, const bool selected)
{
BMFace *efa = NULL;
if (!EDBM_uv_check(em)) {
return NULL;
}
efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
BMFace *efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
if (efa) {
return efa;
}
@ -1765,10 +1752,10 @@ void EDBM_update_extern(struct Mesh *me, const bool do_tessellation, const bool
bool EDBM_view3d_poll(bContext *C)
{
if (ED_operator_editmesh(C) && ED_operator_view3d_active(C)) {
return 1;
return true;
}
return 0;
return false;
}
/** \} */
@ -1779,19 +1766,16 @@ bool EDBM_view3d_poll(bContext *C)
BMElem *EDBM_elem_from_selectmode(BMEditMesh *em, BMVert *eve, BMEdge *eed, BMFace *efa)
{
BMElem *ele = NULL;
if ((em->selectmode & SCE_SELECT_VERTEX) && eve) {
ele = (BMElem *)eve;
return (BMElem *)eve;
}
else if ((em->selectmode & SCE_SELECT_EDGE) && eed) {
ele = (BMElem *)eed;
if ((em->selectmode & SCE_SELECT_EDGE) && eed) {
return (BMElem *)eed;
}
else if ((em->selectmode & SCE_SELECT_FACE) && efa) {
ele = (BMElem *)efa;
if ((em->selectmode & SCE_SELECT_FACE) && efa) {
return (BMElem *)efa;
}
return ele;
return NULL;
}
int EDBM_elem_to_index_any(BMEditMesh *em, BMElem *ele)

View File

@ -415,83 +415,139 @@ void ED_object_modifier_clear(Main *bmain, Scene *scene, Object *ob)
DEG_relations_tag_update(bmain);
}
bool ED_object_modifier_move_up(ReportList *reports, Object *ob, ModifierData *md)
static bool object_modifier_check_move_before(ReportList *reports,
eReportType error_type,
ModifierData *md,
ModifierData *md_prev)
{
if (md->prev) {
if (md_prev) {
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (mti->type != eModifierTypeType_OnlyDeform) {
const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->prev->type);
const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md_prev->type);
if (nmti->flags & eModifierTypeFlag_RequiresOriginalData) {
BKE_report(reports, RPT_WARNING, "Cannot move above a modifier requiring original data");
BKE_report(reports, error_type, "Cannot move above a modifier requiring original data");
return false;
}
}
BLI_listbase_swaplinks(&ob->modifiers, md, md->prev);
}
else {
BKE_report(reports, RPT_WARNING, "Cannot move modifier beyond the start of the list");
BKE_report(reports, error_type, "Cannot move modifier beyond the start of the list");
return false;
}
return true;
}
bool ED_object_modifier_move_down(ReportList *reports, Object *ob, ModifierData *md)
bool ED_object_modifier_move_up(ReportList *reports,
eReportType error_type,
Object *ob,
ModifierData *md)
{
if (md->next) {
if (object_modifier_check_move_before(reports, error_type, md, md->prev)) {
BLI_listbase_swaplinks(&ob->modifiers, md, md->prev);
return true;
}
return false;
}
static bool object_modifier_check_move_after(ReportList *reports,
eReportType error_type,
ModifierData *md,
ModifierData *md_next)
{
if (md_next) {
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->next->type);
const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md_next->type);
if (nmti->type != eModifierTypeType_OnlyDeform) {
BKE_report(reports, RPT_WARNING, "Cannot move beyond a non-deforming modifier");
BKE_report(reports, error_type, "Cannot move beyond a non-deforming modifier");
return false;
}
}
BLI_listbase_swaplinks(&ob->modifiers, md, md->next);
}
else {
BKE_report(reports, RPT_WARNING, "Cannot move modifier beyond the end of the list");
BKE_report(reports, error_type, "Cannot move modifier beyond the end of the list");
return false;
}
return true;
}
bool ED_object_modifier_move_down(ReportList *reports,
eReportType error_type,
Object *ob,
ModifierData *md)
{
if (object_modifier_check_move_after(reports, error_type, md, md->next)) {
BLI_listbase_swaplinks(&ob->modifiers, md, md->next);
return true;
}
return false;
}
bool ED_object_modifier_move_to_index(ReportList *reports,
eReportType error_type,
Object *ob,
ModifierData *md,
const int index)
const int index,
bool allow_partial)
{
BLI_assert(md != nullptr);
BLI_assert(index >= 0);
if (index >= BLI_listbase_count(&ob->modifiers)) {
BKE_report(reports, RPT_WARNING, "Cannot move modifier beyond the end of the stack");
if (index < 0 || index >= BLI_listbase_count(&ob->modifiers)) {
BKE_report(reports, error_type, "Cannot move modifier beyond the end of the stack");
return false;
}
int md_index = BLI_findindex(&ob->modifiers, md);
BLI_assert(md_index != -1);
if (md_index < index) {
/* Move modifier down in list. */
for (; md_index < index; md_index++) {
if (!ED_object_modifier_move_down(reports, ob, md)) {
ModifierData *md_target = md;
for (; md_index < index; md_index++, md_target = md_target->next) {
if (!object_modifier_check_move_after(reports, error_type, md, md_target->next)) {
if (!allow_partial || md == md_target) {
return false;
}
break;
}
}
BLI_assert(md != md_target && md_target);
BLI_remlink(&ob->modifiers, md);
BLI_insertlinkafter(&ob->modifiers, md_target, md);
}
else if (md_index > index) {
/* Move modifier up in list. */
ModifierData *md_target = md;
for (; md_index > index; md_index--, md_target = md_target->prev) {
if (!object_modifier_check_move_before(reports, error_type, md, md_target->prev)) {
if (!allow_partial || md == md_target) {
return false;
}
break;
}
}
BLI_assert(md != md_target && md_target);
BLI_remlink(&ob->modifiers, md);
BLI_insertlinkbefore(&ob->modifiers, md_target, md);
}
else {
/* Move modifier up in list. */
for (; md_index > index; md_index--) {
if (!ED_object_modifier_move_up(reports, ob, md)) {
break;
}
}
return true;
}
/* NOTE: Dependency graph only uses modifier nodes for visibility updates, and exact order of
@ -1518,7 +1574,7 @@ static int modifier_move_up_exec(bContext *C, wmOperator *op)
Object *ob = ED_object_active_context(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
if (!md || !ED_object_modifier_move_up(op->reports, ob, md)) {
if (!md || !ED_object_modifier_move_up(op->reports, RPT_WARNING, ob, md)) {
return OPERATOR_CANCELLED;
}
@ -1563,7 +1619,7 @@ static int modifier_move_down_exec(bContext *C, wmOperator *op)
Object *ob = ED_object_active_context(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
if (!md || !ED_object_modifier_move_down(op->reports, ob, md)) {
if (!md || !ED_object_modifier_move_down(op->reports, RPT_WARNING, ob, md)) {
return OPERATOR_CANCELLED;
}
@ -1609,7 +1665,7 @@ static int modifier_move_to_index_exec(bContext *C, wmOperator *op)
ModifierData *md = edit_modifier_property_get(op, ob, 0);
int index = RNA_int_get(op->ptr, "index");
if (!(md && ED_object_modifier_move_to_index(op->reports, ob, md, index))) {
if (!(md && ED_object_modifier_move_to_index(op->reports, RPT_WARNING, ob, md, index, true))) {
return OPERATOR_CANCELLED;
}

View File

@ -1027,8 +1027,12 @@ static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropDa
}
else {
index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->modifiers);
ED_object_modifier_move_to_index(
reports, ob, static_cast<ModifierData *>(drop_data->drag_directdata), index);
ED_object_modifier_move_to_index(reports,
RPT_WARNING,
ob,
static_cast<ModifierData *>(drop_data->drag_directdata),
index,
true);
}
break;
case TSE_CONSTRAINT:

View File

@ -846,6 +846,8 @@ typedef enum eIMBTransformMode {
* - Only one data type buffer will be used (rect_float has priority over rect)
* \param mode: Cropping/Wrap repeat effect to apply during transformation.
* \param filter: Interpolation to use during sampling.
* \param num_subsamples: Number of subsamples to use. Increasing this would improve the quality,
* but reduces the performance.
* \param transform_matrix: Transformation matrix to use.
* The given matrix should transform between dst pixel space to src pixel space.
* One unit is one pixel.
@ -860,6 +862,7 @@ void IMB_transform(const struct ImBuf *src,
struct ImBuf *dst,
eIMBTransformMode mode,
eIMBInterpolationFilterMode filter,
const int num_subsamples,
const float transform_matrix[4][4],
const struct rctf *src_crop);

View File

@ -9,6 +9,8 @@
#include <type_traits>
#include "BLI_math.h"
#include "BLI_math_color_blend.h"
#include "BLI_math_vector.hh"
#include "BLI_rect.h"
#include "IMB_imbuf.h"
@ -22,19 +24,27 @@ struct TransformUserData {
/** \brief Destination image buffer to write to. */
ImBuf *dst;
/** \brief UV coordinates at the origin (0,0) in source image space. */
double start_uv[2];
double2 start_uv;
/**
* \brief delta UV coordinates along the source image buffer, when moving a single pixel in the X
* axis of the dst image buffer.
*/
double add_x[2];
double2 add_x;
/**
* \brief delta UV coordinate along the source image buffer, when moving a single pixel in the Y
* axes of the dst image buffer.
*/
double add_y[2];
double2 add_y;
struct {
int num;
double2 offset_x;
double2 offset_y;
double2 add_x;
double2 add_y;
} subsampling;
/**
* \brief Cropping region in source image pixel space.
@ -44,55 +54,53 @@ struct TransformUserData {
/**
* \brief Initialize the start_uv, add_x and add_y fields based on the given transform matrix.
*/
void init(const float transform_matrix[4][4])
void init(const float transform_matrix[4][4], const int num_subsamples)
{
init_start_uv(transform_matrix);
init_add_x(transform_matrix);
init_add_y(transform_matrix);
init_subsampling(num_subsamples);
}
private:
void init_start_uv(const float transform_matrix[4][4])
{
double start_uv_v3[3];
double orig[3];
double3 start_uv_v3;
double3 orig(0.0);
double transform_matrix_double[4][4];
copy_m4d_m4(transform_matrix_double, transform_matrix);
zero_v3_db(orig);
mul_v3_m4v3_db(start_uv_v3, transform_matrix_double, orig);
copy_v2_v2_db(start_uv, start_uv_v3);
start_uv = double2(start_uv_v3);
}
void init_add_x(const float transform_matrix[4][4])
{
double transform_matrix_double[4][4];
copy_m4d_m4(transform_matrix_double, transform_matrix);
const int width = src->x;
double add_x_v3[3];
double uv_max_x[3];
zero_v3_db(uv_max_x);
uv_max_x[0] = width;
uv_max_x[1] = 0.0f;
mul_v3_m4v3_db(add_x_v3, transform_matrix_double, uv_max_x);
sub_v2_v2_db(add_x_v3, start_uv);
mul_v3db_db(add_x_v3, 1.0f / width);
copy_v2_v2_db(add_x, add_x_v3);
const double width = src->x;
double3 add_x_v3;
mul_v3_m4v3_db(add_x_v3, transform_matrix_double, double3(width, 0.0, 0.0));
add_x = double2((add_x_v3 - double3(start_uv)) * (1.0 / width));
}
void init_add_y(const float transform_matrix[4][4])
{
double transform_matrix_double[4][4];
copy_m4d_m4(transform_matrix_double, transform_matrix);
const int height = src->y;
double add_y_v3[3];
double uv_max_y[3];
zero_v3_db(uv_max_y);
uv_max_y[0] = 0.0f;
uv_max_y[1] = height;
mul_v3_m4v3_db(add_y_v3, transform_matrix_double, uv_max_y);
sub_v2_v2_db(add_y_v3, start_uv);
mul_v3db_db(add_y_v3, 1.0f / height);
copy_v2_v2_db(add_y, add_y_v3);
const double height = src->y;
double3 add_y_v3;
double3 uv_max_y(0.0, height, 0.0);
mul_v3_m4v3_db(add_y_v3, transform_matrix_double, double3(0.0, height, 0.0));
add_y = double2((add_y_v3 - double3(start_uv)) * (1.0 / height));
}
void init_subsampling(const int num_subsamples)
{
subsampling.num = max_ii(num_subsamples, 1);
subsampling.add_x = add_x / (subsampling.num);
subsampling.add_y = add_y / (subsampling.num);
subsampling.offset_x = -add_x * 0.5 + subsampling.add_x * 0.5;
subsampling.offset_y = -add_y * 0.5 + subsampling.add_y * 0.5;
}
};
@ -110,7 +118,7 @@ class BaseDiscard {
/**
* \brief Should the source pixel at the given uv coordinate be discarded.
*/
virtual bool should_discard(const TransformUserData &user_data, const double uv[2]) = 0;
virtual bool should_discard(const TransformUserData &user_data, const double2 uv) = 0;
};
/**
@ -123,10 +131,10 @@ class CropSource : public BaseDiscard {
*
* Uses user_data.src_crop to determine if the uv coordinate should be skipped.
*/
bool should_discard(const TransformUserData &user_data, const double uv[2]) override
bool should_discard(const TransformUserData &user_data, const double2 uv) override
{
return uv[0] < user_data.src_crop.xmin || uv[0] >= user_data.src_crop.xmax ||
uv[1] < user_data.src_crop.ymin || uv[1] >= user_data.src_crop.ymax;
return uv.x < user_data.src_crop.xmin || uv.x >= user_data.src_crop.xmax ||
uv.y < user_data.src_crop.ymin || uv.y >= user_data.src_crop.ymax;
}
};
@ -140,7 +148,7 @@ class NoDiscard : public BaseDiscard {
*
* Will never discard any pixels.
*/
bool should_discard(const TransformUserData & /*user_data*/, const double /*uv*/[2]) override
bool should_discard(const TransformUserData & /*user_data*/, const double2 /*uv*/) override
{
return false;
}
@ -168,9 +176,10 @@ class PixelPointer {
StorageType *pointer;
public:
void init_pixel_pointer(const ImBuf *image_buffer, int x, int y)
void init_pixel_pointer(const ImBuf *image_buffer, int2 start_coordinate)
{
const size_t offset = (y * size_t(image_buffer->x) + x) * NumChannels;
const size_t offset = (start_coordinate.y * size_t(image_buffer->x) + start_coordinate.x) *
NumChannels;
if constexpr (std::is_same_v<StorageType, float>) {
pointer = image_buffer->rect_float + offset;
@ -214,6 +223,14 @@ class BaseUVWrapping {
* \brief modify the given v coordinate.
*/
virtual double modify_v(const ImBuf *source_buffer, double v) = 0;
/**
* \brief modify the given uv coordinate.
*/
double2 modify_uv(const ImBuf *source_buffer, double2 uv)
{
return double2(modify_u(source_buffer, uv.x), modify_v(source_buffer, uv.y));
}
};
/**
@ -259,6 +276,39 @@ class WrapRepeatUV : public BaseUVWrapping {
}
};
// TODO: should we use math_vectors for this.
template<typename StorageType, int NumChannels>
class Pixel : public std::array<StorageType, NumChannels> {
public:
void clear()
{
for (int channel_index : IndexRange(NumChannels)) {
(*this)[channel_index] = 0;
}
}
void add_subsample(const Pixel<StorageType, NumChannels> other, int sample_number)
{
BLI_STATIC_ASSERT((std::is_same_v<StorageType, uchar>) || (std::is_same_v<StorageType, float>),
"Only uchar and float channels supported.");
float factor = 1.0 / (sample_number + 1);
if constexpr (std::is_same_v<StorageType, uchar>) {
BLI_STATIC_ASSERT(NumChannels == 4, "Pixels using uchar requires to have 4 channels.");
blend_color_interpolate_byte(this->data(), this->data(), other.data(), factor);
}
else if constexpr (std::is_same_v<StorageType, float> && NumChannels == 4) {
blend_color_interpolate_float(this->data(), this->data(), other.data(), factor);
}
else if constexpr (std::is_same_v<StorageType, float>) {
for (int channel_index : IndexRange(NumChannels)) {
(*this)[channel_index] = (*this)[channel_index] * (1.0 - factor) +
other[channel_index] * factor;
}
}
}
};
/**
* \brief Read a sample from an image buffer.
*
@ -288,27 +338,24 @@ class Sampler {
public:
using ChannelType = StorageType;
static const int ChannelLen = NumChannels;
using SampleType = std::array<StorageType, NumChannels>;
using SampleType = Pixel<StorageType, NumChannels>;
void sample(const ImBuf *source, const double u, const double v, SampleType &r_sample)
void sample(const ImBuf *source, const double2 uv, SampleType &r_sample)
{
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float> &&
NumChannels == 4) {
const double wrapped_u = uv_wrapper.modify_u(source, u);
const double wrapped_v = uv_wrapper.modify_v(source, v);
bilinear_interpolation_color_fl(source, nullptr, r_sample.data(), wrapped_u, wrapped_v);
const double2 wrapped_uv = uv_wrapper.modify_uv(source, uv);
bilinear_interpolation_color_fl(source, nullptr, r_sample.data(), UNPACK2(wrapped_uv));
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<StorageType, uchar> &&
NumChannels == 4) {
const double wrapped_u = uv_wrapper.modify_u(source, u);
const double wrapped_v = uv_wrapper.modify_v(source, v);
nearest_interpolation_color_char(source, r_sample.data(), nullptr, wrapped_u, wrapped_v);
const double2 wrapped_uv = uv_wrapper.modify_uv(source, uv);
nearest_interpolation_color_char(source, r_sample.data(), nullptr, UNPACK2(wrapped_uv));
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, uchar> &&
NumChannels == 4) {
const double wrapped_u = uv_wrapper.modify_u(source, u);
const double wrapped_v = uv_wrapper.modify_v(source, v);
bilinear_interpolation_color_char(source, r_sample.data(), nullptr, wrapped_u, wrapped_v);
const double2 wrapped_uv = uv_wrapper.modify_uv(source, uv);
bilinear_interpolation_color_char(source, r_sample.data(), nullptr, UNPACK2(wrapped_uv));
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float>) {
if constexpr (std::is_same_v<UVWrapping, WrapRepeatUV>) {
@ -317,27 +364,23 @@ class Sampler {
source->x,
source->y,
NumChannels,
u,
v,
UNPACK2(uv),
true,
true);
}
else {
const double wrapped_u = uv_wrapper.modify_u(source, u);
const double wrapped_v = uv_wrapper.modify_v(source, v);
const double2 wrapped_uv = uv_wrapper.modify_uv(source, uv);
BLI_bilinear_interpolation_fl(source->rect_float,
r_sample.data(),
source->x,
source->y,
NumChannels,
wrapped_u,
wrapped_v);
UNPACK2(wrapped_uv));
}
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<StorageType, float>) {
const double wrapped_u = uv_wrapper.modify_u(source, u);
const double wrapped_v = uv_wrapper.modify_v(source, v);
sample_nearest_float(source, wrapped_u, wrapped_v, r_sample);
const double2 wrapped_uv = uv_wrapper.modify_uv(source, uv);
sample_nearest_float(source, UNPACK2(wrapped_uv), r_sample);
}
else {
/* Unsupported sampler. */
@ -387,12 +430,12 @@ class Sampler {
template<typename StorageType, int SourceNumChannels, int DestinationNumChannels>
class ChannelConverter {
public:
using SampleType = std::array<StorageType, SourceNumChannels>;
using SampleType = Pixel<StorageType, SourceNumChannels>;
using PixelType = PixelPointer<StorageType, DestinationNumChannels>;
/**
* \brief Convert the number of channels of the given sample to match the pixel pointer and store
* it at the location the pixel_pointer points at.
* \brief Convert the number of channels of the given sample to match the pixel pointer and
* store it at the location the pixel_pointer points at.
*/
void convert_and_store(const SampleType &sample, PixelType &pixel_pointer)
{
@ -422,6 +465,24 @@ class ChannelConverter {
BLI_assert_unreachable();
}
}
void mix_and_store(const SampleType &sample, PixelType &pixel_pointer, const float mix_factor)
{
if constexpr (std::is_same_v<StorageType, uchar>) {
BLI_STATIC_ASSERT(SourceNumChannels == 4, "Unsigned chars always have 4 channels.");
BLI_STATIC_ASSERT(DestinationNumChannels == 4, "Unsigned chars always have 4 channels.");
blend_color_interpolate_byte(
pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor);
}
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 4 &&
DestinationNumChannels == 4) {
blend_color_interpolate_float(
pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor);
}
else {
BLI_assert_unreachable();
}
}
};
/**
@ -451,8 +512,8 @@ class ScanlineProcessor {
Sampler sampler;
/**
* \brief Channels sizzling logic to convert between the input image buffer and the output image
* buffer.
* \brief Channels sizzling logic to convert between the input image buffer and the output
* image buffer.
*/
ChannelConverter<typename Sampler::ChannelType,
Sampler::ChannelLen,
@ -464,21 +525,99 @@ class ScanlineProcessor {
* \brief Inner loop of the transformations, processing a full scanline.
*/
void process(const TransformUserData *user_data, int scanline)
{
if (user_data->subsampling.num > 1) {
process_with_subsampling(user_data, scanline);
}
else {
process_one_sample_per_pixel(user_data, scanline);
}
}
private:
void process_one_sample_per_pixel(const TransformUserData *user_data, int scanline)
{
const int width = user_data->dst->x;
double2 uv = user_data->start_uv + user_data->add_y * scanline;
double uv[2];
madd_v2_v2db_db(uv, user_data->start_uv, user_data->add_y, scanline);
output.init_pixel_pointer(user_data->dst, int2(0, scanline));
int xi = 0;
while (xi < width) {
const bool discard_pixel = discarder.should_discard(*user_data, uv);
if (!discard_pixel) {
break;
}
uv += user_data->add_x;
output.increase_pixel_pointer();
xi += 1;
}
output.init_pixel_pointer(user_data->dst, 0, scanline);
for (int xi = 0; xi < width; xi++) {
for (; xi < width; xi++) {
if (!discarder.should_discard(*user_data, uv)) {
typename Sampler::SampleType sample;
sampler.sample(user_data->src, uv[0], uv[1], sample);
sampler.sample(user_data->src, uv, sample);
channel_converter.convert_and_store(sample, output);
}
add_v2_v2_db(uv, user_data->add_x);
uv += user_data->add_x;
output.increase_pixel_pointer();
}
}
void process_with_subsampling(const TransformUserData *user_data, int scanline)
{
const int width = user_data->dst->x;
double2 uv = user_data->start_uv + user_data->add_y * scanline;
output.init_pixel_pointer(user_data->dst, int2(0, scanline));
int xi = 0;
/*
* Skip leading pixels that would be fully discarded.
*
* NOTE: This could be improved by intersection between an ray and the image bounds.
*/
while (xi < width) {
const bool discard_pixel = discarder.should_discard(*user_data, uv) &&
discarder.should_discard(*user_data, uv + user_data->add_x) &&
discarder.should_discard(*user_data, uv + user_data->add_y) &&
discarder.should_discard(
*user_data, uv + user_data->add_x + user_data->add_y);
if (!discard_pixel) {
break;
}
uv += user_data->add_x;
output.increase_pixel_pointer();
xi += 1;
}
for (; xi < width; xi++) {
typename Sampler::SampleType sample;
sample.clear();
int num_subsamples_added = 0;
double2 subsample_uv_y = uv + user_data->subsampling.offset_y;
for (int subsample_yi : IndexRange(user_data->subsampling.num)) {
UNUSED_VARS(subsample_yi);
double2 subsample_uv = subsample_uv_y + user_data->subsampling.offset_x;
for (int subsample_xi : IndexRange(user_data->subsampling.num)) {
UNUSED_VARS(subsample_xi);
if (!discarder.should_discard(*user_data, subsample_uv)) {
typename Sampler::SampleType sub_sample;
sampler.sample(user_data->src, subsample_uv, sub_sample);
sample.add_subsample(sub_sample, num_subsamples_added);
num_subsamples_added += 1;
}
subsample_uv += user_data->subsampling.add_x;
}
subsample_uv_y += user_data->subsampling.add_y;
}
if (num_subsamples_added != 0) {
float mix_weight = float(num_subsamples_added) /
(user_data->subsampling.num * user_data->subsampling.num);
channel_converter.mix_and_store(sample, output, mix_weight);
}
uv += user_data->add_x;
output.increase_pixel_pointer();
}
}
@ -573,6 +712,7 @@ void IMB_transform(const struct ImBuf *src,
struct ImBuf *dst,
const eIMBTransformMode mode,
const eIMBInterpolationFilterMode filter,
const int num_subsamples,
const float transform_matrix[4][4],
const struct rctf *src_crop)
{
@ -586,7 +726,7 @@ void IMB_transform(const struct ImBuf *src,
if (mode == IMB_TRANSFORM_MODE_CROP_SRC) {
user_data.src_crop = *src_crop;
}
user_data.init(transform_matrix);
user_data.init(transform_matrix, num_subsamples);
if (filter == IMB_FILTER_NEAREST) {
transform_threaded<IMB_FILTER_NEAREST>(&user_data, mode);

View File

@ -163,13 +163,18 @@ target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES})
if(WITH_GTESTS)
set(TEST_SRC
tests/usd_stage_creation_test.cc
tests/usd_export_test.cc
tests/usd_tests_common.cc
tests/usd_tests_common.h
intern/usd_writer_material.h
)
if(USD_IMAGING_HEADERS)
list(APPEND TEST_SRC tests/usd_imaging_test.cc)
endif()
include_directories(intern)
set(TEST_INC
)
set(TEST_LIB

View File

@ -66,7 +66,9 @@ static void export_startjob(void *customdata,
data->start_time = timeit::Clock::now();
G.is_rendering = true;
WM_set_locked_interface(data->wm, true);
if (data->wm) {
WM_set_locked_interface(data->wm, true);
}
G.is_break = false;
/* Construct the depsgraph for exporting. */
@ -160,7 +162,9 @@ static void export_endjob(void *customdata)
}
G.is_rendering = false;
WM_set_locked_interface(data->wm, false);
if (data->wm) {
WM_set_locked_interface(data->wm, false);
}
report_job_duration(data);
}

View File

@ -207,6 +207,7 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
if (!stage) {
WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filepath);
data->import_ok = false;
data->error_code = USD_ARCHIVE_FAIL;
return;
}

View File

@ -748,4 +748,16 @@ static void export_texture(bNode *node,
}
}
const pxr::TfToken token_for_input(const char *input_name)
{
const InputSpecMap &input_map = preview_surface_input_map();
const InputSpecMap::const_iterator it = input_map.find(input_name);
if (it == input_map.end()) {
return pxr::TfToken();
}
return it->second.input_name;
}
} // namespace blender::io::usd

View File

@ -14,6 +14,10 @@ namespace blender::io::usd {
struct USDExporterContext;
/* Returns a USDPreviewSurface token name for a given Blender shader Socket name,
* or an empty TfToken if the input name is not found in the map. */
const pxr::TfToken token_for_input(const char *input_name);
/**
* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes

View File

@ -0,0 +1,316 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "tests/blendfile_loading_base_test.h"
#include <pxr/base/plug/registry.h>
#include <pxr/base/tf/stringUtils.h>
#include <pxr/base/vt/types.h>
#include <pxr/base/vt/value.h>
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/subset.h>
#include <pxr/usd/usdGeom/tokens.h>
#include "DNA_image_types.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "BKE_context.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_node.h"
#include "BLI_fileops.h"
#include "BLI_math.h"
#include "BLI_math_vector_types.hh"
#include "BLI_path_util.h"
#include "BLO_readfile.h"
#include "BKE_node_runtime.hh"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "usd.h"
#include "usd_tests_common.h"
#include "usd_writer_material.h"
namespace blender::io::usd {
const StringRefNull simple_scene_filename = "usd/usd_simple_scene.blend";
const StringRefNull materials_filename = "usd/usd_materials_export.blend";
const StringRefNull output_filename = "output.usd";
static const bNode *find_node_for_type_in_graph(const bNodeTree *nodetree,
const blender::StringRefNull type_idname);
class UsdExportTest : public BlendfileLoadingBaseTest {
protected:
struct bContext *context = nullptr;
public:
bool load_file_and_depsgraph(const StringRefNull &filepath,
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
{
if (!blendfile_load(filepath.c_str())) {
return false;
}
depsgraph_create(eval_mode);
context = CTX_create();
CTX_data_main_set(context, bfile->main);
CTX_data_scene_set(context, bfile->curscene);
return true;
}
virtual void SetUp() override
{
BlendfileLoadingBaseTest::SetUp();
std::string usd_plugin_path = register_usd_plugins_for_tests();
if (usd_plugin_path.empty()) {
FAIL() << "Unable to find the USD Plugins path.";
}
}
virtual void TearDown() override
{
BlendfileLoadingBaseTest::TearDown();
CTX_free(context);
context = nullptr;
if (BLI_exists(output_filename.c_str())) {
BLI_delete(output_filename.c_str(), false, false);
}
}
const pxr::UsdPrim get_first_child_mesh(const pxr::UsdPrim prim)
{
for (auto child : prim.GetChildren()) {
if (child.IsA<pxr::UsdGeomMesh>()) {
return child;
}
}
return pxr::UsdPrim();
}
/*
* Loop the sockets on the Blender bNode, and fail if any of their values do
* not match the equivalent Attribtue values on the UsdPrim.
*/
const void compare_blender_node_to_usd_prim(const bNode *bsdf_node,
const pxr::UsdPrim &bsdf_prim)
{
ASSERT_NE(bsdf_node, nullptr);
ASSERT_TRUE(bool(bsdf_prim));
for (auto socket : bsdf_node->input_sockets()) {
const pxr::TfToken attribute_token = blender::io::usd::token_for_input(socket->name);
if (attribute_token.IsEmpty()) {
/* This socket is not translated between Blender and USD. */
continue;
}
const pxr::UsdAttribute bsdf_attribute = bsdf_prim.GetAttribute(attribute_token);
pxr::SdfPathVector paths;
bsdf_attribute.GetConnections(&paths);
if (!paths.empty() || !bsdf_attribute.IsValid()) {
/* Skip if the attribute is connected or has an error. */
continue;
}
const float socket_value_f = *socket->default_value_typed<float>();
const float3 socket_value_3f = *socket->default_value_typed<float3>();
float attribute_value_f;
pxr::GfVec3f attribute_value_3f;
switch (socket->type) {
case SOCK_FLOAT:
bsdf_attribute.Get(&attribute_value_f, 0.0);
EXPECT_FLOAT_EQ(socket_value_f, attribute_value_f);
break;
case SOCK_VECTOR:
bsdf_attribute.Get(&attribute_value_3f, 0.0);
EXPECT_FLOAT_EQ(socket_value_3f[0], attribute_value_3f[0]);
EXPECT_FLOAT_EQ(socket_value_3f[1], attribute_value_3f[1]);
EXPECT_FLOAT_EQ(socket_value_3f[2], attribute_value_3f[2]);
break;
case SOCK_RGBA:
bsdf_attribute.Get(&attribute_value_3f, 0.0);
EXPECT_FLOAT_EQ(socket_value_3f[0], attribute_value_3f[0]);
EXPECT_FLOAT_EQ(socket_value_3f[1], attribute_value_3f[1]);
EXPECT_FLOAT_EQ(socket_value_3f[2], attribute_value_3f[2]);
break;
default:
FAIL() << "Socket " << socket->name << " has unsupported type " << socket->type;
break;
}
}
}
const void compare_blender_image_to_usd_image_shader(const bNode *image_node,
const pxr::UsdPrim &image_prim)
{
const Image *image = reinterpret_cast<Image *>(image_node->id);
const pxr::UsdShadeShader image_shader(image_prim);
const pxr::UsdShadeInput file_input = image_shader.GetInput(pxr::TfToken("file"));
EXPECT_TRUE(bool(file_input));
pxr::VtValue file_val;
EXPECT_TRUE(file_input.Get(&file_val));
EXPECT_TRUE(file_val.IsHolding<pxr::SdfAssetPath>());
pxr::SdfAssetPath image_prim_asset = file_val.Get<pxr::SdfAssetPath>();
/* The path is expected to be relative, but that means in Blender the
* path will start with //.
*/
EXPECT_EQ(
BLI_path_cmp_normalized(image->filepath + 2, image_prim_asset.GetAssetPath().c_str()), 0);
}
/*
* Determine if a Blender Mesh matches a UsdGeomMesh prim by checking counts
* on vertices, faces, face indices, and normals.
*/
const void compare_blender_mesh_to_usd_prim(const Mesh *mesh, const pxr::UsdGeomMesh &mesh_prim)
{
pxr::VtIntArray face_indices;
pxr::VtIntArray face_counts;
pxr::VtVec3fArray positions;
pxr::VtVec3fArray normals;
/* Our export doesn't use 'primvars:normals' so we're not
* looking for that to be written here. */
mesh_prim.GetFaceVertexIndicesAttr().Get(&face_indices, 0.0);
mesh_prim.GetFaceVertexCountsAttr().Get(&face_counts, 0.0);
mesh_prim.GetPointsAttr().Get(&positions, 0.0);
mesh_prim.GetNormalsAttr().Get(&normals, 0.0);
EXPECT_EQ(mesh->totvert, positions.size());
EXPECT_EQ(mesh->totpoly, face_counts.size());
EXPECT_EQ(mesh->totloop, face_indices.size());
EXPECT_EQ(mesh->totloop, normals.size());
}
};
TEST_F(UsdExportTest, usd_export_rain_mesh)
{
if (!load_file_and_depsgraph(simple_scene_filename)) {
FAIL() << "Unable to load file: " << simple_scene_filename;
return;
}
/* File sanity check. */
EXPECT_EQ(BLI_listbase_count(&bfile->main->objects), 3);
USDExportParams params{};
params.export_normals = true;
params.visible_objects_only = true;
params.evaluation_mode = eEvaluationMode::DAG_EVAL_VIEWPORT;
bool result = USD_export(context, output_filename.c_str(), &params, false);
ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!";
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);
ASSERT_TRUE(bool(stage)) << "Unable to load Stage from " << output_filename;
/*
* Run the mesh comparison for all Meshes in the original scene.
*/
LISTBASE_FOREACH (Object *, object, &bfile->main->objects) {
const Mesh *mesh = static_cast<Mesh *>(object->data);
const StringRefNull object_name(object->id.name + 2);
const pxr::SdfPath sdf_path("/" + pxr::TfMakeValidIdentifier(object_name.c_str()));
pxr::UsdPrim prim = stage->GetPrimAtPath(sdf_path);
EXPECT_TRUE(bool(prim));
const pxr::UsdGeomMesh mesh_prim(get_first_child_mesh(prim));
EXPECT_TRUE(bool(mesh_prim));
compare_blender_mesh_to_usd_prim(mesh, mesh_prim);
}
}
static const bNode *find_node_for_type_in_graph(const bNodeTree *nodetree,
const blender::StringRefNull type_idname)
{
auto found_nodes = nodetree->nodes_by_type(type_idname);
if (found_nodes.size() == 1) {
return found_nodes[0];
}
return nullptr;
}
/*
* Export Material test-- export a scene with a material, then read it back
* in and check that the BSDF and Image Texture nodes translated correctly
* by comparing values between the exported USD stage and the objects in
* memory.
*/
TEST_F(UsdExportTest, usd_export_material)
{
if (!load_file_and_depsgraph(materials_filename)) {
FAIL() << "Unable to load file: " << materials_filename;
return;
}
/* File sanity checks. */
EXPECT_EQ(BLI_listbase_count(&bfile->main->objects), 1);
/* There are two materials because of the Dots Stroke. */
EXPECT_EQ(BLI_listbase_count(&bfile->main->materials), 2);
Material *material = reinterpret_cast<Material *>(
BKE_libblock_find_name(bfile->main, ID_MA, "Material"));
EXPECT_TRUE(bool(material));
USDExportParams params{};
params.export_normals = true;
params.export_materials = true;
params.generate_preview_surface = true;
params.export_uvmaps = true;
params.evaluation_mode = eEvaluationMode::DAG_EVAL_VIEWPORT;
const bool result = USD_export(context, output_filename.c_str(), &params, false);
ASSERT_TRUE(result) << "Unable to export stage to " << output_filename;
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);
ASSERT_NE(stage, nullptr) << "Unable to open exported stage: " << output_filename;
material->nodetree->ensure_topology_cache();
const bNode *bsdf_node = find_node_for_type_in_graph(material->nodetree,
"ShaderNodeBsdfPrincipled");
const std::string prim_name = pxr::TfMakeValidIdentifier(bsdf_node->name);
const pxr::UsdPrim bsdf_prim = stage->GetPrimAtPath(
pxr::SdfPath("/_materials/Material/preview/" + prim_name));
compare_blender_node_to_usd_prim(bsdf_node, bsdf_prim);
const bNode *image_node = find_node_for_type_in_graph(material->nodetree, "ShaderNodeTexImage");
ASSERT_NE(image_node, nullptr);
ASSERT_NE(image_node->storage, nullptr);
const std::string image_prim_name = pxr::TfMakeValidIdentifier(image_node->name);
const pxr::UsdPrim image_prim = stage->GetPrimAtPath(
pxr::SdfPath("/_materials/Material/preview/" + image_prim_name));
ASSERT_TRUE(bool(image_prim)) << "Unable to find Material prim from exported stage "
<< output_filename;
compare_blender_image_to_usd_image_shader(image_node, image_prim);
}
} // namespace blender::io::usd

View File

@ -795,6 +795,7 @@ typedef enum SequenceColorTag {
enum {
SEQ_TRANSFORM_FILTER_NEAREST = 0,
SEQ_TRANSFORM_FILTER_BILINEAR = 1,
SEQ_TRANSFORM_FILTER_NEAREST_3x3 = 2,
};
typedef enum eSeqChannelFlag {

View File

@ -1101,17 +1101,23 @@ static void rna_GPencil_layer_mask_remove(bGPDlayer *gpl,
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
static void rna_GPencil_frame_clear(bGPDframe *frame)
static void rna_GPencil_frame_clear(ID *id, bGPDframe *frame)
{
BKE_gpencil_free_strokes(frame);
bGPdata *gpd = (bGPdata *)id;
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
static void rna_GPencil_layer_clear(bGPDlayer *layer)
static void rna_GPencil_layer_clear(ID *id, bGPDlayer *layer)
{
BKE_gpencil_free_frames(layer);
bGPdata *gpd = (bGPdata *)id;
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
@ -1119,6 +1125,8 @@ static void rna_GPencil_clear(bGPdata *gpd)
{
BKE_gpencil_free_layers(&gpd->layers);
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
@ -1841,6 +1849,7 @@ static void rna_def_gpencil_frame(BlenderRNA *brna)
/* API */
func = RNA_def_function(srna, "clear", "rna_GPencil_frame_clear");
RNA_def_function_ui_description(func, "Remove all the grease pencil frame data");
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
}
static void rna_def_gpencil_frames_api(BlenderRNA *brna, PropertyRNA *cprop)
@ -2304,6 +2313,7 @@ static void rna_def_gpencil_layer(BlenderRNA *brna)
/* Layers API */
func = RNA_def_function(srna, "clear", "rna_GPencil_layer_clear");
RNA_def_function_ui_description(func, "Remove all the grease pencil layer data");
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
}
static void rna_def_gpencil_layers_api(BlenderRNA *brna, PropertyRNA *cprop)

View File

@ -1435,9 +1435,9 @@ static void rna_NodeTree_active_output_set(PointerRNA *ptr, int value)
}
}
static bool rna_NodeTree_contains_in(bNodeTree *tree, bNodeTree *parent_tree)
static bool rna_NodeTree_contains_group(bNodeTree *tree, bNodeTree *sub_tree)
{
return ntreeContainsTree(parent_tree, tree);
return ntreeContainsTree(tree, sub_tree);
}
static bNodeSocket *rna_NodeTree_inputs_new(
@ -12722,14 +12722,9 @@ static void rna_def_nodetree(BlenderRNA *brna)
parm = RNA_def_pointer(func, "context", "Context", "", "");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
/* Test to check if current node tree contained in parent_tree.
* Generally use to avoid creating recursive nodegroups.
*/
func = RNA_def_function(srna, "contains_in", "rna_NodeTree_contains_in");
RNA_def_function_ui_description(
func, "Recursively check if this node group contained in other node group");
parm = RNA_def_pointer(
func, "parent_tree", "NodeTree", "Node Group", "Node group for recursive check");
func = RNA_def_function(srna, "contains_group", "rna_NodeTree_contains_group");
RNA_def_function_ui_description(func, "Check if the node tree contains another. Used to avoid creating recursive node groups");
parm = RNA_def_pointer(func, "sub_tree", "NodeTree", "Node Group", "Node group for recursive check");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
parm = RNA_def_property(func, "contained", PROP_BOOLEAN, PROP_NONE);
RNA_def_function_return(func, parm);

View File

@ -1759,6 +1759,19 @@ static void rna_Object_modifier_clear(Object *object, bContext *C)
WM_main_add_notifier(NC_OBJECT | ND_MODIFIER | NA_REMOVED, object);
}
static void rna_Object_modifier_move(
Object *object, Main *bmain, ReportList *reports, int from, int to)
{
ModifierData *md = BLI_findlink(&object->modifiers, from);
if (!md) {
BKE_reportf(reports, RPT_ERROR, "Invalid original modifier index '%d'", from);
return;
}
ED_object_modifier_move_to_index(reports, RPT_ERROR, object, md, to, false);
}
static PointerRNA rna_Object_active_modifier_get(PointerRNA *ptr)
{
Object *ob = (Object *)ptr->owner_id;
@ -2635,6 +2648,16 @@ static void rna_def_object_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Remove all modifiers from the object");
/* move a modifier */
func = RNA_def_function(srna, "move", "rna_Object_modifier_move");
RNA_def_function_ui_description(func, "Move a modifier to a different position");
RNA_def_function_flag(func, FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_int(
func, "from_index", -1, INT_MIN, INT_MAX, "From Index", "Index to move", 0, 10000);
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_int(func, "to_index", -1, INT_MIN, INT_MAX, "To Index", "Target index", 0, 10000);
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
/* Active modifier. */
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Modifier");

View File

@ -1510,6 +1510,11 @@ static void rna_def_strip_crop(BlenderRNA *brna)
static const EnumPropertyItem transform_filter_items[] = {
{SEQ_TRANSFORM_FILTER_NEAREST, "NEAREST", 0, "Nearest", ""},
{SEQ_TRANSFORM_FILTER_BILINEAR, "BILINEAR", 0, "Bilinear", ""},
{SEQ_TRANSFORM_FILTER_NEAREST_3x3,
"SUBSAMPLING_3x3",
0,
"Subsampling (3x3)",
"Use nearest with 3x3 subsamples during rendering"},
{0, NULL, 0, NULL, NULL},
};

View File

@ -6,6 +6,8 @@
#include "BKE_context.h"
#include "BKE_node_runtime.hh"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -26,7 +28,11 @@ static void node_shader_buts_normal_map(uiLayout *layout, bContext *C, PointerRN
PointerRNA obptr = CTX_data_pointer_get(C, "active_object");
if (obptr.data && RNA_enum_get(&obptr, "type") == OB_MESH) {
PointerRNA dataptr = RNA_pointer_get(&obptr, "data");
PointerRNA eval_obptr;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr);
PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data");
uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE);
}
else {

View File

@ -5,6 +5,8 @@
#include "BKE_context.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -29,7 +31,11 @@ static void node_shader_buts_tangent(uiLayout *layout, bContext *C, PointerRNA *
PointerRNA obptr = CTX_data_pointer_get(C, "active_object");
if (obptr.data && RNA_enum_get(&obptr, "type") == OB_MESH) {
PointerRNA dataptr = RNA_pointer_get(&obptr, "data");
PointerRNA eval_obptr;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr);
PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data");
uiItemPointerR(row, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE);
}
else {

View File

@ -7,6 +7,8 @@
#include "DNA_customdata_types.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -25,7 +27,11 @@ static void node_shader_buts_uvmap(uiLayout *layout, bContext *C, PointerRNA *pt
PointerRNA obptr = CTX_data_pointer_get(C, "active_object");
if (obptr.data && RNA_enum_get(&obptr, "type") == OB_MESH) {
PointerRNA dataptr = RNA_pointer_get(&obptr, "data");
PointerRNA eval_obptr;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr);
PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data");
uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE);
}
}

View File

@ -5,6 +5,8 @@
#include "BKE_context.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -20,8 +22,11 @@ static void node_shader_buts_vertex_color(uiLayout *layout, bContext *C, Pointer
{
PointerRNA obptr = CTX_data_pointer_get(C, "active_object");
if (obptr.data && RNA_enum_get(&obptr, "type") == OB_MESH) {
PointerRNA dataptr = RNA_pointer_get(&obptr, "data");
PointerRNA eval_obptr;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr);
PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data");
uiItemPointerR(layout, ptr, "layer_name", &dataptr, "color_attributes", "", ICON_GROUP_VCOL);
}
else {

View File

@ -445,8 +445,14 @@ static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out)
(const float[]){scale_x, scale_y, 1.0f});
transform_pivot_set_m4(transform_matrix, pivot);
invert_m4(transform_matrix);
IMB_transform(in, out, IMB_TRANSFORM_MODE_REGULAR, IMB_FILTER_NEAREST, transform_matrix, NULL);
const int num_subsamples = 1;
IMB_transform(in,
out,
IMB_TRANSFORM_MODE_REGULAR,
IMB_FILTER_NEAREST,
num_subsamples,
transform_matrix,
NULL);
}
/* Check whether transform introduces transparent ares in the result (happens when the transformed
@ -509,16 +515,31 @@ static void sequencer_preprocess_transform_crop(
const float crop_scale_factor = do_scale_to_render_size ? preview_scale_factor : 1.0f;
sequencer_image_crop_init(seq, in, crop_scale_factor, &source_crop);
eIMBInterpolationFilterMode filter;
const StripTransform *transform = seq->strip->transform;
if (transform->filter == SEQ_TRANSFORM_FILTER_NEAREST) {
filter = IMB_FILTER_NEAREST;
}
else {
filter = IMB_FILTER_BILINEAR;
eIMBInterpolationFilterMode filter;
int num_subsamples = 1;
switch (transform->filter) {
case SEQ_TRANSFORM_FILTER_NEAREST:
filter = IMB_FILTER_NEAREST;
num_subsamples = 1;
break;
case SEQ_TRANSFORM_FILTER_BILINEAR:
filter = IMB_FILTER_BILINEAR;
num_subsamples = 1;
break;
case SEQ_TRANSFORM_FILTER_NEAREST_3x3:
filter = IMB_FILTER_NEAREST;
num_subsamples = G.is_rendering ? 3 : 1;
break;
}
IMB_transform(in, out, IMB_TRANSFORM_MODE_CROP_SRC, filter, transform_matrix, &source_crop);
IMB_transform(in,
out,
IMB_TRANSFORM_MODE_CROP_SRC,
filter,
num_subsamples,
transform_matrix,
&source_crop);
if (!seq_image_transform_transparency_gained(context, seq)) {
out->planes = in->planes;

View File

@ -944,6 +944,14 @@ static intptr_t wm_operator_undo_active_id(const wmWindowManager *wm)
return -1;
}
static intptr_t wm_operator_register_active_id(const wmWindowManager *wm)
{
if (wm->operators.last) {
return intptr_t(wm->operators.last);
}
return -1;
}
bool WM_operator_poll(bContext *C, wmOperatorType *ot)
{
@ -1078,9 +1086,14 @@ static bool wm_operator_register_check(wmWindowManager *wm, wmOperatorType *ot)
/**
* \param has_undo_step: True when an undo step was added,
* needed when the operator doesn't use #OPTYPE_UNDO, #OPTYPE_UNDO_GROUPED but adds an undo step.
* \param has_register: True when an operator was registered.
*/
static void wm_operator_finished(
bContext *C, wmOperator *op, const bool repeat, const bool store, const bool has_undo_step)
static void wm_operator_finished(bContext *C,
wmOperator *op,
const bool repeat,
const bool store,
const bool has_undo_step,
const bool has_register)
{
wmWindowManager *wm = CTX_wm_manager(C);
enum {
@ -1088,6 +1101,7 @@ static void wm_operator_finished(
SET,
CLEAR,
} hud_status = NOP;
const bool do_register = (repeat == false) && wm_operator_register_check(wm, op->type);
op->customdata = nullptr;
@ -1112,8 +1126,14 @@ static void wm_operator_finished(
}
}
else if (has_undo_step) {
if (repeat == 0) {
hud_status = CLEAR;
/* An undo step was added but the operator wasn't registered (and won't register it's self),
* therefor a redo panel wouldn't redo this action but the previous registered action,
* causing the "redo" to remove/loose this operator. See: T101743.
* Register check is needed so nested operator calls don't clear the HUD. See: T103587. */
if (!(has_register || do_register)) {
if (repeat == 0) {
hud_status = CLEAR;
}
}
}
}
@ -1125,7 +1145,7 @@ static void wm_operator_finished(
MEM_freeN(buf);
}
if (wm_operator_register_check(wm, op->type)) {
if (do_register) {
/* Take ownership of reports (in case python provided own). */
op->reports->flag |= RPT_FREE;
@ -1177,6 +1197,8 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons
}
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
const intptr_t register_id_prev = wm_operator_register_active_id(wm);
if (op->type->exec) {
if (op->type->flag & OPTYPE_UNDO) {
wm->op_undo_depth++;
@ -1199,8 +1221,10 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons
if (retval & OPERATOR_FINISHED) {
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
const bool has_register = (register_id_prev != wm_operator_register_active_id(wm));
wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0, has_undo_step);
wm_operator_finished(
C, op, repeat, store && wm->op_undo_depth == 0, has_undo_step, has_register);
}
else if (repeat == 0) {
/* WARNING: modal from exec is bad practice, but avoid crashing. */
@ -1442,6 +1466,7 @@ static int wm_operator_invoke(bContext *C,
if (WM_operator_poll(C, ot)) {
wmWindowManager *wm = CTX_wm_manager(C);
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
const intptr_t register_id_prev = wm_operator_register_active_id(wm);
/* If `reports == nullptr`, they'll be initialized. */
wmOperator *op = wm_operator_create(wm, ot, properties, reports);
@ -1511,8 +1536,9 @@ static int wm_operator_invoke(bContext *C,
}
else if (retval & OPERATOR_FINISHED) {
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
const bool has_register = (register_id_prev != wm_operator_register_active_id(wm));
const bool store = !is_nested_call && use_last_properties;
wm_operator_finished(C, op, false, store, has_undo_step);
wm_operator_finished(C, op, false, store, has_undo_step, has_register);
}
else if (retval & OPERATOR_RUNNING_MODAL) {
/* Take ownership of reports (in case python provided own). */
@ -2402,6 +2428,7 @@ static int wm_handler_operator_call(bContext *C,
wm_event_modalkeymap_begin(C, op, event, &event_backup);
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
const intptr_t register_id_prev = wm_operator_register_active_id(wm);
if (ot->flag & OPTYPE_UNDO) {
wm->op_undo_depth++;
}
@ -2438,8 +2465,9 @@ static int wm_handler_operator_call(bContext *C,
/* Important to run 'wm_operator_finished' before setting the context members to null. */
if (retval & OPERATOR_FINISHED) {
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
const bool has_register = (register_id_prev != wm_operator_register_active_id(wm));
wm_operator_finished(C, op, false, true, has_undo_step);
wm_operator_finished(C, op, false, true, has_undo_step, has_register);
handler->op = nullptr;
}
else if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {

View File

@ -24,24 +24,132 @@ class AbstractUSDTest(unittest.TestCase):
class USDImportTest(AbstractUSDTest):
def test_import_operator(self):
"""Test running the import operator on valid and invalid files."""
infile = str(self.testdir / "usd_mesh_polygon_types.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
infile = str(self.testdir / "this_file_doesn't_exist.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!")
def test_import_prim_hierarchy(self):
"""Test importing a simple object hierarchy from a USDA file."""
infile = str(self.testdir / "prim-hierarchy.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
objects = bpy.context.scene.collection.objects
self.assertEqual(5, len(objects))
self.assertEqual(5, len(objects), f"Test scene {infile} should have five objects; found {len(objects)}")
# Test the hierarchy.
self.assertIsNone(objects['World'].parent)
self.assertEqual(objects['World'], objects['Plane'].parent)
self.assertEqual(objects['World'], objects['Plane_001'].parent)
self.assertEqual(objects['World'], objects['Empty'].parent)
self.assertEqual(objects['Empty'], objects['Plane_002'].parent)
self.assertIsNone(objects['World'].parent, "/World should not be parented.")
self.assertEqual(objects['World'], objects['Plane'].parent, "Plane should be child of /World")
self.assertEqual(objects['World'], objects['Plane_001'].parent, "Plane_001 should be a child of /World")
self.assertEqual(objects['World'], objects['Empty'].parent, "Empty should be a child of /World")
self.assertEqual(objects['Empty'], objects['Plane_002'].parent, "Plane_002 should be a child of /World")
def test_import_mesh_topology(self):
"""Test importing meshes with different polygon types."""
infile = str(self.testdir / "usd_mesh_polygon_types.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
objects = bpy.context.scene.collection.objects
self.assertEqual(5, len(objects), f"Test scene {infile} should have five objects; found {len(objects)}")
# Test topology counts.
self.assertIn("m_degenerate", objects, "Scene does not contain object m_degenerate")
mesh = objects["m_degenerate"].data
self.assertEqual(len(mesh.polygons), 2)
self.assertEqual(len(mesh.edges), 7)
self.assertEqual(len(mesh.vertices), 6)
self.assertIn("m_triangles", objects, "Scene does not contain object m_triangles")
mesh = objects["m_triangles"].data
self.assertEqual(len(mesh.polygons), 2)
self.assertEqual(len(mesh.edges), 5)
self.assertEqual(len(mesh.vertices), 4)
self.assertEqual(len(mesh.polygons[0].vertices), 3)
self.assertIn("m_quad", objects, "Scene does not contain object m_quad")
mesh = objects["m_quad"].data
self.assertEqual(len(mesh.polygons), 1)
self.assertEqual(len(mesh.edges), 4)
self.assertEqual(len(mesh.vertices), 4)
self.assertEqual(len(mesh.polygons[0].vertices), 4)
self.assertIn("m_ngon_concave", objects, "Scene does not contain object m_ngon_concave")
mesh = objects["m_ngon_concave"].data
self.assertEqual(len(mesh.polygons), 1)
self.assertEqual(len(mesh.edges), 5)
self.assertEqual(len(mesh.vertices), 5)
self.assertEqual(len(mesh.polygons[0].vertices), 5)
self.assertIn("m_ngon_convex", objects, "Scene does not contain object m_ngon_convex")
mesh = objects["m_ngon_convex"].data
self.assertEqual(len(mesh.polygons), 1)
self.assertEqual(len(mesh.edges), 5)
self.assertEqual(len(mesh.vertices), 5)
self.assertEqual(len(mesh.polygons[0].vertices), 5)
def test_import_mesh_uv_maps(self):
"""Test importing meshes with udim UVs and multiple UV sets."""
infile = str(self.testdir / "usd_mesh_udim.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
objects = bpy.context.scene.collection.objects
if "preview" in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects["preview"])
self.assertEqual(1, len(objects), f"File {infile} should contain one object, found {len(objects)}")
mesh = bpy.data.objects["uvmap_plane"].data
self.assertEqual(len(mesh.uv_layers), 2, f"Object uvmap_plane should have two uv layers, found {len(mesh.uv_layers)}")
expected_layer_names = {"udim_map", "uvmap"}
imported_layer_names = set(mesh.uv_layers.keys())
self.assertEqual(expected_layer_names, imported_layer_names, f"Expected layer names ({expected_layer_names}) not found on uvmap_plane.")
def get_coords(data):
coords = [x.uv for x in uvmap]
return coords
def uv_min_max(data):
coords = get_coords(data)
uv_min_x = min([uv[0] for uv in coords])
uv_max_x = max([uv[0] for uv in coords])
uv_min_y = min([uv[1] for uv in coords])
uv_max_y = max([uv[1] for uv in coords])
return uv_min_x, uv_max_x, uv_min_y, uv_max_y
## Quick tests for point range.
uvmap = mesh.uv_layers["uvmap"].data
self.assertEqual(len(uvmap), 128)
min_x, max_x, min_y, max_y = uv_min_max(uvmap)
self.assertGreaterEqual(min_x, 0.0)
self.assertGreaterEqual(min_y, 0.0)
self.assertLessEqual(max_x, 1.0)
self.assertLessEqual(max_y, 1.0)
uvmap = mesh.uv_layers["udim_map"].data
self.assertEqual(len(uvmap), 128)
min_x, max_x, min_y, max_y = uv_min_max(uvmap)
self.assertGreaterEqual(min_x, 0.0)
self.assertGreaterEqual(min_y, 0.0)
self.assertLessEqual(max_x, 2.0)
self.assertLessEqual(max_y, 1.0)
## Make sure at least some points are in a udim tile.
coords = get_coords(uvmap)
coords = list(filter(lambda x: x[0] > 1.0, coords))
self.assertGreater(len(coords), 16)
def main():
global args