Curves: cage overlay for sculpt mode #104467
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, ¶ms, 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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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")) &&
|
||||
|
|
|
@ -604,8 +604,11 @@ static void gizmo2d_xform_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
|
|||
UI_view2d_view_to_region_m4(®ion->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);
|
||||
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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", "");
|
||||
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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; });
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue