mirror of
https://github.com/blender/blender
synced 2026-09-26 16:15:47 +03:00
USD: export to a single root prim by default
This PR adds the following changes: A single root is always set as default. After talking to Wave and Spiff, we settled on root being the best default. Users who don't want a single root prim inserted, can choose to clear the field The root prim no longer requires the user to prefix the field with /. It will implicitly insert that for them. On export, the root_prim hierarchy is now defined all as Xform instead of just the final prim in the path. Each prim also has custom metadata added to show that it was generated by Blender. This follows convention in other DCCs as well. On import, the code now finds the hierarchy of generated prims using that metadata. It then skips importing them. This means that you can roundtrip hierarchies even with an inserted root. Co-authored-by: Dhruv Govil <dgovil2@apple.com> Pull Request: https://projects.blender.org/blender/blender/pulls/113187
This commit is contained in:
parent
89e3ba4e25
commit
b262655d39
7 changed files with 84 additions and 13 deletions
|
|
@ -111,6 +111,13 @@ static void process_prim_path(char *prim_path)
|
|||
if (prim_path[0] == '/' && strlen(prim_path) == 1) {
|
||||
prim_path[0] = '\0';
|
||||
}
|
||||
|
||||
/* If a prim path doesn't start with a "/" it
|
||||
* is invalid when creating the prim. */
|
||||
if (prim_path[0] != '/') {
|
||||
auto new_path = "/" + std::string(prim_path);
|
||||
snprintf(prim_path, FILE_MAX, "%s", new_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
||||
|
|
@ -371,7 +378,7 @@ void WM_OT_usd_export(wmOperatorType *ot)
|
|||
|
||||
RNA_def_string(ot->srna,
|
||||
"root_prim_path",
|
||||
nullptr,
|
||||
"/root",
|
||||
FILE_MAX,
|
||||
"Root Prim",
|
||||
"If set, add a transform primitive with the given path to the stage "
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "usd_hook.h"
|
||||
|
||||
#include <pxr/base/plug/registry.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
|
|
@ -135,7 +136,11 @@ static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &p
|
|||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomXform::Define(stage, pxr::SdfPath(params.root_prim_path));
|
||||
for (auto path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) {
|
||||
auto xform = pxr::UsdGeomXform::Define(stage, path);
|
||||
/* Tag generated prims to allow filtering on import */
|
||||
xform.GetPrim().SetCustomDataByKey(pxr::TfToken("Blender:generated"), pxr::VtValue(true));
|
||||
}
|
||||
}
|
||||
|
||||
static void report_job_duration(const ExportJobData *data)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,52 @@ static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_setting
|
|||
copy_m4_m3(r_settings->conversion_mat, rmat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest level of Blender generated roots
|
||||
* so that round tripping an export can be more invisible
|
||||
*/
|
||||
static void find_prefix_to_skip(pxr::UsdStageRefPtr stage, ImportSettings *r_settings)
|
||||
{
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::TfToken generated_key("Blender:generated");
|
||||
pxr::SdfPath path("/");
|
||||
auto prim = stage->GetPseudoRoot();
|
||||
while (true) {
|
||||
|
||||
uint32_t child_count = 0;
|
||||
for (auto child : prim.GetChildren()) {
|
||||
if (child_count == 0) {
|
||||
prim = child.GetPrim();
|
||||
}
|
||||
++child_count;
|
||||
}
|
||||
|
||||
if (child_count != 1) {
|
||||
/* Our blender write out only supports a single root chain,
|
||||
* so whenever we encounter more than one child, we should
|
||||
* early exit */
|
||||
break;
|
||||
}
|
||||
|
||||
/* We only care about prims that have the key and the value doesn't matter */
|
||||
if (!prim.HasCustomDataKey(generated_key)) {
|
||||
break;
|
||||
}
|
||||
path = path.AppendChild(prim.GetName());
|
||||
}
|
||||
|
||||
/* Treat the root as empty */
|
||||
auto path_string = path.GetString();
|
||||
if (path == pxr::SdfPath("/")) {
|
||||
path = pxr::SdfPath();
|
||||
}
|
||||
|
||||
r_settings->skip_prefix = path;
|
||||
}
|
||||
|
||||
enum {
|
||||
USD_NO_ERROR = 0,
|
||||
USD_ARCHIVE_FAIL,
|
||||
|
|
@ -234,6 +280,7 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
|||
}
|
||||
|
||||
convert_to_z_up(stage, &data->settings);
|
||||
find_prefix_to_skip(stage, &data->settings);
|
||||
data->settings.stage_meters_per_unit = UsdGeomGetStageMetersPerUnit(stage);
|
||||
|
||||
/* Set up the stage for animated data. */
|
||||
|
|
@ -608,7 +655,7 @@ CacheArchiveHandle *USD_create_handle(Main * /*bmain*/,
|
|||
|
||||
blender::io::usd::ImportSettings settings{};
|
||||
convert_to_z_up(stage, &settings);
|
||||
|
||||
find_prefix_to_skip(stage, &settings);
|
||||
USDStageReader *stage_reader = new USDStageReader(stage, params, settings);
|
||||
|
||||
if (object_paths) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
|
||||
#include <map>
|
||||
|
|
@ -59,6 +60,8 @@ struct ImportSettings {
|
|||
* correct millimeter scale that Blender uses for camera parameters. */
|
||||
double stage_meters_per_unit;
|
||||
|
||||
pxr::SdfPath skip_prefix;
|
||||
|
||||
ImportSettings()
|
||||
: do_convert_mat(false),
|
||||
from_up(0),
|
||||
|
|
@ -71,7 +74,8 @@ struct ImportSettings {
|
|||
read_flag(0),
|
||||
validate_meshes(false),
|
||||
cache_file(NULL),
|
||||
stage_meters_per_unit(1.0)
|
||||
stage_meters_per_unit(1.0),
|
||||
skip_prefix(pxr::SdfPath{})
|
||||
{
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -283,6 +283,14 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/* If we find prims that have been auto generated by Blender, we skip them on import
|
||||
* so that the imported scene can closely match the exported scene */
|
||||
if (!settings_.skip_prefix.IsEmpty()) {
|
||||
if (settings_.skip_prefix.HasPrefix(prim.GetPath())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we can merge an Xform with its child prim. */
|
||||
if (child_readers.size() == 1) {
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class USDStageReader {
|
|||
*/
|
||||
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns true if the specified UsdPrim is a UsdGeom primitive,
|
||||
* procedural shape, such as UsdGeomCube.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -97,11 +97,11 @@ class USDExportTest(AbstractUSDTest):
|
|||
|
||||
# if prims are missing, the exporter must have skipped some objects
|
||||
stats = UsdUtils.ComputeUsdStageStats(str(export_path))
|
||||
self.assertEqual(stats["totalPrimCount"], 15, "Unexpected number of prims")
|
||||
self.assertEqual(stats["totalPrimCount"], 16, "Unexpected number of prims")
|
||||
|
||||
# validate the overall world bounds of the scene
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
scenePrim = stage.GetPrimAtPath("/scene")
|
||||
scenePrim = stage.GetPrimAtPath("/root/scene")
|
||||
bboxcache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_])
|
||||
bounds = bboxcache.ComputeWorldBound(scenePrim)
|
||||
bound_min = bounds.GetRange().GetMin()
|
||||
|
|
@ -110,15 +110,15 @@ class USDExportTest(AbstractUSDTest):
|
|||
self.compareVec3d(bound_max, Gf.Vec3d(1, 2.9515805244, 2.7985136508))
|
||||
|
||||
# validate the locally authored extents
|
||||
prim = stage.GetPrimAtPath("/scene/BigCube/BigCubeMesh")
|
||||
prim = stage.GetPrimAtPath("/root/scene/BigCube/BigCubeMesh")
|
||||
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
|
||||
self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -2.7985137))
|
||||
self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 2.7985137))
|
||||
prim = stage.GetPrimAtPath("/scene/LittleCube/LittleCubeMesh")
|
||||
prim = stage.GetPrimAtPath("/root/scene/LittleCube/LittleCubeMesh")
|
||||
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
|
||||
self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -1))
|
||||
self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 1))
|
||||
prim = stage.GetPrimAtPath("/scene/Volume/Volume")
|
||||
prim = stage.GetPrimAtPath("/root/scene/Volume/Volume")
|
||||
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
|
||||
self.compareVec3d(
|
||||
Gf.Vec3d(extent[0]), Gf.Vec3d(-0.7313742, -0.68043584, -0.5801515)
|
||||
|
|
@ -143,7 +143,7 @@ class USDExportTest(AbstractUSDTest):
|
|||
|
||||
# Inspect and validate the exported USD for the opaque blend case.
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF")
|
||||
shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
opacity_input = shader.GetInput('opacity')
|
||||
self.assertEqual(opacity_input.HasConnectedSource(), False,
|
||||
|
|
@ -170,7 +170,7 @@ class USDExportTest(AbstractUSDTest):
|
|||
|
||||
# Inspect and validate the exported USD for the alpha clip case.
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF")
|
||||
shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
opacity_input = shader.GetInput('opacity')
|
||||
opacity_thres_input = shader.GetInput('opacityThreshold')
|
||||
|
|
@ -189,7 +189,7 @@ class USDExportTest(AbstractUSDTest):
|
|||
|
||||
# Inspect and validate the exported USD for the alpha blend case.
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF")
|
||||
shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
opacity_input = shader.GetInput('opacity')
|
||||
opacity_thres_input = shader.GetInput('opacityThreshold')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue