Curves: cage overlay for sculpt mode #104467

Merged
Jacques Lucke merged 44 commits from JacquesLucke/blender:sculpt-edit-overlay into main 2023-02-14 18:10:24 +01:00
38 changed files with 1087 additions and 292 deletions
Showing only changes of commit 97765a270a - Show all commits

View File

@ -4913,7 +4913,6 @@ def km_image_paint(params):
{"properties": [("data_path", 'image_paint_object.data.use_paint_mask')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.image_paint.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
("wm.context_menu_enum", {"type": 'E', "value": 'PRESS'},
{"properties": [("data_path", 'tool_settings.image_paint.brush.stroke_method')]}),
*_template_items_context_panel("VIEW3D_PT_paint_texture_context_menu", params.context_menu_event),
@ -4962,7 +4961,6 @@ def km_vertex_paint(params):
{"properties": [("data_path", 'vertex_paint_object.data.use_paint_mask')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.vertex_paint.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
("wm.context_menu_enum", {"type": 'E', "value": 'PRESS'},
{"properties": [("data_path", 'tool_settings.vertex_paint.brush.stroke_method')]}),
("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None),
@ -5108,11 +5106,11 @@ def km_sculpt(params):
{"properties": [("data_path", 'scene.tool_settings.sculpt.show_mask')]}),
# Dynamic topology
("sculpt.dynamic_topology_toggle", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
("sculpt.dyntopo_detail_size_edit", {"type": 'D', "value": 'PRESS', "shift": True}, None),
("sculpt.dyntopo_detail_size_edit", {"type": 'R', "value": 'PRESS'}, None),
("sculpt.set_detail_size", {"type": 'D', "value": 'PRESS', "shift": True, "alt": True}, None),
# Remesh
("object.voxel_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None),
("object.voxel_size_edit", {"type": 'R', "value": 'PRESS', "shift": True}, None),
("object.voxel_size_edit", {"type": 'R', "value": 'PRESS'}, None),
("object.quadriflow_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True, "alt": True}, None),
# Color
("sculpt.sample_color", {"type": 'S', "value": 'PRESS'}, None),
@ -5163,7 +5161,6 @@ def km_sculpt(params):
{"properties": [("data_path", 'tool_settings.sculpt.brush.stroke_method')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.sculpt.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_mask_edit_pie", {"type": 'A', "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_automasking_pie", {"type": 'A', "alt": True, "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_face_sets_edit_pie", {"type": 'W', "value": 'PRESS'}),
@ -5643,7 +5640,7 @@ def km_sculpt_curves(params):
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
*_template_paint_radial_control("curves_sculpt"),
*_template_items_select_actions(params, "curves.select_all"),
("sculpt_curves.min_distance_edit", {"type": 'R', "value": 'PRESS', "shift": True}, {}),
("sculpt_curves.min_distance_edit", {"type": 'R', "value": 'PRESS'}, {}),
("sculpt_curves.select_grow", {"type": 'A', "value": 'PRESS', "shift": True}, {}),
])
@ -6284,6 +6281,8 @@ def km_sculpt_expand_modal(_params):
("MOVE_TOGGLE", {"type": 'SPACE', "value": 'ANY', "any": True}, None),
*((e, {"type": NUMBERS_1[i], "value": 'PRESS', "any": True}, None) for i, e in enumerate(
("FALLOFF_GEODESICS", "FALLOFF_TOPOLOGY", "FALLOFF_TOPOLOGY_DIAGONALS", "FALLOFF_SPHERICAL"))),
*((e, {"type": "NUMPAD_%i" % (i+1), "value": 'PRESS', "any": True}, None) for i, e in enumerate(
("FALLOFF_GEODESICS", "FALLOFF_TOPOLOGY", "FALLOFF_TOPOLOGY_DIAGONALS", "FALLOFF_SPHERICAL"))),
("SNAP_TOGGLE", {"type": 'LEFT_CTRL', "value": 'ANY'}, None),
("SNAP_TOGGLE", {"type": 'RIGHT_CTRL', "value": 'ANY'}, None),
("LOOP_COUNT_INCREASE", {"type": 'W', "value": 'PRESS', "any": True, "repeat": True}, None),

View File

@ -3281,7 +3281,6 @@ def km_image_paint(params):
{"properties": [("data_path", 'image_paint_object.data.use_paint_mask')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.image_paint.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
*_template_items_context_panel("VIEW3D_PT_paint_texture_context_menu",
{"type": 'RIGHTMOUSE', "value": 'PRESS'}),
# Tools
@ -3332,7 +3331,6 @@ def km_vertex_paint(params):
{"properties": [("data_path", 'vertex_paint_object.data.use_paint_mask')]}),
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.vertex_paint.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None),
*_template_items_context_panel("VIEW3D_PT_paint_vertex_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),
# Tools

View File

@ -468,7 +468,7 @@ class TOPBAR_MT_file_import(Menu):
self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
if bpy.app.build_options.usd:
self.layout.operator(
"wm.usd_import", text="Universal Scene Description (.usd, .usdc, .usda)")
"wm.usd_import", text="Universal Scene Description (.usd*)")
if bpy.app.build_options.io_gpencil:
self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")

View File

@ -2067,40 +2067,6 @@ class VIEW3D_MT_select_sculpt_curves(Menu):
layout.operator("sculpt_curves.select_grow", text="Grow")
class VIEW3D_MT_angle_control(Menu):
bl_label = "Angle Control"
@classmethod
def poll(cls, context):
settings = UnifiedPaintPanel.paint_settings(context)
if not settings:
return False
brush = settings.brush
tex_slot = brush.texture_slot
return tex_slot.has_texture_angle and tex_slot.has_texture_angle_source
def draw(self, context):
layout = self.layout
settings = UnifiedPaintPanel.paint_settings(context)
brush = settings.brush
sculpt = (context.sculpt_object is not None)
tex_slot = brush.texture_slot
layout.prop(tex_slot, "use_rake", text="Rake")
if brush.brush_capabilities.has_random_texture_angle and tex_slot.has_random_texture_angle:
if sculpt:
if brush.sculpt_capabilities.has_random_texture_angle:
layout.prop(tex_slot, "use_random", text="Random")
else:
layout.prop(tex_slot, "use_random", text="Random")
class VIEW3D_MT_mesh_add(Menu):
bl_idname = "VIEW3D_MT_mesh_add"
bl_label = "Mesh"
@ -8041,7 +8007,6 @@ classes = (
VIEW3D_MT_select_paint_mask_vertex,
VIEW3D_MT_select_edit_curves,
VIEW3D_MT_select_sculpt_curves,
VIEW3D_MT_angle_control,
VIEW3D_MT_mesh_add,
VIEW3D_MT_curve_add,
VIEW3D_MT_surface_add,

View File

@ -366,7 +366,7 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file
}
#endif
#ifdef WITH_USD
if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc")) {
if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc;*.usdz")) {
cache_file->type = CACHEFILE_TYPE_USD;
cache_file->handle = USD_create_handle(bmain, filepath, &cache_file->object_paths);
BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX);

View File

@ -225,20 +225,26 @@ float BM_face_calc_area_with_mat3(const BMFace *f, const float mat3[3][3])
return len_v3(n) * 0.5f;
}
float BM_face_calc_area_uv(const BMFace *f, int cd_loop_uv_offset)
float BM_face_calc_area_uv_signed(const BMFace *f, int cd_loop_uv_offset)
{
/* inline 'area_poly_v2' logic, avoid creating a temp array */
const BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
/* The Trapezium Area Rule */
/* Green's theorem applied to area of a polygon.
* TODO: `cross` should be of type `double` to reduce rounding error. */
float cross = 0.0f;
do {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
const float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l_iter->next, cd_loop_uv_offset);
cross += (luv_next[0] - luv[0]) * (luv_next[1] + luv[1]);
} while ((l_iter = l_iter->next) != l_first);
return fabsf(cross * 0.5f);
return cross * 0.5f;
}
float BM_face_calc_area_uv(const BMFace *f, int cd_loop_uv_offset)
{
return fabsf(BM_face_calc_area_uv_signed(f, cd_loop_uv_offset));
}
float BM_face_calc_perimeter(const BMFace *f)

View File

@ -73,7 +73,12 @@ float BM_face_calc_area(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
float BM_face_calc_area_with_mat3(const BMFace *f, const float mat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* get the area of UV face
* Calculate the signed area of UV face.
*/
float BM_face_calc_area_uv_signed(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Calculate the area of UV face.
*/
float BM_face_calc_area_uv(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();

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

@ -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

@ -72,6 +72,23 @@ const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_usd_tex_import_mode_items[] = {
{USD_TEX_IMPORT_NONE, "IMPORT_NONE", 0, "None", "Don't import textures"},
{USD_TEX_IMPORT_PACK, "IMPORT_PACK", 0, "Packed", "Import textures as packed data"},
{USD_TEX_IMPORT_COPY, "IMPORT_COPY", 0, "Copy", "Copy files to Textures Directory"},
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_usd_tex_name_collision_mode_items[] = {
{USD_TEX_NAME_COLLISION_USE_EXISTING,
"USE_EXISTING",
0,
"Use Existing",
"If a file with the same name already exists, use that instead of copying"},
{USD_TEX_NAME_COLLISION_OVERWRITE, "OVERWRITE", 0, "Overwrite", "Overwrite existing files"},
{0, NULL, 0, NULL, NULL},
};
/* Stored in the wmOperator's customdata field to indicate it should run as a background job.
* This is set when the operator is invoked, and not set when it is only executed. */
enum { AS_BACKGROUND_JOB = 1 };
@ -405,6 +422,14 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool validate_meshes = false;
const bool use_instancing = false;
const eUSDTexImportMode import_textures_mode = RNA_enum_get(op->ptr, "import_textures_mode");
char import_textures_dir[FILE_MAXDIR];
RNA_string_get(op->ptr, "import_textures_dir", import_textures_dir);
const eUSDTexNameCollisionMode tex_name_collision_mode = RNA_enum_get(op->ptr,
"tex_name_collision_mode");
struct USDImportParams params = {.scale = scale,
.is_sequence = is_sequence,
.set_frame_range = set_frame_range,
@ -430,9 +455,12 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.set_material_blend = set_material_blend,
.light_intensity_scale = light_intensity_scale,
.mtl_name_collision_mode = mtl_name_collision_mode,
.import_textures_mode = import_textures_mode,
.tex_name_collision_mode = tex_name_collision_mode,
.import_all_materials = import_all_materials};
STRNCPY(params.prim_path_mask, prim_path_mask);
STRNCPY(params.import_textures_dir, import_textures_dir);
const bool ok = USD_import(C, filename, &params, as_background_job);
@ -490,6 +518,18 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(row, ptr, "set_material_blend", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, RNA_boolean_get(ptr, "import_usd_preview"));
uiItemR(col, ptr, "mtl_name_collision_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "import_textures_mode", 0, NULL, ICON_NONE);
bool copy_textures = RNA_enum_get(op->ptr, "import_textures_mode") == USD_TEX_IMPORT_COPY;
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "import_textures_dir", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, copy_textures);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "tex_name_collision_mode", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, copy_textures);
uiLayoutSetEnabled(col, RNA_boolean_get(ptr, "import_materials"));
}
void WM_OT_usd_import(struct wmOperatorType *ot)
@ -622,6 +662,28 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
USD_MTL_NAME_COLLISION_MAKE_UNIQUE,
"Material Name Collision",
"Behavior when the name of an imported material conflicts with an existing material");
RNA_def_enum(ot->srna,
"import_textures_mode",
rna_enum_usd_tex_import_mode_items,
USD_TEX_IMPORT_PACK,
"Import Textures",
"Behavior when importing textures from a USDZ archive");
RNA_def_string(ot->srna,
"import_textures_dir",
"//textures/",
FILE_MAXDIR,
"Textures Directory",
"Path to the directory where imported textures will be copied ");
RNA_def_enum(
ot->srna,
"tex_name_collision_mode",
rna_enum_usd_tex_name_collision_mode_items,
USD_TEX_NAME_COLLISION_USE_EXISTING,
"File Name Collision",
"Behavior when the name of an imported texture file conflicts with an existing file");
}
#endif /* WITH_USD */

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

@ -388,11 +388,11 @@ static BLI_bitmap *sculpt_expand_boundary_from_enabled(SculptSession *ss,
return boundary_verts;
}
static void sculpt_expand_check_topology_islands(Object *ob)
static void sculpt_expand_check_topology_islands(Object *ob, eSculptExpandFalloffType falloff_type)
{
SculptSession *ss = ob->sculpt;
ss->expand_cache->check_islands = ELEM(ss->expand_cache->falloff_type,
ss->expand_cache->check_islands = ELEM(falloff_type,
SCULPT_EXPAND_FALLOFF_GEODESIC,
SCULPT_EXPAND_FALLOFF_TOPOLOGY,
SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS,
@ -1865,8 +1865,7 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event
return OPERATOR_FINISHED;
}
case SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC: {
expand_cache->falloff_gradient = true;
sculpt_expand_check_topology_islands(ob);
sculpt_expand_check_topology_islands(ob, SCULPT_EXPAND_FALLOFF_GEODESIC);
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
@ -1877,8 +1876,7 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event
break;
}
case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY: {
expand_cache->falloff_gradient = SCULPT_EXPAND_FALLOFF_TOPOLOGY;
sculpt_expand_check_topology_islands(ob);
sculpt_expand_check_topology_islands(ob, SCULPT_EXPAND_FALLOFF_TOPOLOGY);
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
@ -1889,8 +1887,7 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event
break;
}
case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS: {
expand_cache->falloff_gradient = true;
sculpt_expand_check_topology_islands(ob);
sculpt_expand_check_topology_islands(ob, SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS);
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
@ -2247,7 +2244,7 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
ss->expand_cache, sd, ob, ss->expand_cache->initial_active_vertex, falloff_type);
sculpt_expand_check_topology_islands(ob);
sculpt_expand_check_topology_islands(ob, falloff_type);
/* Initial mesh data update, resets all target data in the sculpt mesh. */
sculpt_expand_update_for_vertex(C, ob, ss->expand_cache->initial_active_vertex);

View File

@ -2667,7 +2667,7 @@ int ED_path_extension_type(const char *path)
if (BLI_path_extension_check(path, ".abc")) {
return FILE_TYPE_ALEMBIC;
}
if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", nullptr)) {
if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", ".usdz", nullptr)) {
return FILE_TYPE_USD;
}
if (BLI_path_extension_check(path, ".vdb")) {

View File

@ -405,7 +405,10 @@ static void image_listener(const wmSpaceTypeListenerParams *params)
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob && (ob == wmn->reference) && (ob->mode & OB_MODE_EDIT)) {
/* \note With a geometry nodes modifier, the UVs on `ob` can change in response to
* any change on `wmn->reference`. If we could track the upstream dependencies,
* unnecessary redraws could be reduced. Until then, just redraw. See T98594. */
if (ob && (ob->mode & OB_MODE_EDIT)) {
if (sima->lock && (sima->flag & SI_DRAWSHADOW)) {
ED_area_tag_refresh(area);
ED_area_tag_redraw(area);

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

@ -1001,6 +1001,9 @@ void postSelectConstraint(TransInfo *t)
static void setNearestAxis2d(TransInfo *t)
{
/* Clear any prior constraint flags. */
t->con.mode &= ~(CON_AXIS0 | CON_AXIS1 | CON_AXIS2);
/* no correction needed... just use whichever one is lower */
if (abs(t->mval[0] - t->con.imval[0]) < abs(t->mval[1] - t->con.imval[1])) {
t->con.mode |= CON_AXIS1;
@ -1014,6 +1017,9 @@ static void setNearestAxis2d(TransInfo *t)
static void setNearestAxis3d(TransInfo *t)
{
/* Clear any prior constraint flags. */
t->con.mode &= ~(CON_AXIS0 | CON_AXIS1 | CON_AXIS2);
float zfac;
float mvec[3], proj[3];
float len[3];
@ -1090,10 +1096,7 @@ static void setNearestAxis3d(TransInfo *t)
void setNearestAxis(TransInfo *t)
{
/* clear any prior constraint flags */
t->con.mode &= ~CON_AXIS0;
t->con.mode &= ~CON_AXIS1;
t->con.mode &= ~CON_AXIS2;
eTConstraint mode_prev = t->con.mode;
/* constraint setting - depends on spacetype */
if (t->spacetype == SPACE_VIEW3D) {
@ -1105,7 +1108,9 @@ void setNearestAxis(TransInfo *t)
setNearestAxis2d(t);
}
projection_matrix_calc(t, t->con.pmtx);
if (mode_prev != t->con.mode) {
projection_matrix_calc(t, t->con.pmtx);
}
}
/** \} */

View File

@ -440,6 +440,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
{
short orient_types[3];
short orient_type_apply = O_DEFAULT;
float custom_matrix[3][3];
int orient_type_scene = V3D_ORIENT_GLOBAL;
@ -502,14 +503,23 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->is_orient_default_overwrite = true;
}
}
else if (t->con.mode & CON_APPLY) {
orient_type_set = orient_type_scene;
}
else if (orient_type_scene == V3D_ORIENT_GLOBAL) {
orient_type_set = V3D_ORIENT_LOCAL;
if (orient_type_set == -1) {
if (orient_type_scene == V3D_ORIENT_GLOBAL) {
orient_type_set = V3D_ORIENT_LOCAL;
}
else {
orient_type_set = V3D_ORIENT_GLOBAL;
}
if (t->con.mode & CON_APPLY) {
orient_type_apply = O_SCENE;
}
}
else {
orient_type_set = V3D_ORIENT_GLOBAL;
if (t->con.mode & CON_APPLY) {
orient_type_apply = O_SET;
}
}
BLI_assert(!ELEM(-1, orient_type_default, orient_type_set));
@ -546,7 +556,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
}
}
transform_orientations_current_set(t, (t->con.mode & CON_APPLY) ? 2 : 0);
transform_orientations_current_set(t, orient_type_apply);
}
if (op && ((prop = RNA_struct_find_property(op->ptr, "release_confirm")) &&

View File

@ -604,8 +604,11 @@ static void gizmo2d_xform_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
UI_view2d_view_to_region_m4(&region->v2d, ggd->cage->matrix_space);
/* Define the bounding box of the gizmo in the offset transform matrix. */
unit_m4(ggd->cage->matrix_offset);
ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]);
ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]);
const float min_gizmo_pixel_size = 0.001f; /* Draw Gizmo larger than this many pixels. */
const float min_scale_axis_x = min_gizmo_pixel_size / ggd->cage->matrix_space[0][0];
const float min_scale_axis_y = min_gizmo_pixel_size / ggd->cage->matrix_space[1][1];
ggd->cage->matrix_offset[0][0] = max_ff(min_scale_axis_x, ggd->max[0] - ggd->min[0]);
ggd->cage->matrix_offset[1][1] = max_ff(min_scale_axis_y, ggd->max[1] - ggd->min[1]);
ScrArea *area = CTX_wm_area(C);

View File

@ -87,9 +87,11 @@ typedef enum {
UV_SSIM_FACE,
UV_SSIM_LENGTH_UV,
UV_SSIM_LENGTH_3D,
UV_SSIM_SIDES,
UV_SSIM_PIN,
UV_SSIM_MATERIAL,
UV_SSIM_OBJECT,
UV_SSIM_PIN,
UV_SSIM_SIDES,
UV_SSIM_WINDING,
} eUVSelectSimilar;
/* -------------------------------------------------------------------- */
@ -4633,6 +4635,7 @@ static float get_uv_edge_needle(const eUVSelectSimilar type,
static float get_uv_face_needle(const eUVSelectSimilar type,
BMFace *face,
int ob_index,
const float ob_m3[3][3],
const BMUVOffsets offsets)
{
@ -4646,6 +4649,8 @@ static float get_uv_face_needle(const eUVSelectSimilar type,
return BM_face_calc_area_with_mat3(face, ob_m3);
case UV_SSIM_SIDES:
return face->len;
case UV_SSIM_OBJECT:
return ob_index;
case UV_SSIM_PIN: {
BMLoop *l;
BMIter liter;
@ -4657,6 +4662,8 @@ static float get_uv_face_needle(const eUVSelectSimilar type,
} break;
case UV_SSIM_MATERIAL:
return face->mat_nr;
case UV_SSIM_WINDING:
return signum_i(BM_face_calc_area_uv_signed(face, offsets.uv));
default:
BLI_assert_unreachable();
return false;
@ -4966,7 +4973,7 @@ static int uv_select_similar_face_exec(bContext *C, wmOperator *op)
continue;
}
float needle = get_uv_face_needle(type, face, ob_m3, offsets);
float needle = get_uv_face_needle(type, face, ob_index, ob_m3, offsets);
if (tree_1d) {
BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle);
}
@ -5000,7 +5007,7 @@ static int uv_select_similar_face_exec(bContext *C, wmOperator *op)
continue;
}
float needle = get_uv_face_needle(type, face, ob_m3, offsets);
float needle = get_uv_face_needle(type, face, ob_index, ob_m3, offsets);
bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare);
if (select) {
@ -5180,8 +5187,10 @@ static EnumPropertyItem prop_edge_similar_types[] = {
static EnumPropertyItem prop_face_similar_types[] = {
{UV_SSIM_AREA_UV, "AREA", 0, "Area", ""},
{UV_SSIM_AREA_3D, "AREA_3D", 0, "Area 3D", ""},
{UV_SSIM_SIDES, "SIDES", 0, "Polygon Sides", ""},
{UV_SSIM_MATERIAL, "MATERIAL", 0, "Material", ""},
{UV_SSIM_OBJECT, "OBJECT", 0, "Object", ""},
{UV_SSIM_SIDES, "SIDES", 0, "Polygon Sides", ""},
{UV_SSIM_WINDING, "WINDING", 0, "Winding", ""},
{0}};
static EnumPropertyItem prop_island_similar_types[] = {

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

@ -8,8 +8,13 @@
#include <array>
#include <type_traits>
#include "BLI_float4x4.hh"
#include "BLI_math.h"
#include "BLI_math_color_blend.h"
#include "BLI_math_vector.hh"
#include "BLI_rect.h"
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@ -22,19 +27,31 @@ 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 {
/**
* Contains per sub-sample a delta to be added to the uv of the source image buffer.
*/
Vector<double2, 9> delta_uvs;
} subsampling;
struct {
IndexRange x_range;
IndexRange y_range;
} destination_region;
/**
* \brief Cropping region in source image pixel space.
@ -44,55 +61,97 @@ 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,
const bool do_crop_destination_region)
{
init_start_uv(transform_matrix);
init_add_x(transform_matrix);
init_add_y(transform_matrix);
init_subsampling(num_subsamples);
init_destination_region(transform_matrix, do_crop_destination_region);
}
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)
{
double2 subsample_add_x = add_x / num_subsamples;
double2 subsample_add_y = add_y / num_subsamples;
double2 offset_x = -add_x * 0.5 + subsample_add_x * 0.5;
double2 offset_y = -add_y * 0.5 + subsample_add_y * 0.5;
for (int y : IndexRange(0, num_subsamples)) {
for (int x : IndexRange(0, num_subsamples)) {
double2 delta_uv = -offset_x - offset_y;
delta_uv += x * subsample_add_x;
delta_uv += y * subsample_add_y;
subsampling.delta_uvs.append(delta_uv);
}
}
}
void init_destination_region(const float4x4 &transform_matrix,
const bool do_crop_destination_region)
{
if (!do_crop_destination_region) {
destination_region.x_range = IndexRange(dst->x);
destination_region.y_range = IndexRange(dst->y);
return;
}
/* Transform the src_crop to the destination buffer with a margin.*/
const int2 margin(2);
rcti rect;
BLI_rcti_init_minmax(&rect);
float4x4 inverse = transform_matrix.inverted();
for (const int2 &src_coords : {
int2(src_crop.xmin, src_crop.ymin),
int2(src_crop.xmax, src_crop.ymin),
int2(src_crop.xmin, src_crop.ymax),
int2(src_crop.xmax, src_crop.ymax),
}) {
float3 dst_co = inverse * float3(src_coords.x, src_coords.y, 0.0f);
BLI_rcti_do_minmax_v(&rect, int2(dst_co) + margin);
BLI_rcti_do_minmax_v(&rect, int2(dst_co) - margin);
}
/* Clamp rect to fit inside the image buffer.*/
rcti dest_rect;
BLI_rcti_init(&dest_rect, 0, dst->x, 0, dst->y);
BLI_rcti_isect(&rect, &dest_rect, &rect);
destination_region.x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect));
destination_region.y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect));
}
};
@ -110,7 +169,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 +182,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 +199,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 +227,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 +274,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, const double2 &uv)
{
return double2(modify_u(source_buffer, uv.x), modify_v(source_buffer, uv.y));
}
};
/**
@ -259,6 +327,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 +389,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 +415,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, wrapped_uv, r_sample);
}
else {
/* Unsupported sampler. */
@ -346,16 +440,13 @@ class Sampler {
}
private:
void sample_nearest_float(const ImBuf *source,
const double u,
const double v,
SampleType &r_sample)
void sample_nearest_float(const ImBuf *source, const double2 &uv, SampleType &r_sample)
{
BLI_STATIC_ASSERT(std::is_same_v<StorageType, float>);
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
int x1 = int(u);
int y1 = int(v);
int x1 = int(uv.x);
int y1 = int(uv.y);
/* Break when sample outside image is requested. */
if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) {
@ -387,12 +478,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 +513,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 +560,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,
@ -465,20 +574,66 @@ class ScanlineProcessor {
*/
void process(const TransformUserData *user_data, int scanline)
{
const int width = user_data->dst->x;
if (user_data->subsampling.delta_uvs.size() > 1) {
process_with_subsampling(user_data, scanline);
}
else {
process_one_sample_per_pixel(user_data, scanline);
}
}
double uv[2];
madd_v2_v2db_db(uv, user_data->start_uv, user_data->add_y, scanline);
private:
void process_one_sample_per_pixel(const TransformUserData *user_data, int scanline)
{
double2 uv = user_data->start_uv +
user_data->destination_region.x_range.first() * user_data->add_x +
user_data->add_y * scanline;
output.init_pixel_pointer(user_data->dst, 0, scanline);
for (int xi = 0; xi < width; xi++) {
output.init_pixel_pointer(user_data->dst,
int2(user_data->destination_region.x_range.first(), scanline));
for (int xi : user_data->destination_region.x_range) {
UNUSED_VARS(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)
{
double2 uv = user_data->start_uv +
user_data->destination_region.x_range.first() * user_data->add_x +
user_data->add_y * scanline;
output.init_pixel_pointer(user_data->dst,
int2(user_data->destination_region.x_range.first(), scanline));
for (int xi : user_data->destination_region.x_range) {
UNUSED_VARS(xi);
typename Sampler::SampleType sample;
sample.clear();
int num_subsamples_added = 0;
for (const double2 &delta_uv : user_data->subsampling.delta_uvs) {
const double2 subsample_uv = uv + delta_uv;
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;
}
}
if (num_subsamples_added != 0) {
const float mix_weight = float(num_subsamples_added) /
user_data->subsampling.delta_uvs.size();
channel_converter.mix_and_store(sample, output, mix_weight);
}
uv += user_data->add_x;
output.increase_pixel_pointer();
}
}
@ -559,7 +714,11 @@ static void transform_threaded(TransformUserData *user_data, const eIMBTransform
}
if (scanline_func != nullptr) {
IMB_processor_apply_threaded_scanlines(user_data->dst->y, scanline_func, user_data);
threading::parallel_for(user_data->destination_region.y_range, 8, [&](IndexRange range) {
for (int scanline : range) {
scanline_func(user_data, scanline);
}
});
}
}
@ -573,6 +732,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 +746,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, ELEM(mode, IMB_TRANSFORM_MODE_CROP_SRC));
if (filter == IMB_FILTER_NEAREST) {
transform_threaded<IMB_FILTER_NEAREST>(&user_data, mode);

View File

@ -60,6 +60,7 @@ set(INC_SYS
)
set(SRC
intern/usd_asset_utils.cc
intern/usd_capi_export.cc
intern/usd_capi_import.cc
intern/usd_common.cc
@ -88,6 +89,7 @@ set(SRC
usd.h
intern/usd_asset_utils.h
intern/usd_common.h
intern/usd_exporter_context.h
intern/usd_hierarchy_iterator.h

View File

@ -0,0 +1,301 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 NVIDIA Corportation. All rights reserved. */
#include "usd_asset_utils.h"
#include <pxr/usd/ar/asset.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/ar/writableAsset.h>
#include "BKE_main.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "WM_api.h"
#include "WM_types.h"
static const char UDIM_PATTERN[] = "<UDIM>";
static const char UDIM_PATTERN2[] = "%3CUDIM%3E";
/* Maximum range of UDIM tiles, per the
* UsdPreviewSurface specifications. See
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
*/
static const int UDIM_START_TILE = 1001;
static const int UDIM_END_TILE = 1100;
namespace blender::io::usd {
/* The following is copied from _SplitUdimPattern() in
* USD library source file materialParamsUtils.cpp.
* Split a udim file path such as /someDir/myFile.<UDIM>.exr into a
* prefix (/someDir/myFile.) and suffix (.exr). */
static std::pair<std::string, std::string> split_udim_pattern(const std::string &path)
{
static const std::vector<std::string> patterns = {UDIM_PATTERN, UDIM_PATTERN2};
for (const std::string &pattern : patterns) {
const std::string::size_type pos = path.find(pattern);
if (pos != std::string::npos) {
return {path.substr(0, pos), path.substr(pos + pattern.size())};
}
}
return {std::string(), std::string()};
}
/* Return the asset file base name, with special handling of
* package relative paths. */
static std::string get_asset_base_name(const char *src_path)
{
char base_name[FILE_MAXFILE];
if (pxr::ArIsPackageRelativePath(src_path)) {
std::pair<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path);
if (split.second.empty()) {
WM_reportf(RPT_WARNING,
"%s: Couldn't determine package-relative file name from path %s",
__func__,
src_path);
return src_path;
}
BLI_split_file_part(split.second.c_str(), base_name, sizeof(base_name));
}
else {
BLI_split_file_part(src_path, base_name, sizeof(base_name));
}
return base_name;
}
/* Copy an asset to a destination directory. */
static std::string copy_asset_to_directory(const char *src_path,
const char *dest_dir_path,
eUSDTexNameCollisionMode name_collision_mode)
{
std::string base_name = get_asset_base_name(src_path);
char dest_file_path[FILE_MAX];
BLI_path_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str());
BLI_path_normalize(NULL, dest_file_path);
if (name_collision_mode == USD_TEX_NAME_COLLISION_USE_EXISTING && BLI_is_file(dest_file_path)) {
return dest_file_path;
}
if (!copy_asset(src_path, dest_file_path, name_collision_mode)) {
WM_reportf(
RPT_WARNING, "%s: Couldn't copy file %s to %s.", __func__, src_path, dest_file_path);
return src_path;
}
return dest_file_path;
}
static std::string copy_udim_asset_to_directory(const char *src_path,
const char *dest_dir_path,
eUSDTexNameCollisionMode name_collision_mode)
{
/* Get prefix and suffix from udim pattern. */
std::pair<std::string, std::string> splitPath = split_udim_pattern(src_path);
if (splitPath.first.empty() || splitPath.second.empty()) {
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path);
return src_path;
}
/* Copy the individual UDIM tiles. Since there is currently no way to query the contents
* of a directory using the USD resolver, we must take a brute force approach. We iterate
* over the allowed range of tile indices and copy any tiles that exist. The USDPreviewSurface
* specification stipulates "a maximum of ten tiles in the U direction" and that
* "the tiles must be within the range [1001, 1099]". See
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
*/
for (int i = UDIM_START_TILE; i < UDIM_END_TILE; ++i) {
const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second;
if (asset_exists(src_udim.c_str())) {
copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode);
}
}
const std::string src_file_name = get_asset_base_name(src_path);
char ret_udim_path[FILE_MAX];
BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str());
/* Blender only recognizes the <UDIM> pattern, not the
* alternative UDIM_PATTERN2, so we make sure the returned
* path has the former. */
splitPath = split_udim_pattern(ret_udim_path);
if (splitPath.first.empty() || splitPath.second.empty()) {
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path);
return ret_udim_path;
}
return splitPath.first + UDIM_PATTERN + splitPath.second;
}
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode)
{
if (!(src && dst)) {
return false;
}
pxr::ArResolver &ar = pxr::ArGetResolver();
if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) {
if (!ar.Resolve(dst).IsEmpty()) {
/* The asset exists, so this is a no-op. */
WM_reportf(RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst);
return true;
}
}
pxr::ArResolvedPath src_path = ar.Resolve(src);
if (src_path.IsEmpty()) {
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s", __func__, src);
return false;
}
pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst);
if (dst_path.IsEmpty()) {
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst);
return false;
}
if (src_path == dst_path) {
WM_reportf(RPT_ERROR,
"%s: Can't copy %s. The source and destination paths are the same",
__func__,
src_path.GetPathString().c_str());
return false;
}
std::string why_not;
if (!ar.CanWriteAssetToPath(dst_path, &why_not)) {
WM_reportf(RPT_ERROR,
"%s: Can't write to asset %s. %s.",
__func__,
dst_path.GetPathString().c_str(),
why_not.c_str());
return false;
}
std::shared_ptr<pxr::ArAsset> src_asset = ar.OpenAsset(src_path);
if (!src_asset) {
WM_reportf(
RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str());
return false;
}
const size_t size = src_asset->GetSize();
if (size == 0) {
WM_reportf(RPT_WARNING,
"%s: Will not copy zero size source asset %s",
__func__,
src_path.GetPathString().c_str());
return false;
}
std::shared_ptr<const char> buf = src_asset->GetBuffer();
if (!buf) {
WM_reportf(RPT_ERROR,
"%s: Null buffer for source asset %s",
__func__,
src_path.GetPathString().c_str());
return false;
}
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
dst_path, pxr::ArResolver::WriteMode::Replace);
if (!dst_asset) {
WM_reportf(RPT_ERROR,
"%s: Can't open destination asset %s for writing",
__func__,
src_path.GetPathString().c_str());
return false;
}
size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0);
if (bytes_written == 0) {
WM_reportf(RPT_ERROR,
"%s: Error writing to destination asset %s",
__func__,
dst_path.GetPathString().c_str());
}
if (!dst_asset->Close()) {
WM_reportf(RPT_ERROR,
"%s: Couldn't close destination asset %s",
__func__,
dst_path.GetPathString().c_str());
return false;
}
return bytes_written > 0;
}
bool asset_exists(const char *path)
{
return path && !pxr::ArGetResolver().Resolve(path).IsEmpty();
}
std::string import_asset(const char *src,
const char *import_dir,
eUSDTexNameCollisionMode name_collision_mode)
{
if (import_dir[0] == '\0') {
WM_reportf(
RPT_ERROR, "%s: Texture import directory path empty, couldn't import %s", __func__, src);
return src;
}
char dest_dir_path[FILE_MAXDIR];
STRNCPY(dest_dir_path, import_dir);
const char *basepath = nullptr;
if (BLI_path_is_rel(import_dir)) {
basepath = BKE_main_blendfile_path_from_global();
if (!basepath || basepath[0] == '\0') {
WM_reportf(RPT_ERROR,
"%s: import directory is relative "
"but the blend file path is empty. "
"Please save the blend file before importing the USD "
"or provide an absolute import directory path. "
"Can't import %s",
__func__,
src);
return src;
}
}
BLI_path_normalize(basepath, dest_dir_path);
if (!BLI_dir_create_recursive(dest_dir_path)) {
WM_reportf(
RPT_ERROR, "%s: Couldn't create texture import directory %s", __func__, dest_dir_path);
return src;
}
if (is_udim_path(src)) {
return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode);
}
return copy_asset_to_directory(src, dest_dir_path, name_collision_mode);
}
bool is_udim_path(const std::string &path)
{
return path.find(UDIM_PATTERN) != std::string::npos ||
path.find(UDIM_PATTERN2) != std::string::npos;
}
} // namespace blender::io::usd

