USD: optionally author subdivision schema on export
USD: optionally author subdivision schema on export This PR adds support for exporting USD assets which have the subdivision schema applied. Current behavior prior to this PR is that the effects of Subdivision Surface modifiers are always applied to their mesh prior to export, such that it is not possible to recover the original base mesh. In this PR we provide three options for the subdiv schema type: Ignore - Export base mesh without subdivision with USD Scheme = None Tessellate - Export subdivided mesh with USD Scheme = None Best Match (default) - Export base mesh with USD Scheme = Catmull-Clark "Best Match" here means that Blender will set a subdiv scheme type in the exported USD asset when it is possible to closely match the subdivision surface type that was authored in Blender. At this time Blender provides two subdivision types: Catmull-Clark and Simple. Because Simple does not have a corresponding subdivision type in USD, we do not attempt to convert or represent it, and instead the Simple subdiv modifier will be evaluated and applied to the mesh during export. Whenever a Catmull-Clark Subdivision Surface modifier is applied to an object, and is the last modifier in the stack, it is possible to set the subdiv scheme to Catmull-Clark for the respective prim in the exported USD file. Authored by Apple: Matt McLin Co-authored-by: Matt McLin <mmclin@apple.com> Co-authored-by: Brecht Van Lommel <brecht@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/113267
This commit is contained in:
committed by
Jesse Yurkovich
parent
9f0b4344ac
commit
5edda6cbcc
@@ -91,6 +91,26 @@ const EnumPropertyItem rna_enum_usd_tex_name_collision_mode_items[] = {
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
const EnumPropertyItem rna_enum_usd_export_subdiv_mode_items[] = {
|
||||
{USD_SUBDIV_IGNORE,
|
||||
"IGNORE",
|
||||
0,
|
||||
"Ignore",
|
||||
"Subdivision scheme = None, export base mesh without subdivision"},
|
||||
{USD_SUBDIV_TESSELLATE,
|
||||
"TESSELLATE",
|
||||
0,
|
||||
"Tessellate",
|
||||
"Subdivision scheme = None, export subdivided mesh"},
|
||||
{USD_SUBDIV_BEST_MATCH,
|
||||
"BEST_MATCH",
|
||||
0,
|
||||
"Best Match",
|
||||
"Subdivision scheme = Catmull-Clark, when possible. "
|
||||
"Reverts to exporting the subdivided mesh for the Simple subdivision type"},
|
||||
{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 };
|
||||
@@ -155,6 +175,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
const bool export_mesh_colors = RNA_boolean_get(op->ptr, "export_mesh_colors");
|
||||
const bool export_normals = RNA_boolean_get(op->ptr, "export_normals");
|
||||
const bool export_materials = RNA_boolean_get(op->ptr, "export_materials");
|
||||
const eSubdivExportMode export_subdiv = eSubdivExportMode(
|
||||
RNA_enum_get(op->ptr, "export_subdivision"));
|
||||
const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
|
||||
const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode");
|
||||
|
||||
@@ -174,6 +196,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
export_normals,
|
||||
export_mesh_colors,
|
||||
export_materials,
|
||||
export_subdiv,
|
||||
selected_objects_only,
|
||||
visible_objects_only,
|
||||
use_instancing,
|
||||
@@ -211,6 +234,7 @@ static void wm_usd_export_draw(bContext * /*C*/, wmOperator *op)
|
||||
uiItemR(col, ptr, "export_uvmaps", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "export_normals", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "export_materials", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "export_subdivision", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "root_prim_path", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
col = uiLayoutColumn(box, true);
|
||||
@@ -335,6 +359,14 @@ void WM_OT_usd_export(wmOperatorType *ot)
|
||||
"Export viewport settings of materials as USD preview materials, and export "
|
||||
"material assignments as geometry subsets");
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"export_subdivision",
|
||||
rna_enum_usd_export_subdiv_mode_items,
|
||||
USD_SUBDIV_BEST_MATCH,
|
||||
"Subdivision Scheme",
|
||||
"Choose how subdivision modifiers will be mapped to the USD subdivision scheme "
|
||||
"during export");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"use_instancing",
|
||||
false,
|
||||
|
||||
@@ -44,7 +44,6 @@ set(SRC
|
||||
exporter/abc_custom_props.cc
|
||||
exporter/abc_export_capi.cc
|
||||
exporter/abc_hierarchy_iterator.cc
|
||||
exporter/abc_subdiv_disabler.cc
|
||||
exporter/abc_writer_abstract.cc
|
||||
exporter/abc_writer_camera.cc
|
||||
exporter/abc_writer_curves.cc
|
||||
@@ -72,7 +71,6 @@ set(SRC
|
||||
exporter/abc_archive.h
|
||||
exporter/abc_custom_props.h
|
||||
exporter/abc_hierarchy_iterator.h
|
||||
exporter/abc_subdiv_disabler.h
|
||||
exporter/abc_writer_abstract.h
|
||||
exporter/abc_writer_camera.h
|
||||
exporter/abc_writer_curves.h
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "ABC_alembic.h"
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
#include "abc_archive.h"
|
||||
#include "abc_hierarchy_iterator.h"
|
||||
#include "abc_subdiv_disabler.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "abc_subdiv_disabler.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BKE_layer.h"
|
||||
#include "BKE_modifier.hh"
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) {}
|
||||
|
||||
SubdivModifierDisabler::~SubdivModifierDisabler()
|
||||
{
|
||||
for (ModifierData *modifier : disabled_modifiers_) {
|
||||
modifier->mode &= ~eModifierMode_DisableTemporary;
|
||||
}
|
||||
}
|
||||
|
||||
void SubdivModifierDisabler::disable_modifiers()
|
||||
{
|
||||
Scene *scene = DEG_get_input_scene(depsgraph_);
|
||||
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_);
|
||||
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
||||
Object *object = base->object;
|
||||
|
||||
if (object->type != OB_MESH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ModifierData *subdiv = get_subdiv_modifier(scene, object);
|
||||
if (subdiv == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This disables more modifiers than necessary, as it doesn't take restrictions like
|
||||
* "export selected objects only" into account. However, with the subsurfs disabled,
|
||||
* moving to a different frame is also going to be faster, so in the end this is probably
|
||||
* a good thing to do. */
|
||||
subdiv->mode |= eModifierMode_DisableTemporary;
|
||||
disabled_modifiers_.insert(subdiv);
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene, Object *ob)
|
||||
{
|
||||
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
|
||||
|
||||
for (; md; md = md->prev) {
|
||||
if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Subsurf) {
|
||||
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
|
||||
|
||||
if (smd->subdivType == ME_CC_SUBSURF) {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
|
||||
/* mesh is not a subsurf. break */
|
||||
if (!ELEM(md->type, eModifierType_Displace, eModifierType_ParticleSystem)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -1,40 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
struct Depsgraph;
|
||||
struct ModifierData;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
/**
|
||||
* Temporarily all subdivision modifiers on mesh objects.
|
||||
* The destructor restores all disabled modifiers.
|
||||
*
|
||||
* This is used to export unsubdivided meshes to Alembic. It is done in a separate step before the
|
||||
* exporter starts iterating over all the frames, so that it only has to happen once per export.
|
||||
*/
|
||||
class SubdivModifierDisabler final {
|
||||
private:
|
||||
Depsgraph *depsgraph_;
|
||||
std::set<ModifierData *> disabled_modifiers_;
|
||||
|
||||
public:
|
||||
explicit SubdivModifierDisabler(Depsgraph *depsgraph);
|
||||
~SubdivModifierDisabler();
|
||||
|
||||
void disable_modifiers();
|
||||
|
||||
/**
|
||||
* Check if the mesh is a subsurf, ignoring disabled modifiers and
|
||||
* displace if it's after subsurf.
|
||||
*/
|
||||
static ModifierData *get_subdiv_modifier(Scene *scene, Object *ob);
|
||||
};
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -18,12 +18,14 @@ set(SRC
|
||||
intern/object_identifier.cc
|
||||
intern/orientation.cc
|
||||
intern/path_util.cc
|
||||
intern/subdiv_disabler.cc
|
||||
|
||||
IO_abstract_hierarchy_iterator.h
|
||||
IO_dupli_persistent_id.hh
|
||||
IO_orientation.hh
|
||||
IO_path_util.hh
|
||||
IO_path_util_types.hh
|
||||
IO_subdiv_disabler.hh
|
||||
IO_types.hh
|
||||
intern/dupli_parent_finder.hh
|
||||
)
|
||||
|
||||
62
source/blender/io/common/IO_subdiv_disabler.hh
Normal file
62
source/blender/io/common/IO_subdiv_disabler.hh
Normal file
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
struct Depsgraph;
|
||||
struct ModifierData;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/**
|
||||
* This code is shared between the Alembic and USD exporters.
|
||||
* Temporarily disable the subdiv modifier on mesh objects,
|
||||
* if the subdiv modifier is last on the modifier stack.
|
||||
*
|
||||
* The destructor restores all disabled modifiers.
|
||||
*
|
||||
* Currently, this class is used to disable Catmull-Clark subdivision modifiers.
|
||||
* It is done in a separate step before the exporter starts iterating over all
|
||||
* the frames, so that it only has to happen once per export.
|
||||
*/
|
||||
class SubdivModifierDisabler final {
|
||||
private:
|
||||
Depsgraph *depsgraph_;
|
||||
std::set<ModifierData *> disabled_modifiers_;
|
||||
std::set<Object *> modified_objects_;
|
||||
|
||||
public:
|
||||
explicit SubdivModifierDisabler(Depsgraph *depsgraph);
|
||||
~SubdivModifierDisabler();
|
||||
|
||||
/**
|
||||
* Disable subdiv modifiers on all mesh objects.
|
||||
*/
|
||||
void disable_modifiers();
|
||||
|
||||
/**
|
||||
* Return the Catmull-Clark subdiv modifier on the mesh, if it's the last modifier
|
||||
* in the list or if it's the last modifier preceding any particle system modifiers.
|
||||
* This function ignores Simple subdiv modifiers.
|
||||
*/
|
||||
static ModifierData *get_subdiv_modifier(Scene *scene, const Object *ob, ModifierMode mode);
|
||||
|
||||
/* Disallow copying. */
|
||||
SubdivModifierDisabler(const SubdivModifierDisabler &) = delete;
|
||||
SubdivModifierDisabler &operator=(const SubdivModifierDisabler &) = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Disable the given modifier and add it to the disabled
|
||||
* modifiers list.
|
||||
*/
|
||||
void disable_modifier(ModifierData *mod);
|
||||
};
|
||||
|
||||
} // namespace blender::io
|
||||
118
source/blender/io/common/intern/subdiv_disabler.cc
Normal file
118
source/blender/io/common/intern/subdiv_disabler.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BKE_layer.h"
|
||||
#include "BKE_modifier.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/* Returns the last subdiv modifier associated with an object,
|
||||
* if that modifier should be disabled.
|
||||
* We do not disable the subdiv modifier if other modifiers are
|
||||
* applied after it, with the sole exception of particle modifiers,
|
||||
* which are allowed.
|
||||
* Returns nullptr if there is not any subdiv modifier to disable.
|
||||
*/
|
||||
ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene,
|
||||
const Object *ob,
|
||||
ModifierMode mode)
|
||||
{
|
||||
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
|
||||
|
||||
for (; md; md = md->prev) {
|
||||
/* Ignore disabled modifiers. */
|
||||
if (!BKE_modifier_is_enabled(scene, md, mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Subsurf) {
|
||||
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
|
||||
|
||||
if (smd->subdivType == ME_CC_SUBSURF) {
|
||||
/* This is a Catmull-Clark modifier. */
|
||||
return md;
|
||||
}
|
||||
|
||||
/* Not Catmull-Clark, so ignore it. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If any modifier other than a particle system exists after the
|
||||
* subdiv modifier, then abort. */
|
||||
if (md->type != eModifierType_ParticleSystem) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) {}
|
||||
|
||||
SubdivModifierDisabler::~SubdivModifierDisabler()
|
||||
{
|
||||
/* Enable previously disabled modifiers. */
|
||||
for (ModifierData *modifier : disabled_modifiers_) {
|
||||
modifier->mode &= ~eModifierMode_DisableTemporary;
|
||||
}
|
||||
|
||||
/* Update object to render with restored modifiers in the viewport. */
|
||||
for (Object *object : modified_objects_) {
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
void SubdivModifierDisabler::disable_modifiers()
|
||||
{
|
||||
eEvaluationMode eval_mode = DEG_get_mode(depsgraph_);
|
||||
const ModifierMode mode = eval_mode == DAG_EVAL_VIEWPORT ? eModifierMode_Realtime :
|
||||
eModifierMode_Render;
|
||||
|
||||
Scene *scene = DEG_get_input_scene(depsgraph_);
|
||||
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_);
|
||||
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
||||
Object *object = base->object;
|
||||
|
||||
if (object->type != OB_MESH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if a subdiv modifier exists, and should be disabled. */
|
||||
ModifierData *mod = get_subdiv_modifier(scene, object, mode);
|
||||
if (!mod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This might disable more modifiers than necessary, as it doesn't take restrictions like
|
||||
* "export selected objects only" into account. However, with the subdivs disabled,
|
||||
* moving to a different frame is also going to be faster, so in the end this is probably
|
||||
* a good thing to do. */
|
||||
disable_modifier(mod);
|
||||
modified_objects_.insert(object);
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
void SubdivModifierDisabler::disable_modifier(ModifierData *mod)
|
||||
{
|
||||
mod->mode |= eModifierMode_DisableTemporary;
|
||||
disabled_modifiers_.insert(mod);
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
#include "usd.h"
|
||||
#include "usd.hh"
|
||||
#include "usd_hierarchy_iterator.h"
|
||||
@@ -226,6 +227,16 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms,
|
||||
Scene *scene = DEG_get_input_scene(depsgraph);
|
||||
Main *bmain = DEG_get_bmain(depsgraph);
|
||||
|
||||
SubdivModifierDisabler mod_disabler(depsgraph);
|
||||
|
||||
/* If we want to set the subdiv scheme, then we need to the export the mesh
|
||||
* without the subdiv modifier applied. */
|
||||
if ((params.export_subdiv == USD_SUBDIV_BEST_MATCH) ||
|
||||
(params.export_subdiv == USD_SUBDIV_IGNORE)) {
|
||||
mod_disabler.disable_modifiers();
|
||||
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
||||
}
|
||||
|
||||
/* This whole `export_to_stage` function is assumed to cover about 80% of the whole export
|
||||
* process, from 0.1f to 0.9f. */
|
||||
worker_status->progress = 0.10f;
|
||||
|
||||
@@ -56,6 +56,35 @@ bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Get the last subdiv modifier, regardless of enable/disable status */
|
||||
static const SubsurfModifierData *get_last_subdiv_modifier(eEvaluationMode eval_mode, Object *obj)
|
||||
{
|
||||
BLI_assert(obj);
|
||||
|
||||
/* Return the subdiv modifier if it is the last modifier and has
|
||||
* the required mode enabled. */
|
||||
|
||||
ModifierData *md = (ModifierData *)(obj->modifiers.last);
|
||||
|
||||
if (!md) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Determine if the modifier is enabled for the current evaluation mode. */
|
||||
ModifierMode mod_mode = (eval_mode == DAG_EVAL_RENDER) ? eModifierMode_Render :
|
||||
eModifierMode_Realtime;
|
||||
|
||||
if ((md->mode & mod_mode) != mod_mode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Subsurf) {
|
||||
return reinterpret_cast<SubsurfModifierData *>(md);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
Object *object_eval = context.object;
|
||||
@@ -67,7 +96,11 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
||||
}
|
||||
|
||||
try {
|
||||
write_mesh(context, mesh);
|
||||
/* Fetch the subdiv modifier, if one exists and it is the last modifier. */
|
||||
const SubsurfModifierData *subsurfData = get_last_subdiv_modifier(
|
||||
usd_export_context_.export_params.evaluation_mode, object_eval);
|
||||
|
||||
write_mesh(context, mesh, subsurfData);
|
||||
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
@@ -376,7 +409,9 @@ struct USDMeshData {
|
||||
pxr::VtFloatArray corner_sharpnesses;
|
||||
};
|
||||
|
||||
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||
void USDGenericMeshWriter::write_mesh(HierarchyContext &context,
|
||||
Mesh *mesh,
|
||||
const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
@@ -465,18 +500,26 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||
}
|
||||
|
||||
write_custom_data(mesh, usd_mesh);
|
||||
write_surface_velocity(mesh, usd_mesh);
|
||||
|
||||
if (usd_export_context_.export_params.export_normals) {
|
||||
const pxr::TfToken subdiv_scheme = get_subdiv_scheme(subsurfData);
|
||||
|
||||
/* Normals can be animated, so ensure these are written for each frame,
|
||||
* unless a subdiv modifier is used, in which case normals are computed,
|
||||
* not stored with the mesh. */
|
||||
if (usd_export_context_.export_params.export_normals &&
|
||||
subdiv_scheme == pxr::UsdGeomTokens->none) {
|
||||
write_normals(mesh, usd_mesh);
|
||||
}
|
||||
write_surface_velocity(mesh, usd_mesh);
|
||||
|
||||
/* TODO(Sybren): figure out what happens when the face groups change. */
|
||||
if (frame_has_been_written_) {
|
||||
return;
|
||||
}
|
||||
|
||||
usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
|
||||
/* The subdivision scheme is a uniform according to spec,
|
||||
* so this value cannot be animated. */
|
||||
write_subdiv(subdiv_scheme, usd_mesh, subsurfData);
|
||||
|
||||
if (usd_export_context_.export_params.export_materials) {
|
||||
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
|
||||
@@ -491,6 +534,80 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||
}
|
||||
}
|
||||
|
||||
pxr::TfToken USDGenericMeshWriter::get_subdiv_scheme(const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
/* Default to setting the subdivision scheme to None. */
|
||||
pxr::TfToken subdiv_scheme = pxr::UsdGeomTokens->none;
|
||||
|
||||
if (subsurfData) {
|
||||
if (subsurfData->subdivType == SUBSURF_TYPE_CATMULL_CLARK) {
|
||||
if (usd_export_context_.export_params.export_subdiv == USD_SUBDIV_BEST_MATCH) {
|
||||
/* If a subdivision modifier exists, and it uses Catmull-Clark, then apply Catmull-Clark
|
||||
* SubD scheme. */
|
||||
subdiv_scheme = pxr::UsdGeomTokens->catmullClark;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* "Simple" is currently the only other subdivision type provided by Blender, */
|
||||
/* and we do not yet provide a corresponding representation for USD export. */
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"USD export: Simple subdivision not supported, exporting subdivided mesh");
|
||||
}
|
||||
}
|
||||
|
||||
return subdiv_scheme;
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_subdiv(const pxr::TfToken &subdiv_scheme,
|
||||
pxr::UsdGeomMesh &usd_mesh,
|
||||
const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
usd_mesh.CreateSubdivisionSchemeAttr().Set(subdiv_scheme);
|
||||
if (subdiv_scheme == pxr::UsdGeomTokens->catmullClark) {
|
||||
/* For Catmull-Clark, also consider the various interpolation modes. */
|
||||
/* For reference, see
|
||||
* https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#face-varying-interpolation-rules
|
||||
*/
|
||||
switch (subsurfData->uv_smooth) {
|
||||
case SUBSURF_UV_SMOOTH_NONE:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->all);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersOnly);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_AND_JUNCTIONS:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus1);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_JUNCTIONS_AND_CONCAVE:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus2);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->boundaries);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_ALL:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->none);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported UV smoothing mode.");
|
||||
}
|
||||
|
||||
/* For reference, see
|
||||
* https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#boundary-interpolation-rules
|
||||
*/
|
||||
switch (subsurfData->boundary_smooth) {
|
||||
case SUBSURF_BOUNDARY_SMOOTH_ALL:
|
||||
usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeOnly);
|
||||
break;
|
||||
case SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS:
|
||||
usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeAndCorner);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported boundary smoothing mode.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_positions(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
const Span<pxr::GfVec3f> positions = mesh->vert_positions().cast<pxr::GfVec3f>();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "usd_writer_abstract.h"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
@@ -31,7 +32,11 @@ class USDGenericMeshWriter : public USDAbstractWriter {
|
||||
/* Mapping from material slot number to array of face indices with that material. */
|
||||
using MaterialFaceGroups = Map<short, pxr::VtIntArray>;
|
||||
|
||||
void write_mesh(HierarchyContext &context, Mesh *mesh);
|
||||
void write_mesh(HierarchyContext &context, Mesh *mesh, const SubsurfModifierData *subsurfData);
|
||||
pxr::TfToken get_subdiv_scheme(const SubsurfModifierData *subsurfData);
|
||||
void write_subdiv(const pxr::TfToken &subdiv_scheme,
|
||||
pxr::UsdGeomMesh &usd_mesh,
|
||||
const SubsurfModifierData *subsurfData);
|
||||
void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data);
|
||||
void assign_materials(const HierarchyContext &context,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
|
||||
@@ -40,6 +40,13 @@ typedef enum eUSDTexNameCollisionMode {
|
||||
USD_TEX_NAME_COLLISION_OVERWRITE = 1,
|
||||
} eUSDTexNameCollisionMode;
|
||||
|
||||
typedef enum eSubdivExportMode {
|
||||
USD_SUBDIV_IGNORE = 0, /* Subdivision scheme = None, export base mesh without subdivision. */
|
||||
USD_SUBDIV_TESSELLATE = 1, /* Subdivision scheme = None, export subdivided mesh. */
|
||||
USD_SUBDIV_BEST_MATCH = 2, /* Apply the USD subdivision scheme that is the closest match to Blender. */
|
||||
/* Reverts to USD_SUBDIV_TESSELLATE if the subdivision method is not supported. */
|
||||
} eSubdivExportMode;
|
||||
|
||||
struct USDExportParams {
|
||||
bool export_animation = false;
|
||||
bool export_hair = true;
|
||||
@@ -47,6 +54,7 @@ struct USDExportParams {
|
||||
bool export_normals = true;
|
||||
bool export_mesh_colors = true;
|
||||
bool export_materials = true;
|
||||
eSubdivExportMode export_subdiv = USD_SUBDIV_BEST_MATCH;
|
||||
bool selected_objects_only = false;
|
||||
bool visible_objects_only = true;
|
||||
bool use_instancing = false;
|
||||
|
||||
Reference in New Issue
Block a user