diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index a42df2026db..91f469c1197 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -145,6 +145,17 @@ const EnumPropertyItem rna_enum_usd_xform_op_mode_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +const EnumPropertyItem prop_usdz_downscale_size[] = { + {USD_TEXTURE_SIZE_KEEP, "KEEP", 0, "Keep", "Keep all current texture sizes"}, + {USD_TEXTURE_SIZE_256, "256", 0, "256", "Resize to a maximum of 256 pixels"}, + {USD_TEXTURE_SIZE_512, "512", 0, "512", "Resize to a maximum of 512 pixels"}, + {USD_TEXTURE_SIZE_1024, "1024", 0, "1024", "Resize to a maximum of 1024 pixels"}, + {USD_TEXTURE_SIZE_2048, "2048", 0, "2048", "Resize to a maximum of 2048 pixels"}, + {USD_TEXTURE_SIZE_4096, "4096", 0, "4096", "Resize to a maximum of 4096 pixels"}, + {USD_TEXTURE_SIZE_CUSTOM, "CUSTOM", 0, "Custom", "Specify a custom size"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + /* 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 }; @@ -246,6 +257,11 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) const eUSDXformOpMode xform_op_mode = eUSDXformOpMode(RNA_enum_get(op->ptr, "xform_op_mode")); + const eUSDZTextureDownscaleSize usdz_downscale_size = eUSDZTextureDownscaleSize( + RNA_enum_get(op->ptr, "usdz_downscale_size")); + + const int usdz_downscale_custom_size = RNA_int_get(op->ptr, "usdz_downscale_custom_size"); + char root_prim_path[FILE_MAX]; RNA_string_get(op->ptr, "root_prim_path", root_prim_path); process_prim_path(root_prim_path); @@ -284,6 +300,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) export_cameras, export_curves, export_volumes, + usdz_downscale_size, + usdz_downscale_custom_size, }; STRNCPY(params.root_prim_path, root_prim_path); @@ -364,6 +382,14 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op) const bool preview = RNA_boolean_get(ptr, "generate_preview_surface"); uiLayoutSetActive(row, export_mtl && preview); + uiLayout *col2 = uiLayoutColumn(col, true); + uiLayoutSetPropSep(col2, true); + uiLayoutSetEnabled(col2, RNA_boolean_get(ptr, "export_textures")); + uiItemR(col2, ptr, "usdz_downscale_size", UI_ITEM_NONE, nullptr, ICON_NONE); + if (RNA_enum_get(ptr, "usdz_downscale_size") == USD_TEXTURE_SIZE_CUSTOM) { + uiItemR(col2, ptr, "usdz_downscale_custom_size", UI_ITEM_NONE, nullptr, ICON_NONE); + } + row = uiLayoutRow(col, true); uiItemR(row, ptr, "overwrite_textures", UI_ITEM_NONE, nullptr, ICON_NONE); const bool export_tex = RNA_boolean_get(ptr, "export_textures"); @@ -651,6 +677,23 @@ void WM_OT_usd_export(wmOperatorType *ot) MOD_TRIANGULATE_NGON_BEAUTY, "N-gon Method", "Method for splitting the n-gons into triangles"); + + RNA_def_enum(ot->srna, + "usdz_downscale_size", + prop_usdz_downscale_size, + DAG_EVAL_VIEWPORT, + "USDZ Texture Downsampling", + "Choose a maximum size for all exported textures"); + + RNA_def_int(ot->srna, + "usdz_downscale_custom_size", + 128, + 64, + 16384, + "USDZ Custom Downscale Size", + "Custom size for downscaling exported textures", + 128, + 8192); } /* ====== USD Import ====== */ diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index 0cbce032379..e70f27c9140 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -34,6 +34,8 @@ #include "BKE_blender_version.h" #include "BKE_context.hh" #include "BKE_global.hh" +#include "BKE_image.h" +#include "BKE_image_save.h" #include "BKE_lib_id.hh" #include "BKE_report.hh" #include "BKE_scene.hh" @@ -49,12 +51,16 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "CLG_log.h" +static CLG_LogRef LOG = {"io.usd"}; + namespace blender::io::usd { struct ExportJobData { Main *bmain; Depsgraph *depsgraph; wmWindowManager *wm; + Scene *scene; /** Unarchived_filepath is used for USDA/USDC/USD export. */ char unarchived_filepath[FILE_MAX]; @@ -188,6 +194,78 @@ static void report_job_duration(const ExportJobData *data) std::cout << '\n'; } +static void process_usdz_textures(const ExportJobData *data, const char *path) +{ + const eUSDZTextureDownscaleSize enum_value = data->params.usdz_downscale_size; + if (enum_value == USD_TEXTURE_SIZE_KEEP) { + return; + } + + int image_size = ((enum_value == USD_TEXTURE_SIZE_CUSTOM ? + data->params.usdz_downscale_custom_size : + enum_value)); + + char texture_path[FILE_MAX]; + BLI_strncpy_rlen(texture_path, path, FILE_MAX); + BLI_path_append(texture_path, FILE_MAX, "textures"); + BLI_path_slash_ensure(texture_path, sizeof(texture_path)); + + struct direntry *entries; + unsigned int num_files = BLI_filelist_dir_contents(texture_path, &entries); + + for (int index = 0; index < num_files; index++) { + /* We can skip checking extensions as this folder is only created + * when we're doing a USDZ export. */ + if (!BLI_is_dir(entries[index].path)) { + Image *im = BKE_image_load(data->bmain, entries[index].path); + if (!im) { + CLOG_WARN(&LOG, "-- Unable to open file for downscaling: %s", entries[index].path); + continue; + } + + int width, height; + BKE_image_get_size(im, NULL, &width, &height); + const int longest = width >= height ? width : height; + const float scale = 1.0 / ((float)longest / (float)image_size); + + if (longest > image_size) { + const int width_adjusted = float(width) * scale; + const int height_adjusted = float(height) * scale; + BKE_image_scale(im, width_adjusted, height_adjusted, nullptr); + + ImageSaveOptions opts; + + if (BKE_image_save_options_init(&opts, data->bmain, data->scene, im, NULL, false, false)) { + bool result = BKE_image_save(NULL, data->bmain, im, NULL, &opts); + if (!result) { + CLOG_ERROR(&LOG, + "-- Unable to resave %s (new size: %dx%d)", + data->usdz_filepath, + width_adjusted, + height_adjusted); + } + else { + CLOG_INFO(&LOG, + 2, + "Downscaled %s to %dx%d", + entries[index].path, + width_adjusted, + height_adjusted); + } + } + + BKE_image_save_options_free(&opts); + } + + /* Make sure to free the image so it doesn't stick + * around in the library of the open file. */ + BKE_id_free(data->bmain, (void *)im); + } + } + + BLI_filelist_free(entries, num_files); +} + /** * For usdz export, we must first create a usd/a/c file and then covert it to usdz. In Blender's * case, we first create a usdc file in Blender's temporary working directory, and store the path @@ -217,6 +295,8 @@ static bool perform_usdz_conversion(const ExportJobData *data) BLI_change_working_dir(usdc_temp_dir); + process_usdz_textures(data, usdc_temp_dir); + pxr::UsdUtilsCreateNewUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file); BLI_change_working_dir(original_working_dir); @@ -525,6 +605,7 @@ bool USD_export(bContext *C, job->bmain = CTX_data_main(C); job->wm = CTX_wm_manager(C); + job->scene = scene; job->export_ok = false; set_job_filepath(job, filepath); diff --git a/source/blender/io/usd/usd.hh b/source/blender/io/usd/usd.hh index 17148311690..bc468fd1e39 100644 --- a/source/blender/io/usd/usd.hh +++ b/source/blender/io/usd/usd.hh @@ -85,6 +85,16 @@ typedef enum eUSDXformOpMode { USD_XFORM_OP_MAT = 2, } eUSDXformOpMode; +typedef enum eUSDZTextureDownscaleSize { + USD_TEXTURE_SIZE_CUSTOM = -1, + USD_TEXTURE_SIZE_KEEP = 0, + USD_TEXTURE_SIZE_256 = 256, + USD_TEXTURE_SIZE_512 = 512, + USD_TEXTURE_SIZE_1024 = 1024, + USD_TEXTURE_SIZE_2048 = 2048, + USD_TEXTURE_SIZE_4096 = 4096 +} eUSDZTextureDownscaleSize; + struct USDExportParams { bool export_animation = false; bool export_hair = true; @@ -120,6 +130,8 @@ struct USDExportParams { bool export_curves = true; bool export_volumes = true; + eUSDZTextureDownscaleSize usdz_downscale_size = eUSDZTextureDownscaleSize::USD_TEXTURE_SIZE_KEEP; + int usdz_downscale_custom_size = 128; char root_prim_path[1024] = ""; /* FILE_MAX */ char collection[MAX_IDPROP_NAME] = "";