View File

@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 NVIDIA Corporation. All rights reserved. */
#pragma once
#include "usd.h"
#include <pxr/usd/usd/stage.h>
#include <string>
namespace blender::io::usd {
/**
* Invoke the USD asset resolver to copy an asset.
*
* \param src: source path of the asset to copy
* \param dst: destination path of the copy
* \param name_collision_mode: behavior when `dst` already exists
* \return true if the copy succeeded, false otherwise
*/
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode);
/**
* Invoke the USD asset resolver to determine if the
* asset with the given path exists.
*
* \param path: the path to resolve
* \return true if the asset exists, false otherwise
*/
bool asset_exists(const char *path);
/**
* Invoke the USD asset resolver to copy an asset to a destination
* directory and return the path to the copied file. This function may
* be used to copy textures from a USDZ archive to a directory on disk.
* The destination directory will be created if it doesn't already exist.
* If the copy was unsuccessful, this function will log an error and
* return the original source file path unmodified.
*
* \param src: source path of the asset to import
* \param import_dir: path to the destination directory
* \param name_collision_mode: behavior when a file of the same name already exists
* \return path to copied file or the original `src` path if there was an error
*/
std::string import_asset(const char *src,
const char *import_dir,
eUSDTexNameCollisionMode name_collision_mode);
/**
* Check if the given path contains a UDIM token.
*
* \param path: the path to check
* \return true if the path contains a UDIM token, false otherwise
*/
bool is_udim_path(const std::string &path);
} // namespace blender::io::usd

View File

@ -3,6 +3,9 @@
#include "usd_reader_material.h"
#include "usd_asset_utils.h"
#include "BKE_appdir.h"
#include "BKE_image.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
@ -19,6 +22,7 @@
#include "DNA_material_types.h"
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/shader.h>
@ -63,6 +67,23 @@ static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Temporary folder for saving imported textures prior to packing.
* CAUTION: this directory is recursively deleted after material
* import. */
static const char *temp_textures_dir()
{
static bool inited = false;
static char temp_dir[FILE_MAXDIR] = {'\0'};
if (!inited) {
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
inited = true;
}
return temp_dir;
}
/* Add a node of the given type at the given location coordinates. */
static bNode *add_node(
const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy)
@ -112,11 +133,6 @@ static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &attribute)
return pxr::SdfLayerHandle();
}
static bool is_udim_path(const std::string &path)
{
return path.find("<UDIM>") != std::string::npos;
}
/* For the given UDIM path (assumed to contain the UDIM token), returns an array
* containing valid tile indices. */
static blender::Vector<int> get_udim_tiles(const std::string &file_path)
@ -676,6 +692,26 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
return;
}
/* Optionally copy the asset if it's inside a USDZ package. */
const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE &&
pxr::ArIsPackageRelativePath(file_path);
if (import_textures) {
/* If we are packing the imported textures, we first write them
* to a temporary directory. */
const char *textures_dir = params_.import_textures_mode == USD_TEX_IMPORT_PACK ?
temp_textures_dir() :
params_.import_textures_dir;
const eUSDTexNameCollisionMode name_collision_mode = params_.import_textures_mode ==
USD_TEX_IMPORT_PACK ?
USD_TEX_NAME_COLLISION_OVERWRITE :
params_.tex_name_collision_mode;
file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode);
}
/* If this is a UDIM texture, this will store the
* UDIM tile indices. */
blender::Vector<int> udim_tiles;
@ -712,6 +748,14 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
STRNCPY(image->colorspace_settings.name, "Raw");
}
if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image)) {
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain_, &image->id));
if (BLI_is_dir(temp_textures_dir())) {
BLI_delete(temp_textures_dir(), true, true);
}
}
}
void USDMaterialReader::convert_usd_primvar_reader_float2(

View File

@ -355,7 +355,8 @@ void USDStageReader::fake_users_for_unused_materials()
{
/* Iterate over the imported materials and set a fake user for any unused
* materials. */
for (const std::pair<std::string, std::string> &path_mat_pair : settings_.usd_path_to_mat_name) {
for (const std::pair<const std::string, std::string> &path_mat_pair :
settings_.usd_path_to_mat_name) {
std::map<std::string, Material *>::iterator mat_it = settings_.mat_name_to_mat.find(
path_mat_pair.second);

View File

@ -25,8 +25,8 @@
#include "BKE_node.h"
#include "BLI_fileops.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_math_vector_types.hh"
#include "BLI_path_util.h"
#include "BLO_readfile.h"
#include "BKE_node_runtime.hh"
@ -45,11 +45,9 @@ 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;
@ -104,7 +102,9 @@ class UsdExportTest : public BlendfileLoadingBaseTest {
* 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) {
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));
@ -155,7 +155,9 @@ class UsdExportTest : public BlendfileLoadingBaseTest {
}
}
const void compare_blender_image_to_usd_image_shader(const bNode *image_node, const pxr::UsdPrim& image_prim) {
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);
@ -171,14 +173,16 @@ class UsdExportTest : public BlendfileLoadingBaseTest {
/* 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);
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) {
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;
@ -196,10 +200,8 @@ class UsdExportTest : public BlendfileLoadingBaseTest {
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)) {
@ -239,8 +241,8 @@ TEST_F(UsdExportTest, usd_export_rain_mesh)
}
}
static const bNode *find_node_for_type_in_graph(const bNodeTree *nodetree, const blender::StringRefNull type_idname)
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) {
@ -250,7 +252,6 @@ static const bNode *find_node_for_type_in_graph(const bNodeTree *nodetree, const
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
@ -269,7 +270,8 @@ TEST_F(UsdExportTest, usd_export_material)
/* 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"));
Material *material = reinterpret_cast<Material *>(
BKE_libblock_find_name(bfile->main, ID_MA, "Material"));
EXPECT_TRUE(bool(material));
@ -300,13 +302,13 @@ TEST_F(UsdExportTest, usd_export_material)
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;
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);
}

View File

@ -21,6 +21,21 @@ typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_REFERENCE_EXISTING = 1,
} eUSDMtlNameCollisionMode;
/* Behavior when importing textures from a package
* (e.g., USDZ archive) or from a URI path. */
typedef enum eUSDTexImportMode {
USD_TEX_IMPORT_NONE = 0,
USD_TEX_IMPORT_PACK,
USD_TEX_IMPORT_COPY,
} eUSDTexImportMode;
/* Behavior when the name of an imported texture
* file conflicts with an existing file. */
typedef enum eUSDTexNameCollisionMode {
USD_TEX_NAME_COLLISION_USE_EXISTING = 0,
USD_TEX_NAME_COLLISION_OVERWRITE = 1,
} eUSDTexNameCollisionMode;
struct USDExportParams {
bool export_animation;
bool export_hair;
@ -64,6 +79,9 @@ struct USDImportParams {
bool set_material_blend;
float light_intensity_scale;
eUSDMtlNameCollisionMode mtl_name_collision_mode;
eUSDTexImportMode import_textures_mode;
char import_textures_dir[768]; /* FILE_MAXDIR */
eUSDTexNameCollisionMode tex_name_collision_mode;
bool import_all_materials;
};

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

@ -968,7 +968,7 @@ static const EnumPropertyItem *rna_Brush_stroke_itemf(bContext *C,
/* Grease Pencil Drawing Brushes Settings */
static char *rna_BrushGpencilSettings_path(const PointerRNA *UNUSED(ptr))
{
return BLI_strdup("tool_settings.gpencil_paint.brush.gpencil_settings");
return BLI_strdup("gpencil_settings");
}
static void rna_BrushGpencilSettings_default_eraser_update(Main *bmain,
@ -1123,6 +1123,11 @@ static void rna_Brush_automasking_cavity_set(PointerRNA *ptr, bool val)
}
}
static char *rna_BrushCurvesSculptSettings_path(const PointerRNA *UNUSED(ptr))
{
return BLI_strdup("curves_sculpt_settings");
}
#else
static void rna_def_brush_texture_slot(BlenderRNA *brna)
@ -2037,6 +2042,7 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
};
srna = RNA_def_struct(brna, "BrushCurvesSculptSettings", NULL);
RNA_def_struct_path_func(srna, "rna_BrushCurvesSculptSettings_path");
RNA_def_struct_sdna(srna, "BrushCurvesSculptSettings");
RNA_def_struct_ui_text(srna, "Curves Sculpt Brush Settings", "");

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

@ -25,6 +25,7 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Int>(N_("Count"))
.default_value(10)
.min(2)
.field_on_all()
.max(100000)
.make_available(
[](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT; });

View File

@ -49,6 +49,8 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput {
const eAttrDomain domain,
const IndexMask mask) const final
{
const OffsetIndices points_by_curve = curves.points_by_curve();
const bke::CurvesFieldContext context{curves, domain};
fn::FieldEvaluator evaluator{context, &mask};
evaluator.add(curve_index_);
@ -62,7 +64,7 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput {
point_evaluator.add(sort_weight_);
point_evaluator.evaluate();
const VArray<float> all_sort_weights = point_evaluator.get_evaluated<float>(0);
const OffsetIndices points_by_curve = curves.points_by_curve();
const bool use_sorting = !all_sort_weights.is_single();
Array<int> point_of_curve(mask.min_array_size());
threading::parallel_for(mask.index_range(), 256, [&](const IndexRange range) {
@ -77,25 +79,29 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput {
point_of_curve[selection_i] = 0;
continue;
}
const IndexRange points = points_by_curve[curve_i];
/* Retrieve the weights for each point. */
sort_weights.reinitialize(points.size());
all_sort_weights.materialize_compressed(IndexMask(points), sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(points.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
const int index_in_sort_wrapped = mod_i(index_in_sort, points.size());
point_of_curve[selection_i] = points[sort_indices[index_in_sort_wrapped]];
if (use_sorting) {
/* Retrieve the weights for each point. */
sort_weights.reinitialize(points.size());
all_sort_weights.materialize_compressed(IndexMask(points),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(points.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
point_of_curve[selection_i] = points[sort_indices[index_in_sort_wrapped]];
}
else {
point_of_curve[selection_i] = points[index_in_sort_wrapped];
}
}
});

View File

@ -64,6 +64,7 @@ class CornersOfFaceInput final : public bke::MeshFieldInput {
corner_evaluator.add(sort_weight_);
corner_evaluator.evaluate();
const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0);
const bool use_sorting = !all_sort_weights.is_single();
Array<int> corner_of_face(mask.min_array_size());
threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
@ -82,23 +83,27 @@ class CornersOfFaceInput final : public bke::MeshFieldInput {
const MPoly &poly = polys[poly_i];
const IndexRange corners(poly.loopstart, poly.totloop);
/* Retrieve the weights for each corner. */
sort_weights.reinitialize(corners.size());
all_sort_weights.materialize_compressed(IndexMask(corners),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(corners.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size());
corner_of_face[selection_i] = corners[sort_indices[index_in_sort_wrapped]];
if (use_sorting) {
/* Retrieve the weights for each corner. */
sort_weights.reinitialize(corners.size());
all_sort_weights.materialize_compressed(IndexMask(corners),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(corners.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
corner_of_face[selection_i] = corners[sort_indices[index_in_sort_wrapped]];
}
else {
corner_of_face[selection_i] = corners[index_in_sort_wrapped];
}
}
});

View File

@ -77,6 +77,7 @@ class CornersOfVertInput final : public bke::MeshFieldInput {
corner_evaluator.add(sort_weight_);
corner_evaluator.evaluate();
const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0);
const bool use_sorting = !all_sort_weights.is_single();
Array<int> corner_of_vertex(mask.min_array_size());
threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
@ -99,27 +100,31 @@ class CornersOfVertInput final : public bke::MeshFieldInput {
continue;
}
/* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
corner_indices.reinitialize(corners.size());
convert_span(corners, corner_indices);
/* Retrieve a compressed array of weights for each edge. */
sort_weights.reinitialize(corners.size());
all_sort_weights.materialize_compressed(IndexMask(corner_indices),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(corners.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size());
corner_of_vertex[selection_i] = corner_indices[sort_indices[index_in_sort_wrapped]];
if (use_sorting) {
/* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
corner_indices.reinitialize(corners.size());
convert_span(corners, corner_indices);
/* Retrieve a compressed array of weights for each edge. */
sort_weights.reinitialize(corners.size());
all_sort_weights.materialize_compressed(IndexMask(corner_indices),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(corners.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
corner_of_vertex[selection_i] = corner_indices[sort_indices[index_in_sort_wrapped]];
}
else {
corner_of_vertex[selection_i] = corners[index_in_sort_wrapped];
}
}
});

View File

@ -61,8 +61,8 @@ class EdgesOfVertInput final : public bke::MeshFieldInput {
{
const IndexRange vert_range(mesh.totvert);
const Span<MEdge> edges = mesh.edges();
Array<Vector<int>> vert_to_edge_map = bke::mesh_topology::build_vert_to_edge_map(edges,
mesh.totvert);
const Array<Vector<int>> vert_to_edge_map = bke::mesh_topology::build_vert_to_edge_map(
edges, mesh.totvert);
const bke::MeshFieldContext context{mesh, domain};
fn::FieldEvaluator evaluator{context, &mask};
@ -77,6 +77,7 @@ class EdgesOfVertInput final : public bke::MeshFieldInput {
edge_evaluator.add(sort_weight_);
edge_evaluator.evaluate();
const VArray<float> all_sort_weights = edge_evaluator.get_evaluated<float>(0);
const bool use_sorting = !all_sort_weights.is_single();
Array<int> edge_of_vertex(mask.min_array_size());
threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
@ -99,27 +100,32 @@ class EdgesOfVertInput final : public bke::MeshFieldInput {
continue;
}
/* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
edge_indices.reinitialize(edges.size());
convert_span(edges, edge_indices);
/* Retrieve a compressed array of weights for each edge. */
sort_weights.reinitialize(edges.size());
all_sort_weights.materialize_compressed(IndexMask(edge_indices),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(edges.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
const int index_in_sort_wrapped = mod_i(index_in_sort, edges.size());
edge_of_vertex[selection_i] = edge_indices[sort_indices[index_in_sort_wrapped]];
if (use_sorting) {
/* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
edge_indices.reinitialize(edges.size());
convert_span(edges, edge_indices);
/* Retrieve a compressed array of weights for each edge. */
sort_weights.reinitialize(edges.size());
all_sort_weights.materialize_compressed(IndexMask(edge_indices),
sort_weights.as_mutable_span());
/* Sort a separate array of compressed indices corresponding to the compressed weights.
* This allows using `materialize_compressed` to avoid virtual function call overhead
* when accessing values in the sort weights. However, it means a separate array of
* indices within the compressed array is necessary for sorting. */
sort_indices.reinitialize(edges.size());
std::iota(sort_indices.begin(), sort_indices.end(), 0);
std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
return sort_weights[a] < sort_weights[b];
});
edge_of_vertex[selection_i] = edge_indices[sort_indices[index_in_sort_wrapped]];
}
else {
edge_of_vertex[selection_i] = edges[index_in_sort_wrapped];
}
}
});

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;