Cleanup: Use utility function to find groups in node tree #104465
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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('.'):
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -103,8 +103,8 @@ void BlendfileLoadingBaseTest::TearDownTestCase()
|
|||
void BlendfileLoadingBaseTest::TearDown()
|
||||
{
|
||||
BKE_mball_cubeTable_free();
|
||||
depsgraph_free();
|
||||
blendfile_free();
|
||||
depsgraph_free();
|
||||
|
||||
testing::Test::TearDown();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(), ¶ms, 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(), ¶ms, 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
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue