This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/io/io_usd.c
Michael Kowalski c85c52f2ce USD Preview Surface material export.
Add `USD Preview Surface From Nodes` export option, to convert a
Principled BSDF material node network to an approximate USD Preview
Surface shader representation. If this option is disabled, the original
material export behavior is maintained, where viewport setting are saved
to the Preview Surface shader.

Also added the following options for texture export.

  - `Export Textures`: If converting Preview Surface, export textures
    referenced by shader nodes to a 'textures' directory which is a
    sibling of the USD file.
  - `Overwrite Textures`: Allow overwriting existing texture files when
    exporting textures (this option is off by default).
  - `Relative Texture Paths`:  Make texture asset paths relative to the
    USD.

The entry point for the new functionality is
`create_usd_preview_surface_material()`, called from
`USDAbstractWriter::ensure_usd_material()`.  The material conversion
currently handles a small subset of Blender shading nodes,
`BSDF_DIFFUSE`, `BSDF_PRINCIPLED`, `TEX_IMAGE` and `UVMAP`.

Texture export is handled by copying texture files from their original
location to a `textures` folder in the same directory as the USD.
In-memory and packed textures are saved directly to the textures folder.

This patch is based, in part, on code in Tangent Animation's USD
exporter branch.

Reviewed By: sybren, HooglyBoogly

Differential Revision: https://developer.blender.org/D13647
2022-01-27 15:51:50 +01:00

580 lines
22 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2019 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#ifdef WITH_USD
# include "DNA_modifier_types.h"
# include "DNA_space_types.h"
# include <string.h>
# include "BKE_context.h"
# include "BKE_main.h"
# include "BKE_report.h"
# include "BLI_blenlib.h"
# include "BLI_path_util.h"
# include "BLI_string.h"
# include "BLI_utildefines.h"
# include "BLT_translation.h"
# include "ED_object.h"
# include "MEM_guardedalloc.h"
# include "RNA_access.h"
# include "RNA_define.h"
# include "RNA_enum_types.h"
# include "UI_interface.h"
# include "UI_resources.h"
# include "WM_api.h"
# include "WM_types.h"
# include "DEG_depsgraph.h"
# include "io_usd.h"
# include "usd.h"
# include <stdio.h>
const EnumPropertyItem rna_enum_usd_export_evaluation_mode_items[] = {
{DAG_EVAL_RENDER,
"RENDER",
0,
"Render",
"Use Render settings for object visibility, modifier settings, etc"},
{DAG_EVAL_VIEWPORT,
"VIEWPORT",
0,
"Viewport",
"Use Viewport settings for object visibility, modifier settings, etc"},
{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 };
typedef struct eUSDOperatorOptions {
bool as_background_job;
} eUSDOperatorOptions;
static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
eUSDOperatorOptions *options = MEM_callocN(sizeof(eUSDOperatorOptions), "eUSDOperatorOptions");
options->as_background_job = true;
op->customdata = options;
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
const char *main_blendfile_path = BKE_main_blendfile_path(bmain);
if (main_blendfile_path[0] == '\0') {
BLI_strncpy(filepath, "untitled", sizeof(filepath));
}
else {
BLI_strncpy(filepath, main_blendfile_path, sizeof(filepath));
}
BLI_path_extension_replace(filepath, sizeof(filepath), ".usdc");
RNA_string_set(op->ptr, "filepath", filepath);
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_usd_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
char filename[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filename);
eUSDOperatorOptions *options = (eUSDOperatorOptions *)op->customdata;
const bool as_background_job = (options != NULL && options->as_background_job);
MEM_SAFE_FREE(op->customdata);
const bool selected_objects_only = RNA_boolean_get(op->ptr, "selected_objects_only");
const bool visible_objects_only = RNA_boolean_get(op->ptr, "visible_objects_only");
const bool export_animation = RNA_boolean_get(op->ptr, "export_animation");
const bool export_hair = RNA_boolean_get(op->ptr, "export_hair");
const bool export_uvmaps = RNA_boolean_get(op->ptr, "export_uvmaps");
const bool export_normals = RNA_boolean_get(op->ptr, "export_normals");
const bool export_materials = RNA_boolean_get(op->ptr, "export_materials");
const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode");
const bool generate_preview_surface = RNA_boolean_get(op->ptr, "generate_preview_surface");
const bool export_textures = RNA_boolean_get(op->ptr, "export_textures");
const bool overwrite_textures = RNA_boolean_get(op->ptr, "overwrite_textures");
const bool relative_texture_paths = RNA_boolean_get(op->ptr, "relative_texture_paths");
struct USDExportParams params = {
export_animation,
export_hair,
export_uvmaps,
export_normals,
export_materials,
selected_objects_only,
visible_objects_only,
use_instancing,
evaluation_mode,
generate_preview_surface,
export_textures,
overwrite_textures,
relative_texture_paths,
};
bool ok = USD_export(C, filename, &params, as_background_job);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayout *col;
struct PointerRNA *ptr = op->ptr;
uiLayoutSetPropSep(layout, true);
uiLayout *box = uiLayoutBox(layout);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "selected_objects_only", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "visible_objects_only", 0, NULL, ICON_NONE);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "export_animation", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_hair", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_uvmaps", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_normals", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_materials", 0, NULL, ICON_NONE);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "evaluation_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials"));
uiItemR(col, ptr, "generate_preview_surface", 0, NULL, ICON_NONE);
const bool export_mtl = RNA_boolean_get(ptr, "export_materials");
uiLayoutSetActive(col, export_mtl);
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "export_textures", 0, NULL, ICON_NONE);
const bool preview = RNA_boolean_get(ptr, "generate_preview_surface");
uiLayoutSetActive(row, export_mtl && preview);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "overwrite_textures", 0, NULL, ICON_NONE);
const bool export_tex = RNA_boolean_get(ptr, "export_textures");
uiLayoutSetActive(row, export_mtl && preview && export_tex);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "relative_texture_paths", 0, NULL, ICON_NONE);
uiLayoutSetActive(row, export_mtl && preview);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE);
}
void WM_OT_usd_export(struct wmOperatorType *ot)
{
ot->name = "Export USD";
ot->description = "Export current scene in a USD archive";
ot->idname = "WM_OT_usd_export";
ot->invoke = wm_usd_export_invoke;
ot->exec = wm_usd_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_usd_export_draw;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_USD,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_boolean(ot->srna,
"selected_objects_only",
false,
"Selection Only",
"Only selected objects are exported. Unselected parents of selected objects are "
"exported as empty transform");
RNA_def_boolean(ot->srna,
"visible_objects_only",
true,
"Visible Only",
"Only visible objects are exported. Invisible parents of exported objects are "
"exported as empty transform");
RNA_def_boolean(ot->srna,
"export_animation",
false,
"Animation",
"When checked, the render frame range is exported. When false, only the current "
"frame is exported");
RNA_def_boolean(
ot->srna, "export_hair", false, "Hair", "When checked, hair is exported as USD curves");
RNA_def_boolean(ot->srna,
"export_uvmaps",
true,
"UV Maps",
"When checked, all UV maps of exported meshes are included in the export");
RNA_def_boolean(ot->srna,
"export_normals",
true,
"Normals",
"When checked, normals of exported meshes are included in the export");
RNA_def_boolean(ot->srna,
"export_materials",
true,
"Materials",
"When checked, the viewport settings of materials are exported as USD preview "
"materials, and material assignments are exported as geometry subsets");
RNA_def_boolean(ot->srna,
"use_instancing",
false,
"Instancing",
"When checked, instanced objects are exported as references in USD. "
"When unchecked, instanced objects are exported as real objects");
RNA_def_enum(ot->srna,
"evaluation_mode",
rna_enum_usd_export_evaluation_mode_items,
DAG_EVAL_RENDER,
"Use Settings for",
"Determines visibility of objects, modifier settings, and other areas where there "
"are different settings for viewport and rendering");
RNA_def_boolean(ot->srna,
"generate_preview_surface",
true,
"To USD Preview Surface",
"Generate an approximate USD Preview Surface shader "
"representation of a Principled BSDF node network");
RNA_def_boolean(ot->srna,
"export_textures",
true,
"Export Textures",
"If exporting materials, export textures referenced by material nodes "
"to a 'textures' directory in the same directory as the USD file");
RNA_def_boolean(ot->srna,
"overwrite_textures",
false,
"Overwrite Textures",
"Allow overwriting existing texture files when exporting textures");
RNA_def_boolean(ot->srna,
"relative_texture_paths",
true,
"Relative Texture Paths",
"Make texture asset paths relative to the USD file");
}
/* ====== USD Import ====== */
static int wm_usd_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
eUSDOperatorOptions *options = MEM_callocN(sizeof(eUSDOperatorOptions), "eUSDOperatorOptions");
options->as_background_job = true;
op->customdata = options;
return WM_operator_filesel(C, op, event);
}
static int wm_usd_import_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
char filename[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filename);
eUSDOperatorOptions *options = (eUSDOperatorOptions *)op->customdata;
const bool as_background_job = (options != NULL && options->as_background_job);
MEM_SAFE_FREE(op->customdata);
const float scale = RNA_float_get(op->ptr, "scale");
const bool set_frame_range = RNA_boolean_get(op->ptr, "set_frame_range");
const bool read_mesh_uvs = RNA_boolean_get(op->ptr, "read_mesh_uvs");
const bool read_mesh_colors = RNA_boolean_get(op->ptr, "read_mesh_colors");
char mesh_read_flag = MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY;
if (read_mesh_uvs) {
mesh_read_flag |= MOD_MESHSEQ_READ_UV;
}
if (read_mesh_colors) {
mesh_read_flag |= MOD_MESHSEQ_READ_COLOR;
}
const bool import_cameras = RNA_boolean_get(op->ptr, "import_cameras");
const bool import_curves = RNA_boolean_get(op->ptr, "import_curves");
const bool import_lights = RNA_boolean_get(op->ptr, "import_lights");
const bool import_materials = RNA_boolean_get(op->ptr, "import_materials");
const bool import_meshes = RNA_boolean_get(op->ptr, "import_meshes");
const bool import_volumes = RNA_boolean_get(op->ptr, "import_volumes");
const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv");
const bool import_instance_proxies = RNA_boolean_get(op->ptr, "import_instance_proxies");
const bool import_visible_only = RNA_boolean_get(op->ptr, "import_visible_only");
const bool create_collection = RNA_boolean_get(op->ptr, "create_collection");
char *prim_path_mask = malloc(1024);
RNA_string_get(op->ptr, "prim_path_mask", prim_path_mask);
const bool import_guide = RNA_boolean_get(op->ptr, "import_guide");
const bool import_proxy = RNA_boolean_get(op->ptr, "import_proxy");
const bool import_render = RNA_boolean_get(op->ptr, "import_render");
const bool import_usd_preview = RNA_boolean_get(op->ptr, "import_usd_preview");
const bool set_material_blend = RNA_boolean_get(op->ptr, "set_material_blend");
const float light_intensity_scale = RNA_float_get(op->ptr, "light_intensity_scale");
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
int sequence_len = 1;
/* Switch out of edit mode to avoid being stuck in it (T54326). */
Object *obedit = CTX_data_edit_object(C);
if (obedit) {
ED_object_mode_set(C, OB_MODE_EDIT);
}
const bool validate_meshes = false;
const bool use_instancing = false;
struct USDImportParams params = {.scale = scale,
.is_sequence = is_sequence,
.set_frame_range = set_frame_range,
.sequence_len = sequence_len,
.offset = offset,
.validate_meshes = validate_meshes,
.mesh_read_flag = mesh_read_flag,
.import_cameras = import_cameras,
.import_curves = import_curves,
.import_lights = import_lights,
.import_materials = import_materials,
.import_meshes = import_meshes,
.import_volumes = import_volumes,
.prim_path_mask = prim_path_mask,
.import_subdiv = import_subdiv,
.import_instance_proxies = import_instance_proxies,
.create_collection = create_collection,
.import_guide = import_guide,
.import_proxy = import_proxy,
.import_render = import_render,
.import_visible_only = import_visible_only,
.use_instancing = use_instancing,
.import_usd_preview = import_usd_preview,
.set_material_blend = set_material_blend,
.light_intensity_scale = light_intensity_scale};
const bool ok = USD_import(C, filename, &params, as_background_job);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
struct PointerRNA *ptr = op->ptr;
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayout *box = uiLayoutBox(layout);
uiLayout *col = uiLayoutColumnWithHeading(box, true, IFACE_("Data Types"));
uiItemR(col, ptr, "import_cameras", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_curves", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_lights", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_materials", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_meshes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_volumes", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "prim_path_mask", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "scale", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Mesh Data"));
uiItemR(col, ptr, "read_mesh_uvs", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "read_mesh_colors", 0, NULL, ICON_NONE);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Include"));
uiItemR(col, ptr, "import_subdiv", 0, IFACE_("Subdivision"), ICON_NONE);
uiItemR(col, ptr, "import_instance_proxies", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_visible_only", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_guide", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_proxy", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_render", 0, NULL, ICON_NONE);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Options"));
uiItemR(col, ptr, "set_frame_range", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "relative_path", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "create_collection", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "light_intensity_scale", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental"));
uiItemR(col, ptr, "import_usd_preview", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(col, RNA_boolean_get(ptr, "import_materials"));
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "set_material_blend", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, RNA_boolean_get(ptr, "import_usd_preview"));
}
void WM_OT_usd_import(struct wmOperatorType *ot)
{
ot->name = "Import USD";
ot->description = "Import USD stage into current scene";
ot->idname = "WM_OT_usd_import";
ot->invoke = wm_usd_import_invoke;
ot->exec = wm_usd_import_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_usd_import_draw;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_USD,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
RNA_def_float(
ot->srna,
"scale",
1.0f,
0.0001f,
1000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
1000.0f);
RNA_def_boolean(ot->srna,
"set_frame_range",
true,
"Set Frame Range",
"Update the scene's start and end frame to match those of the USD archive");
RNA_def_boolean(ot->srna, "import_cameras", true, "Cameras", "");
RNA_def_boolean(ot->srna, "import_curves", true, "Curves", "");
RNA_def_boolean(ot->srna, "import_lights", true, "Lights", "");
RNA_def_boolean(ot->srna, "import_materials", true, "Materials", "");
RNA_def_boolean(ot->srna, "import_meshes", true, "Meshes", "");
RNA_def_boolean(ot->srna, "import_volumes", true, "Volumes", "");
RNA_def_boolean(ot->srna,
"import_subdiv",
false,
"Import Subdivision Scheme",
"Create subdivision surface modifiers based on the USD "
"SubdivisionScheme attribute");
RNA_def_boolean(ot->srna,
"import_instance_proxies",
true,
"Import Instance Proxies",
"Create unique Blender objects for USD instances");
RNA_def_boolean(ot->srna,
"import_visible_only",
true,
"Visible Primitives Only",
"Do not import invisible USD primitives. "
"Only applies to primitives with a non-animated visibility attribute. "
"Primitives with animated visibility will always be imported");
RNA_def_boolean(ot->srna,
"create_collection",
false,
"Create Collection",
"Add all imported objects to a new collection");
RNA_def_boolean(ot->srna, "read_mesh_uvs", true, "UV Coordinates", "Read mesh UV coordinates");
RNA_def_boolean(ot->srna, "read_mesh_colors", false, "Vertex Colors", "Read mesh vertex colors");
RNA_def_string(ot->srna,
"prim_path_mask",
NULL,
1024,
"Path Mask",
"Import only the subset of the USD scene rooted at the given primitive");
RNA_def_boolean(ot->srna, "import_guide", false, "Guide", "Import guide geometry");
RNA_def_boolean(ot->srna, "import_proxy", true, "Proxy", "Import proxy geometry");
RNA_def_boolean(ot->srna, "import_render", true, "Render", "Import final render geometry");
RNA_def_boolean(ot->srna,
"import_usd_preview",
false,
"Import USD Preview",
"Convert UsdPreviewSurface shaders to Principled BSDF shader networks");
RNA_def_boolean(ot->srna,
"set_material_blend",
true,
"Set Material Blend",
"If the Import USD Preview option is enabled, "
"the material blend method will automatically be set based on the "
"shader's opacity and opacityThreshold inputs");
RNA_def_float(ot->srna,
"light_intensity_scale",
1.0f,
0.0001f,
10000.0f,
"Light Intensity Scale",
"Scale for the intensity of imported lights",
0.0001f,
1000.0f);
}
#endif /* WITH_USD */